|Benefits for LWN subscribers|
The primary benefit from subscribing to LWN is helping to keep us publishing, but, beyond that, subscribers get immediate access to all site content and access to a number of extra site features. Please sign up today!
So far, the x32 ABI—a 32-bit ABI for running on x86 processors in 64-bit mode—is not widely used. Only a few distributions have enabled support for it in their kernels (notably Ubuntu), which reduces the impact of a recently discovered local privilege escalation somewhat, but the bug has been in the kernel since 2012. It's a nasty hole, that required a quick fix for Ubuntu 13.10 (and two hardware enablement kernels for 12.04 LTS: linux-lts-raring and linux-lts-saucy).
It is the x32 version of recvmmsg() that has the bug. In the compat_sys_recvmmsg() function that is part of the compatibility shim for handling multiple ABIs in the kernel, a user-space pointer for the timeout value is treated as a kernel pointer (rather than copied using copy_from_user()) for the x32 ABI. The value of the timeout pointer is controlled by the user, but it gets passed as a kernel pointer that __sys_recvmmsg() (which implements the system call) will use. The kernel will dereference the pointer for both reading and writing, which allows a local, unprivileged user to get root privileges.
The problem was reported to the closed firstname.lastname@example.org and linux-distros mailing lists on January 28 by Kees Cook, after "PaX Team" reported it to the Chrome OS bug tracker (in a still-restricted entry). It was embargoed for two days to give distributions time to get fixes out. After that, "Solar Designer" reported it publicly since Cook was traveling. It is a serious bug, but is somewhat mitigated by the fact that few distributions have actually enabled the ABI.
The x32 ABI came about largely to combat the amount of memory wasted on x86_64 processors for 64-bit pointers (and long integers) in programs that did not require the extra 32 bits for each value. It allows programs to use the extra registers and other advantages that come with x86_64 without paying the penalty of extra memory usage. In theory, that should lead to less memory usage and faster programs due to a smaller cache footprint. So far, though, those benefits are somewhat speculative—and controversial.
X32 does exist in the kernel, however, and can be enabled with the CONFIG_X86_X32 flag. If it is enabled, any user can build an x32 program using GCC with the -mx32 flag. The kernel will recognize such a binary and handle it appropriately.
The bug was introduced in a February 2012 commit that was adding support for 64-bit time_t values to x32. The problematic code is as follows (from compat_sys_recvmmsg()):
if (COMPAT_USE_64BIT_TIME) return __sys_recvmmsg(fd, (struct mmsghdr __user *)mmsg, vlen, flags | MSG_CMSG_COMPAT, (struct timespec *) timeout);The timeout value is passed to that function as:
struct compat_timespec __user *timeoutIt is clearly annotated as a user-space pointer, but just gets passed to __sys_recvmmsg(). The fix is to use compat_get_timespec() to copy the data from user space before the call to __sys_recvmmsg() and compat_put_timespec() to copy any changes back to user space afterward.
Exploits have started to appear (for example, one by rebel and another by saelo). The basic idea is to use the fact that recvmmsg() will write the amount of time left in the timeout to the location specified by the timeout pointer. Since the value of that pointer is controlled by the user, it can be arranged to write known values (another exploit-controlled address, say) to somewhere "interesting", for example to a function pointer that gets called when the /proc/sys/net/core/somaxconn file is opened (as rebel's exploit does). The program will already have arranged to have "interesting" code (to gain root privileges) located at that address. When the function is called by the kernel via that pointer, the exploit's code is run.
Users of Ubuntu 13.04 should note that it reached its end of life two days before the bug was found, so no update for that kernel has been issued. One possible solution for those who have not yet upgraded to 13.10 (or are running some other distribution kernel and do not want to patch and build their kernel) is a module that disables the x32 version of the recvmmsg() system call.
As PaX Team noted in the report (quoted by Solar Designer), the presence of this bug certainly calls into question how much testing (fuzz testing in particular) has been done on the x32 ABI. For a bug of that nature to exist in the kernel for two years would also seem to indicate that it isn't just testing that has fallen by the wayside—heavy use would also seem to be precluded. In any case, the problem was found, reported, and fixed, now it is up to users (and any distributions beyond Ubuntu since we have received no other security advisories beyond those mentioned above) to update their kernels.
Copyright © 2014, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds