A serious security vulnerability has recently emerged in Linux kernel. Non-root users can obtain root permissions through the exploit Vulnerability. This is not uncommon. It is worth mentioning that this patch seems so common that most of us will not think this is a security issue.
Let's take a look at the patch for this issue, which is the following:
static int perf_swevent_init(struct perf_event *event) {-int event_id = event->attr.config;+u64 event_id = event->attr.config; if (event->attr.type != PERF_TYPE_SOFTWARE) return -ENOENT;
At first glance, we felt that this was probably just a small warning reported by the compiler. How could it cause such a serious security problem?
In the unpatched code, event_id is a signed integer, and only the upper bound is checked in the following two lines of code:
if (event_id>= PERF_COUNT_SW_MAX) return -ENOENT;
If the passed event-> attr. config value exactly sets the symbol bit, the event_id will become a negative value and will be able to escape the above check.
What does a negative value mean? Continue with the code below:
if (!event->parent) { int err; err = swevent_hlist_get(event); if (err) return err; atomic_inc(&perf_swevent_enabled[event_id]); event->destroy = sw_perf_event_destroy; }
This indicates that the array is out of bounds! At this time, you should start to sweat. To continue, the array perf_swevent_enabled [] on RHEL6 is defined:
atomic_t perf_swevent_enabled[PERF_COUNT_SW_MAX];
Atomic_t is basically an int, that is, perf_swevent_enabled [] is an integer array. When you use event_id to access this array, the value of event_id is multiplied by 4 and the starting address of the array is added. Easy!
Well, we can get the perf_swevent_enabled address through the System. map file:
ffffffff81f360c0 B perf_swevent_enabled
Then, when event-> attr. config = 0 xffffffff (I .e., signed-1), we will eventually get the following on x86_64:
0xffffffffffffffff * 4 + 0xffffffff81f360c0 == 0xFFFFFFFF81F360BC
Similarly, when event-> attr. config = 0 xfffffffe, we get:
0xfffffffffffffffe * 4 + 0xffffffff81f360c0 == 0xFFFFFFFF81F360B8
Therefore, the above mentioned atomic_inc () actually adds the values stored in the first two addresses, both of which point to the kernel space (see Documentation/x86/x86_64/mm.txt )! At this time, you should feel nervous...
More interesting things will happen in the sw_perf_event_destroy () function. It is called when the fd returned by perf_event_open () is disabled. The RHEL6 is defined as follows:
static void sw_perf_event_destroy(struct perf_event *event){ u64 event_id = event->attr.config; WARN_ON(event->parent); atomic_dec(&perf_swevent_enabled[event_id]); swevent_hlist_put(event);}
Obviously, event_id is of the unsigned type this time. So, same as above. When event-> attr. config = 0 xffffffff, we get:
0xffffffff * 4 + 0xffffffff81f360c0 == 0x0000000381F360BC
When event-> attr. config = 0 xfffffffe, we get:
0xfffffffe * 4 + 0xffffffff81f360c0 == 0x0000000381F360B8
So the atomic_dec () here actually reduces the value in the user space address.
The above is "Basic Knowledge". With this knowledge, we can see what exploit code has done. The code snippets are as follows:
#define BASE 0x380000000
#define SIZE 0x010000000
assert((map = mmap((void*)BASE, SIZE, 3, 0x32, 0,0)) == (void*)BASE);
memset(map, 0, SIZE);
sheep(-1); sheep(-2); // sheep will just invoke perf_event_open
// syscall with attr.config set to the param
for (i = 0; i
assert(map[i+1]);
break;
}
It first sets the starting address of mmap () to a memory area of 0 × 380000000. Then, perf_event_open () is called twice with attr. config as-1 and-2 respectively (). According to the previous calculation, it actually adds the memory values at 0xFFFFFFFF81F360BC and 0xFFFFFFFF81F360B8 respectively, reducing the values of 0x0000000381F360BC and 0x0000000381F360B8. The for loop is to find the address of the memory to be reduced, so that the address of the perf_swevent_enabled [] array can be calculated (System. map does not always exist. If it exists and is readable, we can directly read this value ).
With this address, we can manipulate the 32bit value somewhere in the kernel and add one to the value. Because of this, the author cleverly chooses the Interrupt Descriptor Table-an array of 16-byte descriptors, and its address can be obtained through the sidt command. The descriptor structure is defined as follows:
Offset Size Description0 2 Offset low bits (0..15)2 2 Selector (Code segment selector)4 1 Zero5 1 Type and Attributes (same as before)6 2 Offset middle bits (16..31)8 4 Offset high bits (32..63)12 4 Zero
The most interesting thing here is that the offset is 8, and the value of x86_64 is 0 xffffffff. The Interrupt Descriptor selected by the author is 0 × 4, so its offset relative to the Interrupt Descriptor Table is actually 0 × 48. The current task is to use perf_swevent_enabled [] to calculate the memory address with an offset of 8 in the Interrupt Descriptor and add one to it! The following code is used for this task:
sheep(-i + (((idt.addr&0xffffffff)-0x80000000)/4) + 16);
I is an offset of perf_swevent_enabled [] Found in the for loop, idt. addr is the absolute kernel address of the Interrupt Descriptor Table. take its low 32-bit value and subtract 0x80000000 to get the low 28-bit value as the offset. Divide by 4 because the array is int, the last 16 is the offset in the 0 × 4 Interrupt Descriptor (4 has been removed), so the parameters in sheep () are the expected offset, in this case, the kernel adds 1 to the 0 xffffffff offset of 8 in the 0x4 Interrupt Descriptor to 0, which becomes the address of the user space! So the subsequent int 0 × 4 will jump to the user space and the Code already set !!!
However, this code is quite cool, but it means to change the uid/gid of the current process to 0 to improve the permission, so you finally get a shell with the root permission! Attack success!
Note: The above link may not be available, the exploit code can also be seen here: https://gist.github.com/onemouth/5625174