i386/84842: i386_set_ioperm(2) timing issue

Bruce Evans bde at zeta.org.au
Fri Aug 12 23:50:11 GMT 2005

The following reply was made to PR i386/84842; it has been noted by GNATS.

From: Bruce Evans <bde at zeta.org.au>
To: Bruce Evans <bde at FreeBSD.org>
Cc: arundel at h3c.de, freebsd-gnats-submit at FreeBSD.org
Subject: Re: i386/84842: i386_set_ioperm(2) timing issue
Date: Sat, 13 Aug 2005 09:44:33 +1000 (EST)

 On Fri, 12 Aug 2005, Bruce Evans wrote:
 > The problem seems to be that the TSS is not loaded by the syscall.  The
 > i/o permissions bitmap is in the TSS and I think think the TSS must be
 > reloaded for the new bitmap to be seen.  The TSS is reloaded on the next
 > context switch but doesn't seem to be loaded anywhere else in normal
 > execution (it is also loaded at boot time and for vm86 BIOS calls and
 > returns).
 > Try adding an ltr(gsel_tss) near the end of i386_set_ioperm().
 Further reading and testing showed that the bug is a relatively new one
 and fixing it cleanly is not so easy.  The CPU apparently examines the
 permissions bitmap on every access (that's one reason i/o accesses are
 so slow :-), so the TSS [register] doesn't need to be reloaded after
 every change.  However, for the first access the bitmap is usually
 empty since the loading of the new TSS that holds the bitmap has been
 broken by an optimization.  From an old version of i386/sys_machdep.c:
 % int
 % i386_extend_pcb(struct thread *td)
 % {
 % ...
 % 	/* switch to the new TSS after syscall completes */
 % 	td->td_flags |= TDF_NEEDRESCHED;
 This was broken by optimizing null context switches.  Now the switch to
 the new TSS doesn't normally actually occur after the syscall completes,
 since the scheduler normally reschedules the same thread and mi_switch()
 now avoids calling cpu_switch() in this case.
 % ...
 % }
 % static int
 % i386_set_ioperm(td, args)
 % 	struct thread *td;
 % 	char *args;
 % {
 % ..
 % 	if (td->td_pcb->pcb_ext == 0)
 % 		if ((error = i386_extend_pcb(td)) != 0)
 % 			return (error);
 % 	iomap = (char *)td->td_pcb->pcb_ext->ext_iomap;
 This extends the pcb on the first call to i386_set_ioperm() for a thread,
 so if the TSS were reloaded after the call as intended then the first
 call would be less broken than subsequent calls.
 % 	if (ua.start + ua.length > IOPAGES * PAGE_SIZE * NBBY)
 % 		return (EINVAL);
 % 	for (i = ua.start; i < ua.start + ua.length; i++) {
 % 		if (ua.enable) 
 % 			iomap[i >> 3] &= ~(1 << (i & 7));
 % 		else
 % 			iomap[i >> 3] |= (1 << (i & 7));
 % 	}
 Testing shows than an ltr() is not needed here.  It is sufficient to
 sleep (in the application) after the first call so that the new TSS
 gets loaded.  For subsequent calls, the TSS descriptor doesn't change,
 and the TSS apparently doesn't need to be reloaded to get changes to
 the contents of the TSS seen.
 % 	return (error);
 % }
 For a quick fix, a tsleep(..., 1) in 386_extend_pcb() should work as well
 as a sleep in the application (unless/until somone modifies short sleeps
 to not switch).

More information about the freebsd-i386 mailing list