MOKB-03-11-2006

Bug details
Title: FreeBSD 6.1 UFS filesystem ffs_mountfs() integer overflow
Description: The UFS filesystem handling code of the FreeBSD 6.1 kernel fails to properly handle corrupted data structures, leading to exploitable memory corruption (DoS) issues and possible arbitrary code execution. This particular vulnerability is caused by an integer overflow at ffs_mountfs() function.

When a crafted UFS filesystem is mounted, the amount of kernel memory being allocated can be influenced directly, controlling the value passed to kmem_alloc() via ffs_mountfs and the successive calls to malloc, uma_large_malloc and page_alloc. A large or invalid size parameter will cause a kernel panic. A low or zero value for the size parameter may lead to an exploitable heap-based buffer overflow if user controlled data is being copied from the filesystem stream.

Note: A variant of this issue will be released soon.
Author/Contributor:
References:
Proof of concept or exploit: An UFS image will be made available with the next release (related to this one) as mentioned in the description.
Debugging information:

The bug has been found using the BSD version of fsfuzzer on a FreeBSD 6.1 installation with a kernel compiled from sources available in ftp.freebsd.org. No operation except mount itself, is necessary to trigger the bug. The architecture used to conduct the tests is IA32/x86.

# uname -a
FreeBSD freebsdvm.info-pull.com 6.1-RELEASE FreeBSD 6.1-RELEASE #1: Thu Nov  2 21:15:59 UTC 2006
root@freebsdvm.info-pull.com:/usr/obj/usr/src/sys/GENERIC  i386
				

It's been observed that different conditions appear when filesystem MAC labeling is enabled, triggering the bug via different callbacks. The following 'output' is a detailed analysis of the bug using kgdb and the crash dump with a kernel image containing debug symbols:

[root@freebsdvm /sys/i386/include]# kgdb -d /usr/crash/ -n 1 /usr/obj/usr/src/sys/GENERIC/kernel.debug
[GDB will not be able to debug user-mode threads: /usr/lib/libthread_db.so: Undefined symbol "ps_pglobal_lookup"]
GNU gdb 6.1.1 [FreeBSD]
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-marcel-freebsd".

Unread portion of the kernel message buffer:
: mount pending error: blocks 37999121861655040 files 0
panic: kmem_malloc(76681216): kmem_map too small: 10866688 total allocated
Uptime: 2h3m18s
Dumping 255 MB (3 chunks)
  chunk 0: 1MB (159 pages) ... ok
  chunk 1: 254MB (65008 pages) 238 222 206 190 174 158 142 126 110 94 78 62 46 30 14 ... ok
  chunk 2: 1MB (256 pages)

#0  doadump () at pcpu.h:165
165             __asm __volatile("movl %%fs:0,%0" : "=r" (td));
(kgdb) back
#0  doadump () at pcpu.h:165
#1  0xc064dee1 in boot (howto=260) at /usr/src/sys/kern/kern_shutdown.c:402
#2  0xc064e178 in panic (fmt=0xc08baffd ) at /usr/src/sys/kern/kern_shutdown.c:558
#3  0xc07bc3a1 in kmem_malloc (map=0xc10430c0, size=76681216, flags=2) at /usr/src/sys/vm/vm_kern.c:299
#4  0xc07b3c6e in page_alloc (zone=0x0, bytes=76681216, pflag=0x0, wait=2) at /usr/src/sys/vm/uma_core.c:958
#5  0xc07b5fc7 in uma_large_malloc (size=76681216, wait=2) at /usr/src/sys/vm/uma_core.c:2702
#6  0xc0643815 in malloc (size=76681216, mtp=0xc0952460, flags=2) at /usr/src/sys/kern/kern_malloc.c:329
#7  0xc07a0b66 in ffs_mountfs (devvp=0xc2776220, mp=0xc2319400, td=0xc233c600) at /usr/src/sys/ufs/ffs/ffs_vfsops.c:681
#8  0xc079fd29 in ffs_mount (mp=0xc2319400, td=0xc233c600) at /usr/src/sys/ufs/ffs/ffs_vfsops.c:358
#9  0xc069f550 in vfs_domount (td=0xc233c600, fstype=0xc2793990 "\002", fspath=0xc231dc80 "/mnt/test", fsflags=0,
                               fsdata=0xc231dbe0) at /usr/src/sys/kern/vfs_mount.c:882
#10 0xc069edae in vfs_donmount (td=0xc233c600, fsflags=0, fsoptions=0xd0a86c04) at /usr/src/sys/kern/vfs_mount.c:640
#11 0xc06a15bc in kernel_mount (ma=0xc231d6c0, flags=0) at pcpu.h:162
#12 0xc079fdc5 in ffs_cmount (ma=0xc231d6c0, data=0x0, flags=0, td=0xc233c600) at /usr/src/sys/ufs/ffs/ffs_vfsops.c:385
#13 0xc069ef92 in mount (td=0xc233c600, uap=0xd0a86d04) at /usr/src/sys/kern/vfs_mount.c:703
#14 0xc08420ab in syscall (frame=
      {tf_fs = 59, tf_es = 59, tf_ds = 59, tf_edi = 134523775, tf_esi = -1077941580, tf_ebp = -1077944168,
       tf_isp = -794268316, tf_ebx = -1077944112, tf_edx = 0, tf_ecx = 1, tf_eax = 21, tf_trapno = 12,
       tf_err = 2, tf_eip = 671845899, tf_cs = 51, tf_eflags = 582,
       tf_esp = -1077944324, tf_ss = 59}) at /usr/src/sys/i386/i386/trap.c:981
