The brk() vulnerability
| Process memory |
|---|
| 0x00000000-0x08047FFF
Unmapped area |
| 0x08048000-0x08??????
Program text |
| 0x08??????-
Named memory and heap |
| 0x40000000-0x4???????
Shared libraries, shared memory segments |
| 0x4???????-0xbfffcfff
Unmapped area |
| 0xbfffd000-0xc0000000
Stack |
The bulk of the memory used by a program for its variables and heap storage is found in the section marked "named memory and heap" in the diagram. This memory area is initially made large enough to hold the static variables created by the program, but, as soon as more memory is required (to satisfy malloc() calls, perhaps) that region of memory must be expanded. Since the beginning, Unix-like systems have provided a system call (named brk()) which can be used to change the size of the heap area. The caller simply passes in the virtual address indicating where the new "break point" should be set, and the area is expanded or contracted as need be.
Back in September, Andrew Morton noticed that no sort of bounds checking was being applied to the address passed to brk(). In theory, this omission means that a process could request an arbitrarily large heap area. In practice, most programs would not get that far. The kernel does not allow virtual memory areas to overlap each other, so any expansion of the heap area that caused it to impinge upon the shared library areas starting at 0x40000000 would be rejected with an error. So it would appear that the lack of bounds checking was never that serious of a problem; all it could do is allow a user to set up some huge page tables.
Obviously, the situation is worse than that. The memory layout diagram is missing one important area; on ia-32 systems, the kernel itself is mapped in starting at 0xc0000000 - right above the process stack area. Processes normally do not have any access to that part of memory, of course. But, as it turns out, if you can convince brk() to expand your heap area up into the kernel's address range, you have direct access to the kernel code and data areas. At that point, the integrity of the system is lost.
The key to cracking the system is changing the process memory layout so that the heap area can be expanded into the kernel's space. You cannot easily do that with a normal C program, but, with a bit of assembly trickery things become easier. A proof of concept exploit has already been posted to Bugtraq, so one can see how it is done. It is really a matter of (1) moving the program origin up into the highest part of virtual memory, where the stack usually lives, and (2) shorting out the C library's startup code which sets up the address space in the first part. Once you do that, an unpatched system will happily expand your heap area into kernel space.
So, as the Debian Project learned at great cost, this little omission in the implementation of the brk() system call is fully usable for a complete local root exploit.
There have been a lot of questions about how such a vulnerability could remain unfixed for so long. In fact, it was patched in the 2.6.0-test series almost as soon as it was found. The fix also went to Marcelo Tosatti, the 2.4 maintainer, but it was too late for the 2.4.22 release, which happened on August 25. So the fix was merged into 2.4.23-pre7, which came out on October 9. The current 2.4.23 kernel is not vulnerable - but that was too late to help Debian.
The real problem, of course, is that nobody realized the severity of this bug. Had the kernel developers understood that current kernels were vulnerable to this sort of attack, the alert would have gone out and the various distributors would have sent out the usual set of updates. But this patch was just one of over 2000 patches merged by Linus in September. It would seem that it simply became part of the stream of fixes, and nobody looked at it particularly closely.
Except, of course, somebody did. Chances are, the posting of this fix drew an attacker's attention to the brk() code. With a bit of effort, the exploit got written, and now thousands or millions of systems are at risk.
What the kernel (along with most other projects) needs is more friendly
eyes looking for this sort of problem. We do reasonably well, in that most
vulnerabilities are found and fixed by the good guys before they can be
exploited. There are cases where that doesn't happen, however, and the
brk() bug was one of them. Security auditing is hard work, and
usually unrewarding. But it would have been nice if somebody had looked
hard enough at this problem to raise the alarm.
