MOKB-08-11-2006

Bug details
Title: FreeBSD 6.1 UFS filesystem ffs_rdextattr() 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, similar to MOKB-03-11-2006.

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_rdextattr() 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.

Author/Contributor:
References:
Proof of concept or exploit: The following UFS filesystem image can be used to reproduce the bug: MOKB-08-11-2006.img.bz2
Use a loopback device to mount it: bunzip2 MOKB-08-11-2006.img.bz2 && mdconfig -a -t vnode -f MOKB-08-11-2006.img -u 0 && mount /dev/md0 /mnt
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.

kgdb) shell 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
				

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 6 /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:
WARNING: : multilabel flag on fs but no MAC support
panic: kmem_malloc(-1912602624): kmem_map too small: 4636672 total allocated
Uptime: 1m57s
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) bt
#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=2382364672, flags=2) at /usr/src/sys/vm/vm_kern.c:299
#4  0xc07b3c6e in page_alloc (zone=0x0, bytes=-1912602624, pflag=0x0, wait=2) at /usr/src/sys/vm/uma_core.c:958
#5  0xc07b5fc7 in uma_large_malloc (size=-1912602624, wait=2) at /usr/src/sys/vm/uma_core.c:2702
#6  0xc0643815 in malloc (size=2382364672, mtp=0xc0916fc0, flags=2) at /usr/src/sys/kern/kern_malloc.c:329
#7  0xc07a43e6 in ffs_rdextattr (p=0x0, vp=0xc2598110, td=0x0, extra=0) at /usr/src/sys/ufs/ffs/ffs_vnops.c:1137
#8  0xc07a4487 in ffs_open_ea (vp=0x0, cred=0x0, td=0xc257f480) at /usr/src/sys/ufs/ffs/ffs_vnops.c:1170
#9  0xc07a48c2 in ffs_getextattr (ap=0xd135983c) at /usr/src/sys/ufs/ffs/ffs_vnops.c:1423
#10 0xc08539c5 in VOP_GETEXTATTR_APV (vop=0x0, a=0x0) at vnode_if.c:2431
#11 0xc06b1ef5 in vn_extattr_get (vp=0xc2598110, ioflg=8, attrnamespace=0, attrname=0x0, buflen=0xd13598ac, buf=0x0, td=0xc257f480)
at vnode_if.h:1282
#12 0xc07a4f87 in ufs_getacl (ap=0xd13598dc) at /usr/src/sys/ufs/ufs/ufs_acl.c:183
#13 0xc0853825 in VOP_GETACL_APV (vop=0x0, a=0x0) at vnode_if.c:2218
#14 0xc07abb22 in ufs_access (ap=0xd135993c) at vnode_if.h:1151
#15 0xc0852a44 in VOP_ACCESS_APV (vop=0x0, a=0x0) at vnode_if.c:480
#16 0xc0698cd6 in vfs_cache_lookup (ap=0x0) at vnode_if.h:256
#17 0xc085278b in VOP_LOOKUP_APV (vop=0xc0952520, a=0xd13599c8) at vnode_if.c:99
#18 0xc069d345 in lookup (ndp=0xd1359bcc) at vnode_if.h:56
#19 0xc069cbe6 in namei (ndp=0xd1359bcc) at /usr/src/sys/kern/vfs_lookup.c:203
#20 0xc06b033b in vn_open_cred (ndp=0xd1359bcc, flagp=0xd1359ccc, cmode=420, cred=0xc2506780, fdidx=3) at /usr/src/sys/kern/vfs_vnops.c:125
#21 0xc06b02de in vn_open (ndp=0x0, flagp=0xd1359ccc, cmode=420, fdidx=3) at /usr/src/sys/kern/vfs_vnops.c:91
#22 0xc06a8d22 in kern_open (td=0xc257f480, path=0x0, pathseg=UIO_USERSPACE, flags=1538, mode=438) at /usr/src/sys/kern/vfs_syscalls.c:1002
#23 0xc06a8c36 in open (td=0xc257f480, uap=0xd1359d04) at /usr/src/sys/kern/vfs_syscalls.c:968
#24 0xc08420ab in syscall (frame=
      {tf_fs = 59, tf_es = 59, tf_ds = 59, tf_edi = 0, tf_esi = 135282928, tf_ebp = -1077951192, tf_isp = -785015452,
tf_ebx = 1537, tf_edx = 135053297, tf_ecx = 135053307, tf_eax= 5, tf_trapno = 12, tf_err = 2, tf_eip = 674081515, tf_cs = 51,
tf_eflags = 582, tf_esp = -1077951556, tf_ss = 59}) at /usr/src/sys/i386/i386/trap.c:981
#25 0xc0830cef in Xint0x80_syscall () at /usr/src/sys/i386/i386/exception.s:200
#26 0x00000033 in ?? ()
Previous frame inner to this frame (corrupt stack?)
(kgdb) info registers
eax            0x0      0
ecx            0x0      0
edx            0x0      0
ebx            0xc2316480       -1036950400
esp            0xd13596a0       0xd13596a0
ebp            0xd13596a8       0xd13596a8
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 *0xc07a43e6
0xc07a43e6 is in ffs_rdextattr (/usr/src/sys/ufs/ffs/ffs_vnops.c:1137).
1132
1133            ip = VTOI(vp);
1134            dp = ip->i_din2;
1135            easize = dp->di_extsize;
1136
1137            eae = malloc(easize + extra, M_TEMP, M_WAITOK);
1138
1139            liovec.iov_base = eae;
1140            liovec.iov_len = easize;
1141            luio.uio_iov = &liovec;
(kgdb) select-frame 7
(kgdb) info locals
ip = (struct inode *) 0x0
dp = (struct ufs2_dinode *) 0x0
luio = {uio_iov = 0xc12f61c8, uio_iovcnt = -1063583904, uio_offset = -3371621792048218112,
uio_resid = -1065101227, uio_segflg = 3231383392, uio_rw = 3260669952,
  uio_td = 0xc104e640}