#15 0xc0830cef in Xint0x80_syscall () at /usr/src/sys/i386/i386/exception.s:200
#16 0x00000033 in ?? ()
Previous frame inner to this frame (corrupt stack?)
(kgdb) info registers
eax            0x0      0
ecx            0x0      0
edx            0x0      0
ebx            0xc232a080       -1036869504
esp            0xd0a86840       0xd0a86840
ebp            0xd0a86848       0xd0a86848
esi            0x0      0
edi            0x4      4
eip            0xc064da16       0xc064da16
eflags         0x0      0
cs             0x0      0
ss             0x0      0
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(kgdb) list 550
545     static int
546     ffs_mountfs(devvp, mp, td)
547             struct vnode *devvp;
548             struct mount *mp;
549             struct thread *td;
550     {
(...)
(kgdb)
557             int error, i, blks, size, ronly;    <------
(kgdb) list *0xc07a0b66
0xc07a0b66 is in ffs_mountfs (/usr/src/sys/ufs/ffs/ffs_vfsops.c:681).
676             size = fs->fs_cssize;                                   <--------- 2048
677             blks = howmany(size, fs->fs_fsize);                     <-- = 76681236 (fs->fs_fsize = 2048)
                                                                            ((size)+(fs->fs_fsize)-1/(fs->fs_fsize))
------------------------------ sys/ufs/ffs/fs.h
252 #define howmany(x, y)   (((x)+((y)-1))/(y))
------------------------------ sys/ufs/ffs/fs.h

678             if (fs->fs_contigsumsize > 0)
679                     size += fs->fs_ncg * sizeof(int32_t);           <-- fs->fs_ncg = 15335428, int32_t  = 4
680             size += fs->fs_ncg * sizeof(u_int8_t);                  <-- fs->fs_ncg = 15335428, u_int8_t = 1
681             space = malloc((u_long)size, M_UFSMNT, M_WAITOK);       <--------- to #6
682             fs->fs_csp = space;
683             for (i = 0; i < blks; i += fs->fs_frag) {
684                     size = fs->fs_bsize;
685                     if (i + fs->fs_frag > blks)
(kgdb) printf "%d %d\n", sizeof(u_int8_t), sizeof(int32_t)
1 4
(kgdb) list 589,611
589             bp = NULL;
590             ump = NULL;
591             fs = NULL;
592             sblockloc = 0;
593             /*
594              * Try reading the superblock in each of its possible locations.
595              */
596             for (i = 0; sblock_try[i] != -1; i++) {
597                     if ((error = bread(devvp, sblock_try[i] / DEV_BSIZE, SBLOCKSIZE,

------------------------------- sys/ufs/ffs/fs.h
 68 #define SBLOCK_FLOPPY        0
 69 #define SBLOCK_UFS1       8192
 70 #define SBLOCK_UFS2      65536
 71 #define SBLOCK_PIGGY    262144
 72 #define SBLOCKSIZE        8192
 73 #define SBLOCKSEARCH \
 74         { SBLOCK_UFS2, SBLOCK_UFS1, SBLOCK_FLOPPY, SBLOCK_PIGGY, -1 }
------------------------------- sys/ufs/ffs/fs.h

598                         cred, &bp)) != 0)
599                             goto out;
600                     fs = (struct fs *)bp->b_data;         <------------ fs assigned to bp
                                                                            (from bread(devpp at 0xc2776220))
601                     sblockloc = sblock_try[i];
602                     if ((fs->fs_magic == FS_UFS1_MAGIC ||
603                          (fs->fs_magic == FS_UFS2_MAGIC &&
604                           (fs->fs_sblockloc == sblockloc ||
605                            (fs->fs_old_flags & FS_FLAGS_UPDATED) == 0))) &&
606                         fs->fs_bsize <= MAXBSIZE &&        <-
607                         fs->fs_bsize >= sizeof(struct fs)) <-
608                             break;
609                     brelse(bp);
610                     bp = NULL;                             <-
611             }

------------------------------- sys/ufs/ffs/fs.h
360 /*
361  * Filesystem identification
362  */
363 #define FS_UFS1_MAGIC   0x011954        /* UFS1 fast filesystem magic number */
364 #define FS_UFS2_MAGIC   0x19540119      /* UFS2 fast filesystem magic number */
-------------------------------- sys/ufs/ffs/fs.h

(kgdb) x/6 0xc2776220 <---- ptr to devpp
0xc2776220:      "\004"
0xc2776222:      ""
0xc2776223:      ""
0xc2776224:      "+#\212� \025\221�\200\2076�"
0xc2776231:      "�0�"
0xc2776235:      ""
(kgdb) select-frame 7
(kgdb) info locals
ump = (struct ufsmount *) 0xc23ca400
bp = (struct buf *) 0x0
fs = (struct fs *) 0xc2470800
dev = (struct cdev *) 0xc237ad00
space = (void *) 0xc233c600
sblockloc = 65536
error = 0
i = 0
blks = 1
size = 76679188
ronly = 0
lp = (int32_t *) 0x0
cred = (struct ucred *) 0xc25c0680
cp = (struct g_consumer *) 0xc232b300
(kgdb) print fs->fs_volname
$22 = "AA�BCCDD\000\000\000\000\000\000\000\210", '\0' 
(kgdb) print fs->fs_magic
$24 = 424935705
(kgdb) printf "%p\n", 424935705
0x19540119                         <------ = FS_UFS2_MAGIC