Protecting against the RDS Linux local root exploit with grsec

Tue 26 October 2010 by Christophe Devine

On october 19h, Dan Rosenberg, a security researcher at Virtual Security Research LLC, disclosed a flaw in the handling of iovec structures by the rds kernel module (original VSR advisory). Due to the lack of checks, a userland program could directly read or write at arbitrary locations, including inside kernel memory. The exploit released by Dan Rosenberg (linux-rds-exploit.c) uses this memory access to overwrite a kernel function pointer within default_security_ops and have the kernel call "commit_creds(prepare_kernel_cred(0));" thereby elevating its privileges to root.

This article discusses how the grsec patch prevents successful exploitation of this vulnerability -- in particular I'd like to thank Brad Spengler, who maintains grsecurity, for providing us with detailed information.

When starting the exploit against a grsec-hardened kernel, the first protection measure one notices is that the RDS module (rds.ko, protocol family 21) will not auto-load. This is caused by the GRKERNSEC_MODHARDEN compilation option:

grsec: From denied kernel module auto-load of net-pf-21 by /home/cde/a.out[a.out:7424] uid/euid:1000/1000 gid/egid:1000/1000, parent /bin/bash[bash:7417] uid/euid:1000/1000 gid/egid:1000/1000

In addition, Dan Rosenberg's exploit requires the resolution of five kernel symbols. The grsec patch, and especially GRKERNSEC_HIDESYM, prevents access to /proc/k{all,}syms and removes several information leaks (/proc/timers, etc.). Furthermore, during the kernel build grsec restricts the permissions on the source tree, as well as /lib/modules and /boot to prevent an attacker from resolving the adresse of kernel symbols. This protection is useful and blocks Dan's exploit "as-is". But it could be worked around, because in this case using arbitrary kernel reads one could fingerprint the targeted functions at runtime (at the cost of a much more complicated exploit).

There are two final protection at work in the x86 case : PAX_USERCOPY and PAX_UDEREF. Quoting Brad Spengler,

On x86, the UDEREF implementation completely kills exploitation of the RDS vulnerability -- it's not a question of making it more difficult there. This is because on x86, UDEREF uses the appropriate segments when doing userland<->kernel copies, even when the API is the _._copy*user* variety.

The situation is a bit different on the x64 architecture. Introduced by AMD in 2003, it simplified many parts of the ancient x86 architecture, in particular segmentation; on x86, PaX is able to fully separate userland and kernel virtual memory spaces by adjusting the segments characteristics (see uderef.txt for a full explanation). This is no longer possible on x64, leaving the kernel more vulnerable to userland-pointer-dereferencing exploits. As Brad says:

On x64 UDEREF as you may know is a bit different (as described on: Announcing UDEREF/amd64) and one of those differences is applicable here: UDEREF on x64 doesn't do any extra checking for the _._copy*user* or _._put_user/_._get_user APIs (assumption being that there should have been a prior access_ok with the proper checking, so it wouldn't have to be done on every call for performance reasons). So on x64 the bug can be exploited (assuming MODHARDEN isn't being used, or the module was needed by root for something). In the future, we may add extra checking for _._copy*user*, after benchmarking the performance hit.

While the kernel maintainers have implemented over the years many of PaX/grsec features, the fact that stock kernels from Ubuntu, RedHat and SuSE are vulnerable is a useful reminder that security in depth matters; it is a good idea compile a custom kernel to reduce the attack surface, and apply the grsec patch configured in high security mode. Of course, good practices in IT security may be considered too (public-key auth, fine-grained access control, auditing, and so forth).