COMMAND
kernel
SYSTEMS AFFECTED
NetBSD
PROBLEM
Bill Sommerfeld found following. A subtle bug in validation of
user-supplied arguments to a syscall can allow allow user
applications on the i386 platform to transfer control to arbitrary
addresses in kernel memory, bypassing normal system protections.
This problem is only present on the i386 platform, and only when
the USER_LDT kernel configuration option is enabled at compile
time. Unfortunately, this option is present in the GENERIC and
GENERIC_LAPTOP kernel configurations shipped with NetBSD releases,
as well as several examples, so many users will be affected.
The problem stems from the complexity of the x86 processor
architecture memory protection system, and other systems are known
to be affected. This includes systems with code derived from
NetBSD as well as other independently-written systems, indicating
that the same mistake may have been repeated elsewhere as well.
The Intel i386 architecture supports a complex and confusing
protection and segmentation model relying on rings, segment
selectors, segment descriptors, gates, descriptor tables and the
like.
The Local Descriptor Table (LDT) is usually a per-process segment;
most unix OS's provide system calls allowing application
processes to add new segments to the LDT. In NetBSD, this option
is enabled with "options USER_LDT" in the kernel configuration
file, and is generally enabled for the WINE Windows emulator.
One segment type is a "call gate", which allows code in outer
rings to make calls to code in inner, more priviledged rings.
Call gates are one of the three (or more?) different ways to
implement system calls on the x86. The segment descriptor in the
LDT for a call gate contains a segment selector and offset; when
a program running in an outer ring executes a "ljmp" or "lcall"
instruction which specifies the segment selector of a call gate,
control is transferred to the specified offset of the specified
code segment. If the gate target is an inner-ring code segment,
a ring transition occurs; therefore it is vital for code which
creates call gate descriptors to validate that the target segment
and offset of the gate is appropriate.
While reviewing code in NetBSD, it was discovered that the NetBSD
"i386_set_ldt" syscall did not do appropriate validation of call
gate targets; in short, it's possible to create a call gate in
the LDT which transfers control to an arbitrary address in the
kernel.
The problem affects other Operating Systems also:
* OpenBSD has the same bug, in code inherited directly from NetBSD
* FreeBSD also once had the bug, but it was eliminated a couple of
years ago as part of a change to disable setting call gates.
* Sun Solaris/x86 has the same bug, in a different implementation
of a similar mechanism.
* The problem is not necessarily limited to unix; any OS for the
x86 which allows an unprivileged program to create gate
descriptors in its LDT could have this bug.
A common misunderstanding of how gate descriptors work may result
in the programmer believing they've defended against this attack
(by checking the gate's DPL) without having done so (you need to
check the DPL of the code segment that the gate targets).
Note that this behaviour is not restricted to Intel processors;
the bug applies to implementations of the x86 architecture by
other manufacturers as well.
SOLUTION
Systems running NetBSD/i386 with the USER_LDT kernel option
enabled are vulnerable. This includes users running the GENERIC
kernel configurations distributed with releases, or users who have
built their own kernel configurations based on GENERIC and not
removed the USER_LDT option. The most effective and simplest
workaround, for users not requiring this option for Wine or
similar emulators, is to build a kernel without "options
USER_LDT". For users who need this option enabled, kernel sources
must be updated and a new kernel built and installed.
Systems running releases older than NetBSD 1.4 should be upgraded
to NetBSD 1.4.3 before applying the fixes described here. Systems
running NetBSD-current dated from before 2001-01-17 should be
upgraded to NetBSD-current dated 2001-01-17 or later. Systems
running NetBSD-release-1-4 or NetBSD-release-1-5 dated from before
2001-01-18 should be upgraded to 2001-01-18 later.
The following patch to /sys/arch/i386/i386/sys_machdep.c should be
applied before rebuilding a new kernel with USER_LDT enabled.
This patch can be applied (with offset differences) to
NetBSD-1.4.x, NetBSD-1.5 or NetBSD-current kernel sources.
Index: sys_machdep.c
===================================================================
RCS file: /cvsroot/syssrc/sys/arch/i386/i386/sys_machdep.c,v
retrieving revision 1.54
retrieving revision 1.55
diff -c -r1.54 -r1.55
*** sys_machdep.c 2001/01/16 01:50:36 1.54
--- sys_machdep.c 2001/01/16 23:32:21 1.55
***************
*** 222,227 ****
--- 222,238 ----
break;
case SDT_SYS286CGT:
case SDT_SYS386CGT:
+ /*
+ * Only allow call gates targeting a segment
+ * in the LDT or a user segment in the fixed
+ * part of the gdt. Segments in the LDT are
+ * constrained (below) to be user segments.
+ */
+ if (desc.gd.gd_p != 0 && !ISLDT(desc.gd.gd_selector) &&
+ ((IDXSEL(desc.gd.gd_selector) >= NGDT) ||
+ (gdt[IDXSEL(desc.gd.gd_selector)].sd.sd_dpl !=
+ SEL_UPL)))
+ return (EACCES);
/* Can't replace in use descriptor with gate. */
if (n == fsslot || n == gsslot)
return (EBUSY);