kern/63375: Unplugging USB devices panics RELENG_5_2 via devfs vnops

Toni Andjelkovic toni at soth.at
Fri Feb 27 09:40:37 PST 2004


The following reply was made to PR kern/63375; it has been noted by GNATS.

From: Toni Andjelkovic <toni at soth.at>
To: FreeBSD-gnats-submit at FreeBSD.org
Cc:  
Subject: 	kern/63375: Unplugging USB devices panics RELENG_5_2 via devfs vnops
Date: Thu, 26 Feb 2004 03:11:52 +0100 (CET)

 >Number:         63375
 >Category:       kern
 >Synopsis:       Unplugging USB devices panics RELENG_5_2 via devfs vnops
 >Confidential:   no
 >Severity:       critical
 >Priority:       high
 >Responsible:    freebsd-bugs
 >State:          open
 >Quarter:        
 >Keywords:       
 >Date-Required:
 >Class:          sw-bug
 >Submitter-Id:   current-users
 >Arrival-Date:   Wed Feb 25 18:20:14 PST 2004
 >Closed-Date:
 >Last-Modified:
 >Originator:     Toni Andjelkovic
 >Release:        FreeBSD 5.2.1-RELEASE i386
 >Organization:
 >Environment:
 System: FreeBSD shaft.soth.at 5.2.1-RELEASE FreeBSD 5.2.1-RELEASE #0: Thu Feb 26 01:18:13 CET 2004 toni at kiste.soth.at:/usr/src/sys/i386/compile/GENERIC i386
 
 >Description:
 
 Several devfs vnode functions (devfs_getattr, devfs_setattr, devfs_access,
 possibly others) may cause panics by dereferencing NULL pointers produced
 by devfs_populate.
 
 >How-To-Repeat:
 
 1.      Plug in a USB device. I tested with ugen (smartcard reader,
         webcam) and ums (mouse).
 
 2.      Run the following script:
 
         #!/usr/local/bin/python
         import os, time
         fd = os.open("/dev/ugen0", os.O_RDONLY)
         while 1:
                 print os.fstat(fd)
                 time.sleep(1)
 
 3.      Unplug the USB device.
 
 4.      From another window, enter "ls `tty`" or do anything else that
         invokes devfs_lookup or devfs_readdir - log in with ssh, ...
 
 5.      Fatal trap 12: page fault while in kernel mode.
         in <devfs_getattr+0x48>
 
 >Fix:
 
 Maybe the devfs vnop functions should return ENXIO if ap->a_vp->v_data
 is NULL. The error would be returned to the user process, where it can
 be handled appropriately.
 
 When the user process closes the descriptor for ugen0, vp->v_usecount
 becomes 0 and the vnode goes to the vnode_free_list via devfs_reclaim.
 
 Index: fs/devfs/devfs_vnops.c
 ===================================================================
 RCS file: /home/ncvs/src/sys/fs/devfs/devfs_vnops.c,v
 retrieving revision 1.67
 diff -u -r1.67 devfs_vnops.c
 --- fs/devfs/devfs_vnops.c	20 Oct 2003 15:08:10 -0000	1.67
 +++ fs/devfs/devfs_vnops.c	25 Feb 2004 23:43:42 -0000
 @@ -183,6 +183,8 @@
  	int error;
  
  	de = vp->v_data;
 +	if (!de)
 +		return (ENXIO);
  	if (vp->v_type == VDIR)
  		de = de->de_dir;
  
 @@ -216,6 +218,8 @@
  	dev_t dev;
  
  	de = vp->v_data;
 +	if (!de)
 +		return (ENXIO);
  	if (vp->v_type == VDIR)
  		de = de->de_dir;
  	bzero((caddr_t) vap, sizeof(*vap));
 @@ -747,6 +751,8 @@
  	}
  
  	de = vp->v_data;
 +	if (!de)
 +		return (ENXIO);
  	if (vp->v_type == VDIR)
  		de = de->de_dir;
  
 
 
 Analysis:
 
 When a USB device is removed, the corresponding USB_DETACH driver
 function is called. This function calls destroy_dev, which calls
 devfs_destroy, which nulls out the device's dev_t in the array
 devfs_inot:
 
 # ---
 
 (gdb) break devfs_destroy
 Breakpoint 1 at 0xc05effff: file ../../../fs/devfs/devfs_devs.c, line 434.
 (gdb) c
 Continuing.
 # unplug the USB device
 Breakpoint 1, devfs_destroy (dev=0xc2083600)
     at ../../../fs/devfs/devfs_devs.c:434
 434             ino = dev->si_inode;
 (gdb) bt
 #0  devfs_destroy (dev=0xc2083600) at ../../../fs/devfs/devfs_devs.c:434
 #1  0xc061ba0a in destroy_dev (dev=0xc2083600) at
 ./../../kern/kern_conf.c:464
 #2  0xc05cea4f in ugen_detach (self=0xc2083600) at ../../../dev/usb/ugen.c:908
 #3  0xc0655df6 in device_detach (dev=0xc20f8f00) at device_if.h:48
 #4  0xc0655145 in device_delete_child (dev=0xc2083600, child=0xc20f8f00)
     at ../../../kern/subr_bus.c:1037
 #5  0xc05df644 in usb_disconnect_port (up=0xc20473b0, parent=0xc2083600)
     at ../../../dev/usb/usb_subr.c:1374
 #6  0xc05d4cb3 in uhub_explore (dev=0xc2047500) at ../../../dev/usb/uhub.c:428
 #7  0xc05dce45 in usb_discover (v=0xc2019700) at ../../../dev/usb/usb.c:716
 #8  0xc05dc848 in usb_event_thread (arg=0xc2019700)
     at ../../../dev/usb/usb.c:409
 #9  0xc0628a84 in fork_exit (callout=0xc05dc7e0 <usb_event_thread>,
     arg=0xc2083600, frame=0xc2083600) at ../../../kern/kern_fork.c:793
 (gdb) p dev->si_name
 $1 = 0xc2083690 "ugen0"
 (gdb) p dev->si_usecount
 $2 = 1
 (gdb) p devfs_inot[dev->si_inode]
 $3 = 0xc2083600
 (gdb) p dev->si_inode
 $4 = 91
 (gdb) break ../../../fs/devfs/devfs_devs.c:442
 Breakpoint 2 at 0xc05f0049: file ../../../fs/devfs/devfs_devs.c, line 442.
 (gdb) c
 Continuing.
 
 Breakpoint 2, devfs_destroy (dev=0xc2083600)
     at ../../../fs/devfs/devfs_devs.c:442
 442                     if (ino < i)
 (gdb) p devfs_inot[dev->si_inode]
 $5 = 0x0
 (gdb) del br 2
 (gdb) c
 Continuing.
 
 Breakpoint 1, devfs_destroy (dev=0xc20d3300)
     at ../../../fs/devfs/devfs_devs.c:434
 434             ino = dev->si_inode;
 (gdb) p dev->si_name
 $6 = 0xc20d3390 "ugen0.2"
 (gdb) p dev->si_usecount
 $7 = 0
 (gdb) p dev->si_inode
 $8 = 88
 (gdb) c
 Continuing.
 
 Breakpoint 1, devfs_destroy (dev=0xc20d3100)
     at ../../../fs/devfs/devfs_devs.c:434
 434             ino = dev->si_inode;
 (gdb) p dev->si_name
 $9 = 0xc20d3190 "ugen0.4"
 (gdb) p dev->si_usecount
 $10 = 0
 (gdb) p dev->si_inode
 $11 = 89
 
 # ---
 
 In the trace above, devfs_destroy is being called three times, once
 for each devfs file. ugen devices usually create several devfs files -
 one for the control endpoint (ugen0) and one for each device endpoint
 (ugen0.2 and ugen0.4). All of these are destroyed on detach of the driver.
 
 Now that the devices are destroyed, the corresponding dev_t entries
 in devfs_inot contain NULL. This is an important detail because
 devfs_populate makes a decision based on it:
 
 
 
 
 # ---
 
 (gdb) del br 1
 (gdb) br ../../../fs/devfs/devfs_devs.c:314
 Breakpoint 3 at 0xc05efc82: file ../../../fs/devfs/devfs_devs.c, line 314.
 (gdb) c
 Continuing.
 # An unrelated stat() call on /dev/ad0s1a
 Breakpoint 3, devfs_populate (dm=0xc20ae680)
     at ../../../fs/devfs/devfs_devs.c:314
 314                                             de->de_vnode->v_data = NULL;
 (gdb) bt
 #0  devfs_populate (dm=0xc20ae680) at ../../../fs/devfs/devfs_devs.c:314
 #1  0xc05f1b1e in devfs_lookupx (ap=0xc20fd180)
     at ../../../fs/devfs/devfs_vnops.c:356
 #2  0xc05f1feb in devfs_lookup (ap=0xc95adb90)
     at ../../../fs/devfs/devfs_vnops.c:450
 #3  0xc0692632 in lookup (ndp=0xc95adc28) at vnode_if.h:52
 #4  0xc069201e in namei (ndp=0xc95adc28) at ../../../kern/vfs_lookup.c:183
 #5  0xc069f8d2 in stat (td=0xc2029c80, uap=0xc95add14)
     at ../../../kern/vfs_syscalls.c:1994
 #6  0xc07f8cd0 in syscall (frame=
       {tf_fs = 47, tf_es = 47, tf_ds = 47, tf_edi = 672328506, tf_esi =
 672398144, tf_ebp = -1077942936, tf_isp = -916791948, tf_ebx = 672383264,
 tf_edx = -1077943232, tf_ecx = 672398152, tf_eax = 188, tf_trapno = 12, tf_err
 = 2, tf_eip = 671876239, tf_cs = 31, tf_eflags = 582, tf_esp = -1077943556,
 tf_ss = 47})
     at ../../../i386/i386/trap.c:1010
 #7  0xc07e3d3d in Xint0x80_syscall () at {standard input}:136
 #8  0x280bb332 in ?? ()
 #9  0x280bb61b in ?? ()
 #10 0x0804a565 in ?? ()
 #11 0x08049085 in ?? ()
 #12 0x08048e22 in ?? ()
 (gdb) p 0xc95add14
 $12 = 3378175252
 (gdb) pt 0xc95add14
 type = unsigned int
 (gdb) frame 5
 #5  0xc069f8d2 in stat (td=0xc2029c80, uap=0xc95add14)
     at ../../../kern/vfs_syscalls.c:1994
 1994            if ((error = namei(&nd)) != 0)
 (gdb) p *uap
 $13 = {path_l_ = 0xc95add14 "@û\023(ðä¿¿\003",
   path = 0x2813fb40 "/dev/ad0s1a", path_r_ = 0xc95add18 "ðä¿¿\003",
   ub_l_ = 0xc95add18 "ðä¿¿\003", ub = 0xbfbfe4f0, ub_r_ = 0xc95add1c "\003"}
 (gdb) frame 0
 #0  devfs_populate (dm=0xc20ae680) at ../../../fs/devfs/devfs_devs.c:314
 314                                             de->de_vnode->v_data = NULL;
 (gdb) list *$eip
 0xc05efc82 is in devfs_populate (../../../fs/devfs/devfs_devs.c:314).
 309                             if (dev == NULL && de != NULL) {
 310                                     dd = de->de_dir;
 311                                     *dep = NULL;
 312                                     TAILQ_REMOVE(&dd->de_dlist, de,
 de_list);
 313                                     if (de->de_vnode)
 314                                             de->de_vnode->v_data = NULL;
 315                                     FREE(de, M_DEVFS);
 316                                     devfs_dropref(i);
 317                                     continue;
 318                             }
 (gdb) p *de->de_vnode->v_un->vu_spec->vu_cdev
 $16 = {si_flags = 0, si_atime = {tv_sec = 1077744672, tv_nsec = 132892000},
   si_ctime = {tv_sec = 1077744672, tv_nsec = 132892000}, si_mtime = {
     tv_sec = 1077744672, tv_nsec = 132892000}, si_udev = 29184, si_hash = {
     le_next = 0x0, le_prev = 0xc091c72c}, si_hlist = {slh_first = 0xc2258d34},
   si_children = {lh_first = 0x0}, si_siblings = {le_next = 0x0,
     le_prev = 0x0}, si_parent = 0x0, si_inode = 0,
   si_name = 0xc2083690 "ugen0", si_drv1 = 0x0, si_drv2 = 0x0, si_devsw = 0x0,
   si_iosize_max = 65536, si_stripesize = 0, si_stripeoffset = 0, si_uid = 0,
   si_gid = 5, si_mode = 420, si_usecount = 1, __si_u = {__si_tty = {
       __sit_tty = 0x0}, __si_disk = {__sid_mountpoint = 0x0,
       __sid_bsize_phys = 0, __sid_bsize_best = 0, __sid_snapshots = {
         tqh_first = 0x0, tqh_last = 0x0}, __sid_snaplistsize = 0,
       __sid_snapblklist = 0x0, __sid_copyonwrite = 0}},
   __si_namebuf = "ugen0", '\0' <repeats 58 times>}
 (gdb) p de->de_vnode->v_usecount
 $17 = 1
 (gdb) p de->de_vnode
 $18 = (struct vnode *) 0xc2258d34
 (gdb) p de->de_vnode->v_data
 $19 = (void *) 0xc20f9100
 (gdb) p &de->de_vnode->v_data
 $20 = (void **) 0xc2258ddc
 
 # ---
 
 In the dump above, devfs_populate gets called due to an unrelated
 lookup in devfs (here: "/dev/ad0s1a"). devfs_populate iterates over
 all entries in devfs_inot and checks if the dev_t is NULL. If yes,
 the corresponding devfs_dirent * is also cleared (devfs_devs.c:311).
 
 However, in devfs_devs.c:314, the v_data member of the dirent vnode
 gets overwritten with NULL, although the vnode is in use by the python
 process, which continuously calls fstat() on the file descriptor.
 
 This is followed by a panic in the next step:
 
 
 
 
 
 
 
 
 
 # ---
 
 (gdb) c
 Continuing.
 
 Program received signal SIGSEGV, Segmentation fault.
 devfs_getattr (ap=0xc95b0ba8) at ../../../fs/devfs/devfs_vnops.c:223
 223             vap->va_uid = de->de_uid;
 (gdb) bt
 #0  devfs_getattr (ap=0xc95b0ba8) at ../../../fs/devfs/devfs_vnops.c:223
 #1  0xc06a5204 in vn_stat (vp=0xc2258d34, sb=0xc95b0c78,
     active_cred=0xc95b0ba8, file_cred=0xc21dbc00, td=0xc2029dc0)
     at vnode_if.h:330
 #2  0xc06a516d in vn_statfile (fp=0xc20f7cc0, sb=0xc95b0ba8,
     active_cred=0xc95b0ba8, td=0xc2029dc0) at ../../../kern/vfs_vnops.c:616
 #3  0xc061dd19 in fstat (td=0xc2029dc0, uap=0xc20f7cc0)
     at ../../../sys/file.h:283
 #4  0xc07f8cd0 in syscall (frame=
       {tf_fs = 47, tf_es = 47, tf_ds = 47, tf_edi = 5, tf_esi = 0, tf_ebp =
 -1077942216, tf_isp = -916779660, tf_ebx = 672356020, tf_edx = 5, tf_ecx =
 -1077942236, tf_eax = 189, tf_trapno = 22, tf_err = 2, tf_eip = 673621103,
 tf_cs = 31, tf_eflags = 582, tf_esp = -1077942260, tf_ss = 47})
     at ../../../i386/i386/trap.c:1010
 #5  0xc07e3d3d in Xint0x80_syscall () at {standard input}:136
 #6  0x080c5d2e in ?? ()
 #7  0x080d9d72 in ?? ()
 #8  0x080a1345 in ?? ()
 #9  0x0809f31f in ?? ()
 #10 0x080a028f in ?? ()
 #11 0x0809ccd7 in ?? ()
 #12 0x080bc16e in ?? ()
 #13 0x080bc116 in ?? ()
 ---Type <return> to continue, or q <return> to quit---
 #14 0x080bc0ce in ?? ()
 #15 0x080bb238 in ?? ()
 #16 0x080babdb in ?? ()
 #17 0x08054841 in ?? ()
 #18 0x080540e9 in ?? ()
 #19 0x08053fe2 in ?? ()
 (gdb) list *$eip
 0xc05f16b8 is in devfs_getattr (../../../fs/devfs/devfs_vnops.c:223).
 218             de = vp->v_data;
 219             if (vp->v_type == VDIR)
 220                     de = de->de_dir;
 221             bzero((caddr_t) vap, sizeof(*vap));
 222             vattr_null(vap);
 223             vap->va_uid = de->de_uid;
 224             vap->va_gid = de->de_gid;
 225             vap->va_mode = de->de_mode;
 226             if (vp->v_type == VLNK)
 227                     vap->va_size = de->de_dirent->d_namlen;
 (gdb) x/i $eip
 0xc05f16b8 <devfs_getattr+72>:  mov    0x28(%edi),%eax
 (gdb) info reg
 eax            0xc95b0ba8       -916780120
 ecx            0x0      0
 edx            0xc206a600       -1039751680
 ebx            0xc95b0ba8       -916780120
 esp            0xc95b0b64       0xc95b0b64
 ebp            0xc95b0b78       0xc95b0b78
 esi            0xc2258d34       -1037726412
 edi            0x0      0
 eip            0xc05f16b8       0xc05f16b8
 eflags         0x10246  66118
 cs             0x8      8
 ss             0x10     16
 ds             0xc0850010       -1065025520
 es             0xc0660010       -1067057136
 fs             0x0      0
 gs             0x0      0
 fctrl          0x0      0
 fstat          0x0      0
 ftag           0x0      0
 fiseg          0x0      0
 fioff          0x0      0
 foseg          0x0      0
 fooff          0x0      0
 ---Type <return> to continue, or q <return> to quit---
 fop            0x0      0
 (gdb) p vp
 $28 = (struct vnode *) 0xc2258d34
 (gdb) p vp->v_data
 $29 = (void *) 0x0
 (gdb) p &vp->v_data
 $30 = (void **) 0xc2258ddc
 (gdb) p vp->v_usecount
 $31 = 1
 (gdb) p ((struct proc *) 0xc21af000)->p_fd->fd_ofiles[5]->f_vnode
 $32 = (struct vnode *) 0xc2258d34
 
 # ---
 
 Since the v_data member of the devfs dirent vnode has been overwritten
 in the previous step, it contains NULL. However, devfs_getattr()
 assumes that this pointer is always valid and tries to dereference it,
 which results in a page fault.
 
 Several devfs vnop functions are affected by this bug, at least
 devfs_getattr, devfs_setattr and devfs_access.
 
 >Release-Note:
 >Audit-Trail:
 >Unformatted:
 _______________________________________________
 freebsd-bugs at freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/freebsd-bugs
 To unsubscribe, send any mail to "freebsd-bugs-unsubscribe at freebsd.org"


More information about the freebsd-bugs mailing list