liovec = {iov_base = 0xc098ec40, iov_len = 3238265580}
easize = -1912602624
error = -1912602624
eae = (u_char *) 0xc241ddec ""

(kgdb) list *0xc07a4487 <---- #8
0xc07a4487 is in ffs_open_ea (/usr/src/sys/ufs/ffs/ffs_vnops.c:1171).
1166
1167            if (ip->i_ea_area != NULL)
1168                    return (EBUSY);
1169            dp = ip->i_din2;
1170            error = ffs_rdextattr(&ip->i_ea_area, vp, td, 0); <--- to #7
1171            if (error)
1172                    return (error);
1173            ip->i_ea_len = dp->di_extsize;
1174            ip->i_ea_error = 0;
1175            return (0);
(kgdb) select-frame 8
(kgdb) info locals
ip = (struct inode *) 0xc241ddec
dp = (struct ufs2_dinode *) 0xc259c100
error = 0

------------- ufs/ufs/inode.h
 50 /*
 51  * The inode is used to describe each active (or recently active) file in the
 52  * UFS filesystem. It is composed of two types of information. The first part
 53  * is the information that is needed only while the file is active (such as
 54  * the identity of the file and linkage to speed its lookup). The second part
 55  * is the permanent meta-data associated with the file which is read in
 56  * from the permanent dinode from long term storage when the file becomes
 57  * active, and is put back when the file is no longer being used.
 58  */
 59 struct inode {
 60         TAILQ_ENTRY(inode) i_nextsnap; /* snapshot file list. */
 61         struct  vnode  *i_vnode;/* Vnode associated with this inode. */
 62         struct  ufsmount *i_ump;/* Ufsmount point associated with this inode. */
 63         u_int32_t i_flag;       /* flags, see below */
 64         struct cdev *i_dev;     /* Device associated with the inode. */
 65         ino_t     i_number;     /* The identity of the inode. */
 66         int       i_effnlink;   /* i_nlink when I/O completes */
 67 
 68         struct   fs *i_fs;      /* Associated filesystem superblock. */
 69         struct   dquot *i_dquot[MAXQUOTAS]; /* Dquot structures. */
 70         u_quad_t i_modrev;      /* Revision level for NFS lease. */
 71         struct   lockf *i_lockf;/* Head of byte-level lock list. */
(...)
111 };
--------- ufs/ufs/inode.h

(kgdb) frame
#8  0xc07a4487 in ffs_open_ea (vp=0x0, cred=0x0, td=0xc257f480) at /usr/src/sys/ufs/ffs/ffs_vnops.c:1170
1170            error = ffs_rdextattr(&ip->i_ea_area, vp, td, 0);
(kgdb) list 1123,1176
1123    static int
1124    ffs_rdextattr(u_char **p, struct vnode *vp, struct thread *td, int extra)
1125    {
1126            struct inode *ip;
1127            struct ufs2_dinode *dp;
1128            struct uio luio;
1129            struct iovec liovec;
1130            int easize, error;
1131            u_char *eae;
1132
1133            ip = VTOI(vp);
1134            dp = ip->i_din2;
1135            easize = dp->di_extsize;
1136
1137            eae = malloc(easize + extra, M_TEMP, M_WAITOK);
1138
1139            liovec.iov_base = eae;
1140            liovec.iov_len = easize;
1141            luio.uio_iov = &liovec;
1142            luio.uio_iovcnt = 1;
1143            luio.uio_offset = 0;
1144            luio.uio_resid = easize;
1145            luio.uio_segflg = UIO_SYSSPACE;
1146            luio.uio_rw = UIO_READ;
1147            luio.uio_td = td;
1148
1149            error = ffs_extread(vp, &luio, IO_EXT | IO_SYNC);
1150            if (error) {
1151                    free(eae, M_TEMP);
1152                    return(error);
1153            }
1154            *p = eae;
1155            return (0);
1156    }
1157
1158    static int
1159    ffs_open_ea(struct vnode *vp, struct ucred *cred, struct thread *td)
---Type  to continue, or q  to quit---
1160    {
1161            struct inode *ip;
1162            struct ufs2_dinode *dp;
1163            int error;
1164
1165            ip = VTOI(vp);
1166
1167            if (ip->i_ea_area != NULL)
1168                    return (EBUSY);
1169            dp = ip->i_din2;
1170            error = ffs_rdextattr(&ip->i_ea_area, vp, td, 0); <--- to #7
1171            if (error)
1172                    return (error);
1173            ip->i_ea_len = dp->di_extsize;
1174            ip->i_ea_error = 0;
1175            return (0);
1176    }
(kgdb) printf "%p\n",ip->i_fs->fs_magic
0x19540119                                <------ = FS_UFS2_MAGIC
(kgdb) p dp
$43 = (struct ufs2_dinode *) 0xc259c100
(kgdb) p dp->di_extsize
$44 = -1912602624
(kgdb) p ip->i_fs
$51 = (struct fs *) 0xc2325000
(kgdb) list 1390,1440
1390     */
1391    static int
1392    ffs_getextattr(struct vop_getextattr_args *ap)
1393    /*
1394    vop_getextattr {
1395            IN struct vnode *a_vp;
1396            IN int a_attrnamespace;
1397            IN const char *a_name;
1398            INOUT struct uio *a_uio;
1399            OUT size_t *a_size;
1400            IN struct ucred *a_cred;
1401            IN struct thread *a_td;
1402    };
1403    */
1404    {
1405            struct inode *ip;
1406            struct fs *fs;
1407            u_char *eae, *p;
1408            unsigned easize;
1409            int error, ealen, stand_alone;
1410
1411            ip = VTOI(ap->a_vp);
1412            fs = ip->i_fs;
1413
1414            if (ap->a_vp->v_type == VCHR)
1415                    return (EOPNOTSUPP);
1416
1417            error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
1418                ap->a_cred, ap->a_td, IREAD);
1419            if (error)
1420                    return (error);
1421
1422            if (ip->i_ea_area == NULL) {
1423                    error = ffs_open_ea(ap->a_vp, ap->a_cred, ap->a_td); <------------- to #8
1424                    if (error)
1425                            return (error);
1426                    stand_alone = 1;
1427            } else {
1428                    stand_alone = 0;
1429            }
1430            eae = ip->i_ea_area;
1431            easize = ip->i_ea_len;
1432
1433            ealen = ffs_findextattr(eae, easize, ap->a_attrnamespace, ap->a_name,
1434                NULL, &p);
1435            if (ealen >= 0) {
1436                    error = 0;
1437                    if (ap->a_size != NULL)
1438                            *ap->a_size = ealen;
1439                    else if (ap->a_uio != NULL)
1440                            error = uiomove(p, ealen, ap->a_uio);
(kgdb) info locals
ip = (struct inode *) 0xc241ddec
p = (u_char *) 0x0
error = -1034321648
ealen = 0
stand_alone = 0
(kgdb) p ip->dinode_u->din2->di_extsize
$72 = -1912602624

-------------------------------- ufs/ufs/dinode.h
125 struct ufs2_dinode {
126         u_int16_t       di_mode;        /*   0: IFMT, permissions; see below. */
127         int16_t         di_nlink;       /*   2: File link count. */
128         u_int32_t       di_uid;         /*   4: File owner. */
129         u_int32_t       di_gid;         /*   8: File group. */
130         u_int32_t       di_blksize;     /*  12: Inode blocksize. */
131         u_int64_t       di_size;        /*  16: File byte count. */
132         u_int64_t       di_blocks;      /*  24: Bytes actually held. */
(...)
144         int32_t         di_extsize;     /*  92: External attributes block. */
(...)
-------------------------------- ufs/ufs/dinode.h

(kgdb) select-frame 12
(kgdb) frame
#12 0xc07a4f87 in ufs_getacl (ap=0xd13598dc) at /usr/src/sys/ufs/ufs/ufs_acl.c:183
183                     error = vn_extattr_get(ap->a_vp, IO_NODELOCKED,
(kgdb) list
178                      * EA, as they are in fact a combination of the inode
179                      * ownership/permissions and the EA contents.  If the
180                      * EA is present, merge the two in a temporary ACL
181                      * storage, otherwise just return the inode contents.
182                      */
183                     error = vn_extattr_get(ap->a_vp, IO_NODELOCKED,
184                         POSIX1E_ACL_ACCESS_EXTATTRNAESPACE,
185                         POSIX1E_ACL_ACCESS_EXTATTRNAE, &len, (char *) ap->a_aclp,
186                         ap->a_td);
187                     switch (error) {
(kgdb) list -
168
169             /*
170              * Attempt to retrieve the ACL based on the ACL type.
171              */
172             bzero(ap->a_aclp, sizeof(*ap->a_aclp));
173             len = sizeof(*ap->a_aclp);
174             switch(ap->a_type) {
175             case ACL_TYPE_ACCESS:
176                     /*
177                      * ACL_TYPE_ACCESS ACLs may or may not be stored in the
(kgdb) list -
158     {
159             struct inode *ip = VTOI(ap->a_vp);
160             int error, len;
161
162             /*
163              * XXX: If ufs_getacl() should work on file systems not supporting
164              * ACLs, remove this check.
165              */
166             if ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0)
167                     return (EOPNOTSUPP);
(kgdb) p ip->dinode_u->din2->di_extsize
$76 = -1912602624
(kgdb) select-frame 19
(kgdb) info locals
fdp = (struct filedesc *) 0xc24eec00
cp = 0xc24eec00 ""
dp = (struct vnode *) 0xc233fbb0                                                <---------
aiov = {iov_base = 0xfffebff4, iov_len = 0}
auio = {uio_iov = 0x0, uio_iovcnt = -785016152, uio_offset = -4577691262870250712, uio_resid = -1036395872,
uio_segflg = 4294885364, uio_rw = 4294967295, uio_td = 0x4000}
error = -1036780624                                                             <---------
linklen = -1036780624                                                           <--------- 
cnp = (struct componentname *) 0xd1359bf4
td = (struct thread *) 0xc257f480
p = (struct proc *) 0x0
vfslocked = 0
(kgdb) frame
#19 0xc069cbe6 in namei (ndp=0xd1359bcc) at /usr/src/sys/kern/vfs_lookup.c:203
203                     error = lookup(ndp);
(kgdb) list
198                             VREF(dp);
199                     }
200                     if (vfslocked)
201                             ndp->ni_cnd.cn_flags |= GIANTHELD;
202                     ndp->ni_startdir = dp;
203                     error = lookup(ndp);
204                     if (error) {
205                             uma_zfree(namei_zone, cnp->cn_pnbuf);
206     #ifdef DIAGNOSTIC
207                             cnp->cn_pnbuf = NULL;
(kgdb) ptype linklen
type = int

------------ kern/vfs_lookup.c
203                 error = lookup(ndp);
204                 if (error) {
205                         uma_zfree(namei_zone, cnp->cn_pnbuf);
206 #ifdef DIAGNOSTIC
207                         cnp->cn_pnbuf = NULL;
208                         cnp->cn_nameptr = NULL;
209 #endif
210                         return (error);
211                 }
------------- kern/vfs_lookup.c