LWN.net Logo

A nasty FPU bug

The problem was initially reported as a gcc bug. If you execute this code:

    static void Handler(int ignore)
    {
	char fpubuf[108];
	__asm__ __volatile__ ("fsave %0\n" : : "m"(fpubuf));
	__asm__ __volatile__ ("frstor %0\n" : : "m"(fpubuf));
    }

in a signal handler, the system (or, at least, the CPU that was running the code) will freeze up hard. Ways of locking up the system from an unprivileged user-space program are generally considered to be bad news; they also, in general, are not seen as compiler bugs. A bit of digging turned up the real problem, and the latest kernel denial of service vulnerability was found.

In theory, the fsave instruction above saves the floating-point unit (FPU) status into the fpubuf array; the subsequent frstor should simply restore the same state back into the FPU. Unfortunately, the above code is incorrect; the assembly instructions should read "m"(*fpubuf) to actually store the state into the fpubuf array. The code, as written, restores from the wrong address, corrupting the state of the FPU and, in particular, setting some exception flags.

FPU exceptions do not result in immediate kernel traps; instead, the trap happens when the next floating-point command is executed. As it happens, the kernel checks when a signal handler returns and, if that handler has used any floating-point instructions, the kernel performs an fwait instruction to ensure that the last operation is complete. That fwait causes the floating point exception caused by the corrupt restore to be delivered as a kernel trap.

The kernel has a way of dealing with floating point traps; it saves the FPU state and queues up a floating point exception signal for the current process. It also sets the TS ("task switched") processor flag to indicate that the FPU state may be other than expected. At that point, it returns to the place where the exception occurred.

Normally, as part of returning from the trap, the kernel would simply deliver the floating-point exception signal to user space and get on with life. But, in this case, the kernel is returning back to kernel space, and back to the same fwait instruction that caused the problem in the first place. That instruction sees the TS flag and generates another trap. The handler for this trap knows just what to do in response to a TS flag; it restores the saved FPU state and returns. The saved FPU state is, however, the corrupted state which was in effect before the first attempt to execute fwait. So, at this point, the loop is closed and a new floating-point trap will be generated. This will go on for a while.

The fix is relatively straightforward, once the problem is understood. The kernel simply clears any pending exceptions before executing fwait, and the problem goes away. All that is left is the updating and rebooting of large numbers of vulnerable systems.

(Thanks to Sergey Vlasov, whose analysis of the problem made this article much easier to write.)


(Log in to post comments)

A nasty FPU bug

