New version that protects r128 and radeon IOCTLs with root capablity check. Please review this patch. We need everyone's eyes to make sure there are no accidental security holes.
-- Jon Smirl [EMAIL PROTECTED] diff --git a/linux-core/drmP.h b/linux-core/drmP.h --- a/linux-core/drmP.h +++ b/linux-core/drmP.h @@ -280,7 +280,7 @@ typedef int drm_ioctl_compat_t(struct fi typedef struct drm_ioctl_desc { drm_ioctl_t *func; int auth_needed; - int root_only; + int master; } drm_ioctl_desc_t; typedef struct drm_devstate { @@ -375,6 +375,7 @@ typedef struct drm_buf_entry { /** File private data */ typedef struct drm_file { int authenticated; + int master; int minor; pid_t pid; uid_t uid; diff --git a/linux-core/drm_bufs.c b/linux-core/drm_bufs.c --- a/linux-core/drm_bufs.c +++ b/linux-core/drm_bufs.c @@ -56,7 +56,8 @@ static drm_local_map_t *drm_find_matchin list_for_each(list, &dev->maplist->head) { drm_map_list_t *entry = list_entry(list, drm_map_list_t, head); if (entry->map && map->type == entry->map->type && - entry->map->offset == map->offset) { + ((entry->map->offset == map->offset) || + (map->type == _DRM_SHM))) { return entry->map; } } @@ -163,6 +164,19 @@ int drm_addmap(drm_device_t * dev, unsig map->handle = drm_ioremap(map->offset, map->size, dev); break; case _DRM_SHM: + found_map = drm_find_matching_map(dev, map); + if (found_map != NULL) { + if (found_map->size != map->size) { + DRM_DEBUG("Matching maps of type %d with " + "mismatched sizes, (%ld vs %ld)\n", + map->type, map->size, found_map->size); + found_map->size = map->size; + } + + drm_free(map, sizeof(*map), DRM_MEM_MAPS); + *map_ptr = found_map; + return 0; + } map->handle = vmalloc_32(map->size); DRM_DEBUG("%lu %d %p\n", map->size, drm_order(map->size), map->handle); @@ -181,15 +195,34 @@ int drm_addmap(drm_device_t * dev, unsig dev->sigdata.lock = dev->lock.hw_lock = map->handle; /* Pointer to lock */ } break; - case _DRM_AGP: - if (drm_core_has_AGP(dev)) { + case _DRM_AGP: { + drm_agp_mem_t *entry; + int valid = 0; + + if (!drm_core_has_AGP(dev)) { + drm_free(map, sizeof(*map), DRM_MEM_MAPS); + return -EINVAL; + } #ifdef __alpha__ - map->offset += dev->hose->mem_space->start; + map->offset += dev->hose->mem_space->start; #endif - map->offset += dev->agp->base; - map->mtrr = dev->agp->agp_mtrr; /* for getmap */ + map->offset += dev->agp->base; + map->mtrr = dev->agp->agp_mtrr; /* for getmap */ + + for (entry = dev->agp->memory; entry; entry = entry->next) { + if ((map->offset >= entry->bound) && + (map->offset + map->offset <= entry->bound + entry->pages * PAGE_SIZE)) { + valid = 1; + break; + } + } + if (!valid) { + drm_free(map, sizeof(*map), DRM_MEM_MAPS); + return -EPERM; } + DRM_DEBUG("AGP offset = 0x%08lx, size = 0x%08lx\n", map->offset, map->size); break; + } case _DRM_SCATTER_GATHER: if (!dev->sg) { drm_free(map, sizeof(*map), DRM_MEM_MAPS); @@ -258,6 +291,9 @@ int drm_addmap_ioctl(struct inode *inode return -EFAULT; } + if (!(capable(CAP_SYS_ADMIN) || map.type == _DRM_AGP)) + return -EPERM; + err = drm_addmap( dev, map.offset, map.size, map.type, map.flags, & map_ptr ); diff --git a/linux-core/drm_drv.c b/linux-core/drm_drv.c --- a/linux-core/drm_drv.c +++ b/linux-core/drm_drv.c @@ -545,13 +545,14 @@ int drm_ioctl(struct inode *inode, struc if (!func) { DRM_DEBUG("no function\n"); retcode = -EINVAL; - } else if ((ioctl->root_only && !capable(CAP_SYS_ADMIN)) || - (ioctl->auth_needed && !priv->authenticated)) { + } else if (((ioctl->master && !priv->master) || + (ioctl->auth_needed && !priv->authenticated)) && + (!capable(CAP_SYS_ADMIN))) { retcode = -EACCES; } else { retcode = func(inode, filp, cmd, arg); } - err_i1: +err_i1: atomic_dec(&dev->ioctl_count); if (retcode) DRM_DEBUG("ret = %x\n", retcode); diff --git a/linux-core/drm_fops.c b/linux-core/drm_fops.c --- a/linux-core/drm_fops.c +++ b/linux-core/drm_fops.c @@ -34,18 +34,26 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "drmP.h" #include <linux/poll.h> +#include "drmP.h" +#include "drm_sarea.h" + static int drm_open_helper(struct inode *inode, struct file *filp, drm_device_t * dev); static int drm_setup(drm_device_t * dev) { + drm_local_map_t *map; int i; if (dev->driver->presetup) dev->driver->presetup(dev); + /* prebuild the SAREA */ + i = drm_addmap(dev, 0, SAREA_MAX, _DRM_SHM, _DRM_CONTAINS_LOCK, &map); + if (i != 0) + return i; + atomic_set(&dev->ioctl_count, 0); atomic_set(&dev->vma_count, 0); dev->buf_use = 0; @@ -72,7 +80,7 @@ static int drm_setup(drm_device_t * dev) INIT_LIST_HEAD(&dev->ctxlist->head); dev->vmalist = NULL; - dev->sigdata.lock = dev->lock.hw_lock = NULL; + dev->sigdata.lock = NULL; init_waitqueue_head(&dev->lock.lock_queue); dev->queue_count = 0; dev->queue_reserved = 0; @@ -244,6 +252,7 @@ static int drm_open_helper(struct inode priv->minor = minor; priv->head = drm_heads[minor]; priv->ioctl_count = 0; + /* for compatibility root is always authenticated */ priv->authenticated = capable(CAP_SYS_ADMIN); priv->lock_count = 0; @@ -259,6 +268,9 @@ static int drm_open_helper(struct inode priv->prev = NULL; dev->file_first = priv; dev->file_last = priv; + /* first opener automatically becomes master and authenticated */ + priv->master = 1; + priv->authenticated = 1; } else { priv->next = NULL; priv->prev = dev->file_last; diff --git a/shared-core/drm.h b/shared-core/drm.h --- a/shared-core/drm.h +++ b/shared-core/drm.h @@ -62,6 +62,12 @@ #define __user #endif +#ifdef __GNUC__ +# define DEPRECATED __attribute__ ((deprecated)) +#else +# define DEPRECATED +#endif + #if defined(__linux__) #if defined(__KERNEL__) #include <linux/config.h> diff --git a/shared-core/r128_state.c b/shared-core/r128_state.c --- a/shared-core/r128_state.c +++ b/shared-core/r128_state.c @@ -1576,6 +1576,9 @@ static int r128_cce_indirect(DRM_IOCTL_A LOCK_TEST_WITH_RETURN(dev, filp); + if (!(capable(CAP_SYS_ADMIN))) + return DRM_ERR(EPERM); + if (!dev_priv) { DRM_ERROR("%s called with no initialization\n", __FUNCTION__); return DRM_ERR(EINVAL); diff --git a/shared-core/radeon_cp.c b/shared-core/radeon_cp.c --- a/shared-core/radeon_cp.c +++ b/shared-core/radeon_cp.c @@ -1245,7 +1245,7 @@ static void radeon_set_pciegart(drm_rade u32 tmp = RADEON_READ_PCIE(dev_priv, RADEON_PCIE_TX_GART_CNTL); if (on) { - DRM_DEBUG("programming pcie %08X %08lX %08X\n", dev_priv->gart_vm_start, dev_priv->bus_pci_gart,dev_priv->gart_size); + DRM_DEBUG("programming pcie %08X %08X %08X\n", dev_priv->gart_vm_start, dev_priv->bus_pci_gart,dev_priv->gart_size); RADEON_WRITE_PCIE(RADEON_PCIE_TX_DISCARD_RD_ADDR_LO, dev_priv->gart_vm_start); RADEON_WRITE_PCIE(RADEON_PCIE_TX_GART_BASE, dev_priv->bus_pci_gart); RADEON_WRITE_PCIE(RADEON_PCIE_TX_GART_START_LO, dev_priv->gart_vm_start); @@ -1398,8 +1398,6 @@ static int radeon_do_init_cp(drm_device_ DRM_GETSAREA(); - dev_priv->fb_offset = init->fb_offset; - dev_priv->mmio_offset = init->mmio_offset; dev_priv->ring_offset = init->ring_offset; dev_priv->ring_rptr_offset = init->ring_rptr_offset; dev_priv->buffers_offset = init->buffers_offset; @@ -1411,12 +1409,6 @@ static int radeon_do_init_cp(drm_device_ return DRM_ERR(EINVAL); } - dev_priv->mmio = drm_core_findmap(dev, init->mmio_offset); - if (!dev_priv->mmio) { - DRM_ERROR("could not find mmio region!\n"); - radeon_do_cleanup_cp(dev); - return DRM_ERR(EINVAL); - } dev_priv->cp_ring = drm_core_findmap(dev, init->ring_offset); if (!dev_priv->cp_ring) { DRM_ERROR("could not find cp ring region!\n"); diff --git a/shared-core/radeon_drm.h b/shared-core/radeon_drm.h --- a/shared-core/radeon_drm.h +++ b/shared-core/radeon_drm.h @@ -492,7 +492,7 @@ typedef struct drm_radeon_init { RADEON_INIT_R300_CP = 0x04 } func; unsigned long sarea_priv_offset; - int is_pci; /* not used, driver asks hardware */ + int is_pci DEPRECATED; /* deprecated, driver asks hardware */ int cp_mode; int gart_size; int ring_size; @@ -504,8 +504,8 @@ typedef struct drm_radeon_init { unsigned int depth_bpp; unsigned int depth_offset, depth_pitch; - unsigned long fb_offset; - unsigned long mmio_offset; + unsigned long fb_offset DEPRECATED; /* deprecated, driver asks hardware */ + unsigned long mmio_offset DEPRECATED; /* deprecated, driver asks hardware */ unsigned long ring_offset; unsigned long ring_rptr_offset; unsigned long buffers_offset; diff --git a/shared-core/radeon_drv.h b/shared-core/radeon_drv.h --- a/shared-core/radeon_drv.h +++ b/shared-core/radeon_drv.h @@ -242,8 +242,6 @@ typedef struct drm_radeon_private { drm_radeon_depth_clear_t depth_clear; - unsigned long fb_offset; - unsigned long mmio_offset; unsigned long ring_offset; unsigned long ring_rptr_offset; unsigned long buffers_offset; diff --git a/shared-core/radeon_state.c b/shared-core/radeon_state.c --- a/shared-core/radeon_state.c +++ b/shared-core/radeon_state.c @@ -2330,6 +2330,9 @@ static int radeon_cp_indirect(DRM_IOCTL_ LOCK_TEST_WITH_RETURN(dev, filp); + if (!(capable(CAP_SYS_ADMIN))) + return DRM_ERR(EPERM); + if (!dev_priv) { DRM_ERROR("%s called with no initialization\n", __FUNCTION__); return DRM_ERR(EINVAL); @@ -2917,7 +2920,7 @@ static int radeon_cp_getparam(DRM_IOCTL_ value = dev_priv->gart_vm_start; break; case RADEON_PARAM_REGISTER_HANDLE: - value = dev_priv->mmio_offset; + value = dev_priv->mmio->offset; break; case RADEON_PARAM_STATUS_HANDLE: value = dev_priv->ring_rptr_offset;
diff --git a/linux-core/drmP.h b/linux-core/drmP.h --- a/linux-core/drmP.h +++ b/linux-core/drmP.h @@ -280,7 +280,7 @@ typedef int drm_ioctl_compat_t(struct fi typedef struct drm_ioctl_desc { drm_ioctl_t *func; int auth_needed; - int root_only; + int master; } drm_ioctl_desc_t; typedef struct drm_devstate { @@ -375,6 +375,7 @@ typedef struct drm_buf_entry { /** File private data */ typedef struct drm_file { int authenticated; + int master; int minor; pid_t pid; uid_t uid; diff --git a/linux-core/drm_bufs.c b/linux-core/drm_bufs.c --- a/linux-core/drm_bufs.c +++ b/linux-core/drm_bufs.c @@ -56,7 +56,8 @@ static drm_local_map_t *drm_find_matchin list_for_each(list, &dev->maplist->head) { drm_map_list_t *entry = list_entry(list, drm_map_list_t, head); if (entry->map && map->type == entry->map->type && - entry->map->offset == map->offset) { + ((entry->map->offset == map->offset) || + (map->type == _DRM_SHM))) { return entry->map; } } @@ -163,6 +164,19 @@ int drm_addmap(drm_device_t * dev, unsig map->handle = drm_ioremap(map->offset, map->size, dev); break; case _DRM_SHM: + found_map = drm_find_matching_map(dev, map); + if (found_map != NULL) { + if (found_map->size != map->size) { + DRM_DEBUG("Matching maps of type %d with " + "mismatched sizes, (%ld vs %ld)\n", + map->type, map->size, found_map->size); + found_map->size = map->size; + } + + drm_free(map, sizeof(*map), DRM_MEM_MAPS); + *map_ptr = found_map; + return 0; + } map->handle = vmalloc_32(map->size); DRM_DEBUG("%lu %d %p\n", map->size, drm_order(map->size), map->handle); @@ -181,15 +195,34 @@ int drm_addmap(drm_device_t * dev, unsig dev->sigdata.lock = dev->lock.hw_lock = map->handle; /* Pointer to lock */ } break; - case _DRM_AGP: - if (drm_core_has_AGP(dev)) { + case _DRM_AGP: { + drm_agp_mem_t *entry; + int valid = 0; + + if (!drm_core_has_AGP(dev)) { + drm_free(map, sizeof(*map), DRM_MEM_MAPS); + return -EINVAL; + } #ifdef __alpha__ - map->offset += dev->hose->mem_space->start; + map->offset += dev->hose->mem_space->start; #endif - map->offset += dev->agp->base; - map->mtrr = dev->agp->agp_mtrr; /* for getmap */ + map->offset += dev->agp->base; + map->mtrr = dev->agp->agp_mtrr; /* for getmap */ + + for (entry = dev->agp->memory; entry; entry = entry->next) { + if ((map->offset >= entry->bound) && + (map->offset + map->offset <= entry->bound + entry->pages * PAGE_SIZE)) { + valid = 1; + break; + } + } + if (!valid) { + drm_free(map, sizeof(*map), DRM_MEM_MAPS); + return -EPERM; } + DRM_DEBUG("AGP offset = 0x%08lx, size = 0x%08lx\n", map->offset, map->size); break; + } case _DRM_SCATTER_GATHER: if (!dev->sg) { drm_free(map, sizeof(*map), DRM_MEM_MAPS); @@ -258,6 +291,9 @@ int drm_addmap_ioctl(struct inode *inode return -EFAULT; } + if (!(capable(CAP_SYS_ADMIN) || map.type == _DRM_AGP)) + return -EPERM; + err = drm_addmap( dev, map.offset, map.size, map.type, map.flags, & map_ptr ); diff --git a/linux-core/drm_drv.c b/linux-core/drm_drv.c --- a/linux-core/drm_drv.c +++ b/linux-core/drm_drv.c @@ -545,13 +545,14 @@ int drm_ioctl(struct inode *inode, struc if (!func) { DRM_DEBUG("no function\n"); retcode = -EINVAL; - } else if ((ioctl->root_only && !capable(CAP_SYS_ADMIN)) || - (ioctl->auth_needed && !priv->authenticated)) { + } else if (((ioctl->master && !priv->master) || + (ioctl->auth_needed && !priv->authenticated)) && + (!capable(CAP_SYS_ADMIN))) { retcode = -EACCES; } else { retcode = func(inode, filp, cmd, arg); } - err_i1: +err_i1: atomic_dec(&dev->ioctl_count); if (retcode) DRM_DEBUG("ret = %x\n", retcode); diff --git a/linux-core/drm_fops.c b/linux-core/drm_fops.c --- a/linux-core/drm_fops.c +++ b/linux-core/drm_fops.c @@ -34,18 +34,26 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "drmP.h" #include <linux/poll.h> +#include "drmP.h" +#include "drm_sarea.h" + static int drm_open_helper(struct inode *inode, struct file *filp, drm_device_t * dev); static int drm_setup(drm_device_t * dev) { + drm_local_map_t *map; int i; if (dev->driver->presetup) dev->driver->presetup(dev); + /* prebuild the SAREA */ + i = drm_addmap(dev, 0, SAREA_MAX, _DRM_SHM, _DRM_CONTAINS_LOCK, &map); + if (i != 0) + return i; + atomic_set(&dev->ioctl_count, 0); atomic_set(&dev->vma_count, 0); dev->buf_use = 0; @@ -72,7 +80,7 @@ static int drm_setup(drm_device_t * dev) INIT_LIST_HEAD(&dev->ctxlist->head); dev->vmalist = NULL; - dev->sigdata.lock = dev->lock.hw_lock = NULL; + dev->sigdata.lock = NULL; init_waitqueue_head(&dev->lock.lock_queue); dev->queue_count = 0; dev->queue_reserved = 0; @@ -244,6 +252,7 @@ static int drm_open_helper(struct inode priv->minor = minor; priv->head = drm_heads[minor]; priv->ioctl_count = 0; + /* for compatibility root is always authenticated */ priv->authenticated = capable(CAP_SYS_ADMIN); priv->lock_count = 0; @@ -259,6 +268,9 @@ static int drm_open_helper(struct inode priv->prev = NULL; dev->file_first = priv; dev->file_last = priv; + /* first opener automatically becomes master and authenticated */ + priv->master = 1; + priv->authenticated = 1; } else { priv->next = NULL; priv->prev = dev->file_last; diff --git a/shared-core/drm.h b/shared-core/drm.h --- a/shared-core/drm.h +++ b/shared-core/drm.h @@ -62,6 +62,12 @@ #define __user #endif +#ifdef __GNUC__ +# define DEPRECATED __attribute__ ((deprecated)) +#else +# define DEPRECATED +#endif + #if defined(__linux__) #if defined(__KERNEL__) #include <linux/config.h> diff --git a/shared-core/r128_state.c b/shared-core/r128_state.c --- a/shared-core/r128_state.c +++ b/shared-core/r128_state.c @@ -1576,6 +1576,9 @@ static int r128_cce_indirect(DRM_IOCTL_A LOCK_TEST_WITH_RETURN(dev, filp); + if (!(capable(CAP_SYS_ADMIN))) + return DRM_ERR(EPERM); + if (!dev_priv) { DRM_ERROR("%s called with no initialization\n", __FUNCTION__); return DRM_ERR(EINVAL); diff --git a/shared-core/radeon_cp.c b/shared-core/radeon_cp.c --- a/shared-core/radeon_cp.c +++ b/shared-core/radeon_cp.c @@ -1245,7 +1245,7 @@ static void radeon_set_pciegart(drm_rade u32 tmp = RADEON_READ_PCIE(dev_priv, RADEON_PCIE_TX_GART_CNTL); if (on) { - DRM_DEBUG("programming pcie %08X %08lX %08X\n", dev_priv->gart_vm_start, dev_priv->bus_pci_gart,dev_priv->gart_size); + DRM_DEBUG("programming pcie %08X %08X %08X\n", dev_priv->gart_vm_start, dev_priv->bus_pci_gart,dev_priv->gart_size); RADEON_WRITE_PCIE(RADEON_PCIE_TX_DISCARD_RD_ADDR_LO, dev_priv->gart_vm_start); RADEON_WRITE_PCIE(RADEON_PCIE_TX_GART_BASE, dev_priv->bus_pci_gart); RADEON_WRITE_PCIE(RADEON_PCIE_TX_GART_START_LO, dev_priv->gart_vm_start); @@ -1398,8 +1398,6 @@ static int radeon_do_init_cp(drm_device_ DRM_GETSAREA(); - dev_priv->fb_offset = init->fb_offset; - dev_priv->mmio_offset = init->mmio_offset; dev_priv->ring_offset = init->ring_offset; dev_priv->ring_rptr_offset = init->ring_rptr_offset; dev_priv->buffers_offset = init->buffers_offset; @@ -1411,12 +1409,6 @@ static int radeon_do_init_cp(drm_device_ return DRM_ERR(EINVAL); } - dev_priv->mmio = drm_core_findmap(dev, init->mmio_offset); - if (!dev_priv->mmio) { - DRM_ERROR("could not find mmio region!\n"); - radeon_do_cleanup_cp(dev); - return DRM_ERR(EINVAL); - } dev_priv->cp_ring = drm_core_findmap(dev, init->ring_offset); if (!dev_priv->cp_ring) { DRM_ERROR("could not find cp ring region!\n"); diff --git a/shared-core/radeon_drm.h b/shared-core/radeon_drm.h --- a/shared-core/radeon_drm.h +++ b/shared-core/radeon_drm.h @@ -492,7 +492,7 @@ typedef struct drm_radeon_init { RADEON_INIT_R300_CP = 0x04 } func; unsigned long sarea_priv_offset; - int is_pci; /* not used, driver asks hardware */ + int is_pci DEPRECATED; /* deprecated, driver asks hardware */ int cp_mode; int gart_size; int ring_size; @@ -504,8 +504,8 @@ typedef struct drm_radeon_init { unsigned int depth_bpp; unsigned int depth_offset, depth_pitch; - unsigned long fb_offset; - unsigned long mmio_offset; + unsigned long fb_offset DEPRECATED; /* deprecated, driver asks hardware */ + unsigned long mmio_offset DEPRECATED; /* deprecated, driver asks hardware */ unsigned long ring_offset; unsigned long ring_rptr_offset; unsigned long buffers_offset; diff --git a/shared-core/radeon_drv.h b/shared-core/radeon_drv.h --- a/shared-core/radeon_drv.h +++ b/shared-core/radeon_drv.h @@ -242,8 +242,6 @@ typedef struct drm_radeon_private { drm_radeon_depth_clear_t depth_clear; - unsigned long fb_offset; - unsigned long mmio_offset; unsigned long ring_offset; unsigned long ring_rptr_offset; unsigned long buffers_offset; diff --git a/shared-core/radeon_state.c b/shared-core/radeon_state.c --- a/shared-core/radeon_state.c +++ b/shared-core/radeon_state.c @@ -2330,6 +2330,9 @@ static int radeon_cp_indirect(DRM_IOCTL_ LOCK_TEST_WITH_RETURN(dev, filp); + if (!(capable(CAP_SYS_ADMIN))) + return DRM_ERR(EPERM); + if (!dev_priv) { DRM_ERROR("%s called with no initialization\n", __FUNCTION__); return DRM_ERR(EINVAL); @@ -2917,7 +2920,7 @@ static int radeon_cp_getparam(DRM_IOCTL_ value = dev_priv->gart_vm_start; break; case RADEON_PARAM_REGISTER_HANDLE: - value = dev_priv->mmio_offset; + value = dev_priv->mmio->offset; break; case RADEON_PARAM_STATUS_HANDLE: value = dev_priv->ring_rptr_offset;