From: Dawson Engler <engler@csl.Stanford.EDU> To: linux-kernel@vger.kernel.org Subject: [CHECKER] 15 probable security holes in 2.4.5-ac8 Date: Fri, 8 Jun 2001 14:34:01 -0700 (PDT) Cc: mc@cs.Stanford.EDU Hi All, here are 15 probable security holes where user input (e.g. data from copy_from_user, get_user, etc) is: 1. passed as a length argument to copy_*user (or passed to a function that does so), OR 2. is used as an array index. The main difference between this and past checkers is that we do "inherited" tainting: if a structure is copied in from user space, we recursively mark all of it's fields as tainted as well. I'm still fussing with the extension, so hopefully find a bunch more errors after this batch. You can look at this checker as essentially tracking whether the information from an untrusted source (e.g., from copy_from_user) can reach a trusting sink (e.g., an array index). The main limiting factor on its effectiveness is that we have a very incomplete notion of trusting sinks. If anyone has suggestions for other general places where it's dangerous to consume bad data, please send me an email. Here's the location summary: # Total = 15 # BUGs | File Name 3 | drivers/char/drm/mga_state.c 2 | drivers/scsi/megaraid.c 2 | drivers/char/drm/i810_dma.c 2 | drivers/char/raw.c 1 | drivers/usb/usbvideo.c 1 | drivers/usb/se401.c 1 | drivers/net/hamradio/scc.c 1 | drivers/char/moxa.c 1 | drivers/media/video/zr36120.c 1 | drivers/media/video/zr36067.c Dawson --------------------------------------------------------- [BUG] dataxferlen is never checked. /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/scsi/megaraid.c:4108:megadev_ioctl: ERROR:RANGE:3825:4108: Using user length "dataxferlen" as argument to "copy_from_user" [type=LOCAL] [state = any_check] set by 'copy_from_user':3825 [val=28300] ret = verify_area (VERIFY_WRITE, (char *) arg, sizeof (struct uioctl_t)); if (ret) return ret; Start ---> if(copy_from_user (&ioc, (char *) arg, sizeof (struct uioctl_t))) ... DELETED 277 lines ... if (inlen) { if (ioc.mbox[0] == MEGA_MBOXCMD_PASSTHRU) { /* copyin the user data */ copy_from_user (kphysaddr, uaddr, Error ---> ioc.pthru.dataxferlen); } else { copy_from_user (kphysaddr, uaddr, inlen); --------------------------------------------------------- [BUG] symetry. /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/scsi/megaraid.c:4167:megadev_ioctl: ERROR:RANGE:3825:4167: Using user length "dataxferlen" as argument to "copy_to_user" [type=LOCAL] [state = any_check] set by 'copy_from_user':3825 [val=34200] ret = verify_area (VERIFY_WRITE, (char *) arg, sizeof (struct uioctl_t)); if (ret) return ret; Start ---> if(copy_from_user (&ioc, (char *) arg, sizeof (struct uioctl_t))) ... DELETED 336 lines ... down (&mimd_ioctl_sem); if (!scsicmd->result && outlen) { if (ioc.mbox[0] == MEGA_MBOXCMD_PASSTHRU) { copy_to_user (uaddr, Error ---> kphysaddr, ioc.pthru.dataxferlen); } else { copy_to_user (uaddr, kphysaddr, outlen); } --------------------------------------------------------- [BUG] static int moxaload320b(int cardno, unsigned char * tmp, int len) { unsigned long baseAddr; int i; if(copy_from_user(moxaBuff, tmp, len)) return -EFAULT; /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/char/moxa.c:1730:MoxaDriverIoctl: ERROR:RANGE:1728:1730: Using user length "len" as argument to "moxaload320b" [type=GLOBAL] [state = tainted] set by 'copy_from_user':1728 [val=200] case MOXA_FIND_BOARD: if(copy_from_user(&dltmp, (void *)arg, sizeof(struct dl_str))) return -EFAULT; return moxafindcard(dltmp.cardno); case MOXA_LOAD_C320B: Start ---> if(copy_from_user(&dltmp, (void *)arg, sizeof(struct dl_str))) return -EFAULT; Error ---> moxaload320b(dltmp.cardno, dltmp.buf, dltmp.len); return (0); case MOXA_LOAD_CODE: if(copy_from_user(&dltmp, (void *)arg, sizeof(struct dl_str))) --------------------------------------------------------- [BUG] d.idx is an int: < 0 = bad news. /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/char/drm/i810_dma.c:1413:i810_copybuf: ERROR:RANGE:1409:1413: Using user length "idx"as an array index for "buflist" set by 'copy_from_user':1409 [val=400] if(!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) { DRM_ERROR("i810_dma called without lock held\n"); return -EINVAL; } Start ---> if (copy_from_user(&d, (drm_i810_copy_t *)arg, sizeof(d))) return -EFAULT; if(d.idx > dma->buf_count) return -EINVAL; Error ---> buf = dma->buflist[ d.idx ]; buf_priv = buf->dev_private; if (buf_priv->currently_mapped != I810_BUF_MAPPED) return -EPERM; --------------------------------------------------------- [BUG] ouch. (routine also uses it as an index) /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/usb/se401.c:1290:se401_ioctl: ERROR:RANGE:1286:1290: Using user length "frame"as an array index for "frame" set by 'copy_from_user':1286 [val=400] } case VIDIOCSYNC: { int frame, ret=0; Start ---> if (copy_from_user((void *)&frame, arg, sizeof(int))) return -EFAULT; ret=se401_newframe(se401, frame); Error ---> se401->frame[frame].grabstate=FRAME_UNUSED; return ret; } case VIDIOCGFBUF: --------------------------------------------------------- [BUG] ouch x 2: no check either way. /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/char/drm/mga_state.c:835:mga_iload: ERROR:RANGE:827:835: Using user length "idx"as an array index for "buflist" set by 'copy_from_user':827 [val=800] drm_buf_t *buf; drm_mga_buf_priv_t *buf_priv; drm_mga_iload_t iload; unsigned long bus_address; Start ---> if (copy_from_user(&iload, (drm_mga_iload_t *) arg, sizeof(iload))) return -EFAULT; if (!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) { DRM_ERROR("mga_iload called without lock held\n"); return -EINVAL; } Error ---> buf = dma->buflist[iload.idx]; buf_priv = buf->dev_private; bus_address = buf->bus_address; --------------------------------------------------------- [BUG] /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/char/drm/mga_state.c:923:mga_indices: ERROR:RANGE:915:923: Using user length "idx"as an array index for "buflist" set by 'copy_from_user':915 [val=800] drm_buf_t *buf; drm_mga_buf_priv_t *buf_priv; drm_mga_indices_t indices; if (copy_from_user(&indices, Start ---> (drm_mga_indices_t *)arg, sizeof(indices))) return -EFAULT; if (!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) { DRM_ERROR("mga_indices called without lock held\n"); return -EINVAL; } Error ---> buf = dma->buflist[indices.idx]; buf_priv = buf->dev_private; buf_priv->discard = indices.discard; --------------------------------------------------------- [BUG] /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/char/drm/mga_state.c:877:mga_vertex: ERROR:RANGE:869:877: Using user length "idx"as an array index for "buflist" set by 'copy_from_user':869 [val=800] drm_device_dma_t *dma = dev->dma; drm_buf_t *buf; drm_mga_buf_priv_t *buf_priv; drm_mga_vertex_t vertex; Start ---> if (copy_from_user(&vertex, (drm_mga_vertex_t *) arg, sizeof(vertex))) return -EFAULT; if (!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) { DRM_ERROR("mga_vertex called without lock held\n"); return -EINVAL; } Error ---> buf = dma->buflist[vertex.idx]; buf_priv = buf->dev_private; buf->used = vertex.used; --------------------------------------------------------- [BUG] (but i'm not sure whey we're missing the initial irq). /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/net/hamradio/scc.c:1772:scc_net_ioctl: ERROR:RANGE:1762:1772: Using user length "irq"as an array index for "Ivec" set by 'copy_from_user':1762 [val=1000] if (!arg) return -EFAULT; if (Nchips >= SCC_MAXCHIPS) return -EINVAL; Start ---> if (copy_from_user(&hwcfg, arg, sizeof(hwcfg))) return -EFAULT; if (hwcfg.irq == 2) hwcfg.irq = 9; if (!Ivec[hwcfg.irq].used && hwcfg.irq) { if (request_irq(hwcfg.irq, scc_isr, SA_INTERRUPT, "AX.25 SCC", NULL)) printk(KERN_WARNING "z8530drv: warning, cannot get IRQ %d\n", hwcfg.irq); else Error ---> Ivec[hwcfg.irq].used = 1; } if (hwcfg.vector_latch) { --------------------------------------------------------- [BUG] /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/char/drm/i810_dma.c:1291:i810_dma_vertex: ERROR:RANGE:1278:1291: Using user length "idx"as an array index for "buflist" set by 'copy_from_user':1278 [val=1300] u32 *hw_status = (u32 *)dev_priv->hw_status_page; drm_i810_sarea_t *sarea_priv = (drm_i810_sarea_t *) dev_priv->sarea_priv; drm_i810_vertex_t vertex; Start ---> if (copy_from_user(&vertex, (drm_i810_vertex_t *)arg, sizeof(vertex))) return -EFAULT; if(!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) { DRM_ERROR("i810_dma_vertex called without lock held\n"); return -EINVAL; } DRM_DEBUG("i810 dma vertex, idx %d used %d discard %d\n", vertex.idx, vertex.used, vertex.discard); i810_dma_dispatch_vertex( dev, dma->buflist[ vertex.idx ], Error ---> vertex.discard, vertex.used ); atomic_add(vertex.used, &dma->total_bytes); atomic_inc(&dma->total_dmas); --------------------------------------------------------- [BUG] channel is an integer. /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/media/video/zr36120.c:1032:zoran_ioctl: ERROR:RANGE:1010:1032: Using user length "channel"as an array index for "video_mux" set by 'copy_from_user':1010 [val=2200] case VIDIOCGCHAN: { struct video_channel v; int mux; Start ---> if (copy_from_user(&v, arg,sizeof(v))) ... DELETED 16 lines ... /* too many inputs? no decoder -> no channels */ if (!ztv->have_decoder || v.channel >= ztv->card->video_inputs) return -EINVAL; /* now determine the name of the channel */ Error ---> mux = ztv->card->video_mux[v.channel]; if (mux & IS_TUNER) { /* lets assume only one tuner, yes? */ strcpy(v.name,"Television"); --------------------------------------------------------- [BUG] usbvideo_GetFrame can fail, but result is not checked before index. /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/usb/usbvideo.c:1596:usbvideo_v4l_ioctl: ERROR:RANGE:1572:1596: Using user length "frameNum"as an array index for "frame" set by 'copy_from_user':1572 [val=2400] } case VIDIOCSYNC: { int frameNum, ret; Start ---> if (copy_from_user((void *)&frameNum, arg, sizeof(frameNum))) { ... DELETED 18 lines ... * The frame is in FrameState_Done_Hold state. Release it * right now because its data is already mapped into * the user space and it's up to the application to * make use of it until it asks for another frame. */ Error ---> uvd->frame[frameNum].frameState = FrameState_Unused; return ret; } case VIDIOCGFBUF: --------------------------------------------------------- [BUG] though, actually, the line: down(&raw_devices[minor].mutex) is much more dangerous. /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/char/raw.c:234:raw_ctl_ioctl: ERROR:RANGE:192:234: Using user length "minor"as an array index for "raw_devices" [val=4200] case RAW_SETBIND: case RAW_GETBIND: /* First, find out which raw minor we want */ Start ---> err = copy_from_user(&rq, (void *) arg, sizeof(rq)); ... DELETED 36 lines ... up(&raw_devices[minor].mutex); err = -EBUSY; break; } if (raw_devices[minor].binding) Error ---> bdput(raw_devices[minor].binding); raw_devices[minor].binding = bdget(kdev_t_to_nr(MKDEV(rq.block_major, rq.block_minor))); up(&raw_devices[minor].mutex); --------------------------------------------------------- [BUG] /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/char/raw.c:242:raw_ctl_ioctl: ERROR:RANGE:192:242: Using user length "minor"as an array index for "raw_devices" [val=5000] case RAW_SETBIND: case RAW_GETBIND: /* First, find out which raw minor we want */ Start ---> err = copy_from_user(&rq, (void *) arg, sizeof(rq)); ... DELETED 44 lines ... up(&raw_devices[minor].mutex); } else { struct block_device *bdev; kdev_t dev; Error ---> bdev = raw_devices[minor].binding; if (bdev) { dev = to_kdev_t(bdev->bd_dev); rq.block_major = MAJOR(dev); --------------------------------------------------------- [BUG] pretty sure, though the code is convoluted. /u2/engler/mc/oses/linux/2.4.5-ac8/drivers/media/video/zr36067.c:3505:do_zoran_ioctl: ERROR:RANGE:3435:3505: Using user length "norm"as an array index for "cardnorms" [val=7000] struct video_channel v; int input; int on, res; int encoder_norm; Start ---> if (copy_from_user(&v, arg, sizeof(v))) { ... DELETED 64 lines ... } encoder_norm = v.norm; zr->params.norm = v.norm; zr->params.input = v.channel; Error ---> zr->timing = cardnorms[zr->card][zr->params.norm]; /* We switch overlay off and on since a change in the norm needs different VFE settings */ - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/