Posted Jun 17, 2004 16:50 UTC (Thu) by Duncan (guest, #6647) [Link]

/THAT'S/ what all the hubbub has been about. I read the reference to the
bug on the Gentoo-devel list, but the devels (rightly, IMO) didn't react
as I guess the poster expected, and pretty much said "it'll be handled
upstream much more efficiently than we can handle it here", and left it
at that. Later, I saw mention on Gentoo-user, and think I saw fleeting
mention of it here on LWN as well. However, I didn't know what it was all
about.

That said, I wasn't incredibly anxious about it either, because I've been
an LWN subscriber and reader for long enough I knew it'd probably all be
explained here, in the weekly edition, clear as crystal, and so it was!

Thanks!

Very literally, the weekly kernel sections are the reason I subscribe to
LWN. The various Linux new items I could get elsewhere, and there are
plenty of places to get opinion pieces on Linux. However, I've yet to
find anything that CLOSE to matches LWN's weekly kernel columns for
clarity and efficiency. (Reading LKML would often eventually be as clear,
and certainly at times entertaining, but unfortunately, I haven't yet
found the time, so it would lack in efficiency.) The other content may be
replacable tho at some loss of focus and efficiency within the community.
There IS no replacement for LWN's weekly kernel section.

Duncan, continuing proud LWN subscriber!

A nasty FPU bug

Posted Jun 17, 2004 23:14 UTC (Thu) by kunitz (subscriber, #3965) [Link]

I'm just wondering what the fwait should do after the fnclex. The
instruction fnclex clears all exceptions including the ES flag, so the
following fwait will not generate traps anymore.

So the patch could simply exchange fwait with fnclex.

TS flag?

Posted Jun 18, 2004 21:26 UTC (Fri) by giraffedata (subscriber, #1954) [Link]

The whole explanation turns on the involvement of the TS flag. What's a TS flag?

TS flag?

Posted Jun 18, 2004 22:10 UTC (Fri) by corbet (editor, #1) [Link]

From the article: "It also sets the TS ("task switched") processor flag to indicate that the FPU state may be other than expected."

That's really what the flag means: a task switch has occurred, and the contents of the FPU may not be what the current task is expecting. The flag will cause a trap when an attempt is made to use the FPU to prevent somebody from getting strange and unpredictable results.

TS flag?

Posted Jun 18, 2004 23:27 UTC (Fri) by giraffedata (subscriber, #1954) [Link]

Hmm. So why would the floating point exception handler set it? As you described the handler's function, it neither switches tasks nor changes the contents of the FPU. And it's already queued a FPE signal anyway!

Is this some weird interaction between the floating point exception handler, signal deliverer, and scheduler?

The straightforward fix seems to throw away a floating point exception that the original code intended to turn into a FPE signal to tell the user he botched the frstor. And it's not entirely clear to me that it catches all possible cases. Could a FP exception become pending between when the kernel clears pending exceptions and when the kernel does the fwait? Seems like the kernel should fundamentally be able to survive a floating point exception from its own instruction.

TS flag?

Posted Jun 19, 2004 23:08 UTC (Sat) by khim (subscriber, #9252) [Link]

Grrr... Read again: TS ("task switched") processor flag. And again: TS ("task switched") processor flag. Clear now ?

If not... It's not something by signal handler, sheduler or some other part of kernel. Processor (i.e. CPU) will set this flag by itself once FPU operation is executed. No program involment is needed. Idea is clear: if task does not use FPU there are no need to restore FPU state! And there are a lof of such programs... TS flag was introduced with Intel 80286(sic!) almost 20 years - way before Linux was even imagined...

TS flag?

Posted Jun 19, 2004 23:55 UTC (Sat) by giraffedata (subscriber, #1954) [Link]

>Read again

What makes you think that I would understand the same two words the 3rd time I read them, if I didn't the first time? It should be obvious that it requires different words -- probably additional words -- to get this point across to me.

You do then add some information, though, in saying that this particular processor flag is set as a side effect of an FPU instruction, as opposed to by an explicit instruction, which is what Jon's "it [the kernel] also sets the TS ... processor flag" implied to me. But the new information really makes the situation even less clear than it was.

I was hoping it would be easy for someone just to post here an explanation, for someone who isn't already an expert on IA32 floating point architecture, of what Linux is trying to do and how it failed to do it. I am certainly not the only LWN reader is this position. However, if it is not a trivial thing to explain, then this isn't the place for it -- it's not that important.

TS flag?

Posted Jun 20, 2004 0:28 UTC (Sun) by The_Flatlander (guest, #19245) [Link]

I can't resist trying to explain. (And, understand, please, I am not an assembly language programmer, so I maybe have this wrong, myself, but here goes.)

When a programmer asks the Central Processing Unit, (CPU), or the Floating Point Processor, (FPU), to do some math, when that instruction completes the CPU/FPU sets certain flags. The flags are single bits in one of the processor's registers, (that is bytes of memory resident in the processor itself). The programmer doesn't set those bits, (or flags), the processor does that itself, as part of carrying out the instruction. For example if you tell a processor to decrement a given register by one, and that causes the value in that register to go to zero, then the processor's ZF - zero flag, gets set.

In the case of this bug, loading the invalid FPU state info sets a flag in the FPU that says, an error has occured, the TS flag. The bug then is that the kernel programmer assumes that this can be fixed by restoring the state of the FPU, and so trys that, but that's what caused the error in the first place and the TS flag is again set and the kernel programmer assumes... so on and so on, until some one notices and reboots the machine. You see?

The Flatlander

Apologies to all if this truely is the wrong place for this, but it seemed a reasonable question....

TS flag?

Posted Jun 22, 2004 18:22 UTC (Tue) by farnz (guest, #17727) [Link]

Not quite right; the TS flag indicates that some FP math has occurred since the TS flag was last cleared.

When it was introduced (back in 286 days) saving and restoring FP state unnecessarily was considered a bad idea for storage and performance reasons. Used the way Intel intended, the kernel clears the flag after a task switch; if it's set when the scheduler is next entered, the task did FP math, and should have its FP state saved before a new task runs. In addition, the kernel should track whether a task has ever done any FP math; if it hasn't, you needn't restore FP state before reentering the task.

Practically, it only cuts out the save of state; glibc does some FP math in initialisation, which ensures that the kernel always restores FP state when it switches to a task.

Copyright © 2004, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds