Description of problem:
Jake Edge has reported the following gcc kernel related potential
security issue on LWN:
A change to GCC for a recent release coupled with a kernel bug has created a
messy situation, with possible security implications. GCC changed some
assumptions about x86 processor flags, in accordance with the ABI standard, that
can lead to memory corruption for programs built with GCC 4.3.0. No one has come
up with a way to exploit the flaw, at least yet, but it clearly is a problem
that needs to be addressed.
The problem revolves around the x86 direction flag (DF), which governs whether
block memory operations operate forward through memory or backwards. The main
use for the flag is to support overlapping memory copies, where working
backwards through memory may be required so that the data being copied does not
get overwritten as the copy progresses. Debian hacker Aurélien Jarno reported
the problem to linux-kernel on March 5th, which was found when building Steel
Bank Common Lisp (SBCL) using the new compiler.
GCC's most recent release, 4.3.0, assumes that the direction flag has been
cleared (i.e. memory operations go in a forward direction) at the entry of each
function, as is specified by the ABI (which is, somewhat amusingly, found at
sco.com [PDF]). Unfortunately, this clashes with Linux signal handlers, which
get called, incorrectly, with the flag in whatever state it was in when the
signal occurred. This has the effect of leaking one bit of state from the user
space process that was running when the signal occurred to the signal handler,
which could be in another process.
That, in itself, is a bug, seemingly with fairly minimal impact. Prior to 4.3,
GCC would emit a cld (clear direction flag) opcode before doing inline string or
memory operations, so those operations would start from a known state. In 4.3,
GCC relies on the ABI mandate that the direction flag is cleared before entry to
a function, which means that the kernel needs to arrange that before calling a
signal handler. It currently doesn't, but a small patch fixes that.
The window of vulnerability is small, but was observed in SBCL. The sequence of
events that would lead to memory corruption are as follows:
* a user space program does an operation (memmove() for example) that
* a signal occurs for some process
* the kernel calls the signal handler
* the signal handler does a memmove() in what it thinks is a forward
* the memory is copied in the reverse direction, leading to corruption
Link to the post:
Link to upstream fix:
Additional comment from BZ#436131:
The debian folks reported that kernel doesn't ensure direction flag is cleared
upon entry to signal handler, which violates both i?86 and x86_64 ABIs.
Old GCCs conservatively used cld anyway before using any instructions that use
that flag, but GCC 4.3 no longer does that, it relies on the ABI guarantees that
on entry to a function the direction flag must be cleared.
Anything that uses std instruction must cld again before calling another
function or before returning from function.
Unfortunately, if async signal is sent while a thread has std flag set, kernel
will start a signal handler with DF flag set.
The fix is addition of regs->eflags &= ~X86_EFLAGS_DF; or similar in
setup_frame/setup_rt_frame (i386, x86_64, x86_64 32-bit support).
While only code compiled with GCC 4.3 and later will be affected by this bug,
RHEL5 kernels are often used with later Fedora userland (e.g. koji buildboxes),
so IMHO if at all possible to fix should be backported to RHEL5.2 and perhaps
even RHEL4.7 kernels.
Created attachment 297943 [details]
Testcase for finding out the value of the DF flag.
Comments from Jason Baron and reply from Jakub Jelinek describing the
current behaviour and the nature of the bug:
Whenever i run the test case (sending SIGUSR1 to the test process), i get: 'DF =
1'. indicating that the the df flag is set...I expected it to be unset b/c from
reading this gcc clears the df flags...i've tried older distros and compat-gcc
and still i get DF = 1...what am i missing? thanks.
If you get DF = 1, then that means the kernel is buggy. The psABI says that the
DF flag must be clear upon entry to a function and also on exit from function.
GCC <= 4.2.x would add cld just in case, whenever it used some string
instruction (movs*/stos*/loads*/cmps* etc.), GCC 4.3.0 relies on the ABI
guarantee. As GCC itself never issues std insn, it is just inline or
out-of-line assembly which has to reset cld after it did std (AFAIK all such
assembly I saw does that), the kernel has to start a process with cleared DF
flag (also done) and
the kernel signal handler needs to clear it for the signal handler (this is the
Valid link to upstream kernel commit:
And the following one:
Can you post the rhel3 committed patch for clarification? I see some usage of
the DF flag but it's unlike the upstream version.
This was addressed via:
Red Hat Enterprise Linux version 3 (RHSA-2008:0211)
Red Hat Enterprise Linux version 5 (RHSA-2008:0233)
Red Hat Enterprise Linux version 4 (RHSA-2008:0508)