X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/8f6c56a50524aa785f7e596d52dddfb331e18961..36401178fd6817c043cc00b0c00c7f723e58efae:/osfmk/i386/user_ldt.c diff --git a/osfmk/i386/user_ldt.c b/osfmk/i386/user_ldt.c index a21105f9b..87aab0426 100644 --- a/osfmk/i386/user_ldt.c +++ b/osfmk/i386/user_ldt.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2000-2006 Apple Computer, Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -59,7 +59,7 @@ /* * User LDT management. - * Each thread in a task may have its own LDT. + * Each task may have its own LDT. */ #include @@ -71,366 +71,328 @@ #include #include #include +#include +#include +#include +#include +#include -char acc_type[8][3] = { - /* code stack data */ - { 0, 0, 1 }, /* data */ - { 0, 1, 1 }, /* data, writable */ - { 0, 0, 1 }, /* data, expand-down */ - { 0, 1, 1 }, /* data, writable, expand-down */ - { 1, 0, 0 }, /* code */ - { 1, 0, 1 }, /* code, readable */ - { 1, 0, 0 }, /* code, conforming */ - { 1, 0, 1 }, /* code, readable, conforming */ -}; - -#if 0 -/* Forward */ - -extern boolean_t selector_check( - thread_t thread, - int sel, - int type); - -boolean_t -selector_check( - thread_t thread, - int sel, - int type) -{ - struct user_ldt *ldt; - int access; - - ldt = thread->machine.pcb->ims.ldt; - if (ldt == 0) { - switch (type) { - case S_CODE: - return sel == USER_CS; - case S_STACK: - return sel == USER_DS; - case S_DATA: - return sel == 0 || - sel == USER_CS || - sel == USER_DS; - } - } +#include - if (type != S_DATA && sel == 0) - return FALSE; - if ((sel & (SEL_LDTS|SEL_PL)) != (SEL_LDTS|SEL_PL_U) - || sel > ldt->desc.limit_low) - return FALSE; - - access = ldt->ldt[sel_idx(sel)].access; - - if ((access & (ACC_P|ACC_PL|ACC_TYPE_USER)) - != (ACC_P|ACC_PL_U|ACC_TYPE_USER)) - return FALSE; - /* present, pl == pl.user, not system */ - - return acc_type[(access & 0xe)>>1][type]; -} +static void user_ldt_set_action(void *); /* * Add the descriptors to the LDT, starting with * the descriptor for 'first_selector'. */ -kern_return_t +int i386_set_ldt( - thread_t thr_act, - int first_selector, - descriptor_list_t desc_list, - mach_msg_type_number_t count) + uint32_t *retval, + uint32_t start_sel, + uint32_t descs, /* out */ + uint32_t num_sels) { - user_ldt_t new_ldt, old_ldt, temp; + user_ldt_t new_ldt, old_ldt; struct real_descriptor *dp; - int i; - int min_selector = 0; - pcb_t pcb; - vm_size_t ldt_size_needed; - int first_desc = sel_idx(first_selector); - vm_map_copy_t old_copy_object; - thread_t thread; - - if (first_desc < min_selector || first_desc > 8191) - return KERN_INVALID_ARGUMENT; - if (first_desc + count >= 8192) - return KERN_INVALID_ARGUMENT; - if (thr_act == THREAD_NULL) - return KERN_INVALID_ARGUMENT; - if ((thread = act_lock_thread(thr_act)) == THREAD_NULL) { - act_unlock_thread(thr_act); - return KERN_INVALID_ARGUMENT; - } - if (thread == current_thread()) - min_selector = LDTSZ; - act_unlock_thread(thr_act); + 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; - /* - * We must copy out desc_list to the kernel map, and wire - * it down (we touch it while the PCB is locked). - * - * We make a copy of the copyin object, and clear - * out the old one, so that the MIG stub will have a - * a empty (but valid) copyin object to discard. - */ - { - kern_return_t kr; - vm_map_offset_t dst_addr; - - old_copy_object = (vm_map_copy_t) desc_list; - - kr = vm_map_copyout(ipc_kernel_map, &dst_addr, - vm_map_copy_copy(old_copy_object)); - if (kr != KERN_SUCCESS) - return kr; - - (void) vm_map_wire(ipc_kernel_map, - vm_map_trunc_page(dst_addr), - vm_map_round_page(dst_addr + - count * sizeof(struct real_descriptor)), - VM_PROT_READ|VM_PROT_WRITE, FALSE); - desc_list = CAST_DOWN(descriptor_list_t, dst_addr); - } + 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 + && (uint64_t)start_sel + (uint64_t)num_sels > LDTSZ) /* cast to uint64_t to detect wrap-around */ + return EINVAL; - for (i = 0, dp = (struct real_descriptor *) desc_list; - i < count; - i++, dp++) - { - switch (dp->access & ~ACC_A) { - case 0: - case ACC_P: - /* valid empty descriptor */ - break; - case ACC_P | ACC_CALL_GATE: - /* Mach kernel call */ - *dp = *(struct real_descriptor *) - &ldt[sel_idx(USER_SCALL)]; - break; - case ACC_P | ACC_PL_U | ACC_DATA: - case ACC_P | ACC_PL_U | ACC_DATA_W: - case ACC_P | ACC_PL_U | ACC_DATA_E: - case ACC_P | ACC_PL_U | ACC_DATA_EW: - case ACC_P | ACC_PL_U | ACC_CODE: - case ACC_P | ACC_PL_U | ACC_CODE_R: - case ACC_P | ACC_PL_U | ACC_CODE_C: - case ACC_P | ACC_PL_U | ACC_CODE_CR: - case ACC_P | ACC_PL_U | ACC_CALL_GATE_16: - case ACC_P | ACC_PL_U | ACC_CALL_GATE: - break; - default: - (void) vm_map_remove(ipc_kernel_map, - vm_map_trunc_page(desc_list), - vm_map_round_page(&desc_list[count]), - VM_MAP_REMOVE_KUNWIRE); - return KERN_INVALID_ARGUMENT; + task_lock(task); + + old_ldt = task->i386_ldt; + + if (start_sel == LDT_AUTO_ALLOC) { + if (old_ldt) { + unsigned int null_count; + struct real_descriptor null_ldt; + + bzero(&null_ldt, sizeof(null_ldt)); + + /* + * Look for null selectors among the already-allocated + * entries. + */ + null_count = 0; + i = 0; + while (i < old_ldt->count) + { + if (!memcmp(&old_ldt->ldt[i++], &null_ldt, sizeof(null_ldt))) { + null_count++; + if (null_count == num_sels) + break; /* break out of while loop */ + } else { + null_count = 0; + } + } + + /* + * If we broke out of the while loop, i points to the selector + * after num_sels null selectors. Otherwise it points to the end + * of the old LDTs, and null_count is the number of null selectors + * at the end. + * + * Either way, there are null_count null selectors just prior to + * the i-indexed selector, and either null_count >= num_sels, + * or we're at the end, so we can extend. + */ + start_sel = old_ldt->start + i - null_count; + } else { + start_sel = LDTSZ_MIN; + } + + if (start_sel + num_sels > LDTSZ) { + task_unlock(task); + return ENOMEM; } } - ldt_size_needed = sizeof(struct real_descriptor) - * (first_desc + count); - - pcb = thr_act->machine.pcb; - new_ldt = 0; - Retry: - simple_lock(&pcb->lock); - old_ldt = pcb->ims.ldt; - if (old_ldt == 0 || - old_ldt->desc.limit_low + 1 < ldt_size_needed) - { + + if (start_sel == 0 && num_sels == 0) { + new_ldt = NULL; + } else { /* - * No old LDT, or not big enough + * Allocate new LDT */ - if (new_ldt == 0) { - simple_unlock(&pcb->lock); - - new_ldt = (user_ldt_t) kalloc(ldt_size_needed - + sizeof(struct real_descriptor)); - new_ldt->desc.limit_low = ldt_size_needed - 1; - new_ldt->desc.limit_high = 0; - new_ldt->desc.base_low = - ((vm_offset_t)&new_ldt->ldt[0]) & 0xffff; - new_ldt->desc.base_med = - (((vm_offset_t)&new_ldt->ldt[0]) >> 16) - & 0xff; - new_ldt->desc.base_high = - ((vm_offset_t)&new_ldt->ldt[0]) >> 24; - new_ldt->desc.access = ACC_P | ACC_LDT; - new_ldt->desc.granularity = 0; - - goto Retry; + + 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; + /* * Have new LDT. If there was a an old ldt, copy descriptors - * from old to new. Otherwise copy the default ldt. + * from old to new. */ if (old_ldt) { - bcopy((char *)&old_ldt->ldt[0], - (char *)&new_ldt->ldt[0], - old_ldt->desc.limit_low + 1); + bcopy(&old_ldt->ldt[0], + &new_ldt->ldt[old_ldt->start - begin_sel], + old_ldt->count * sizeof(struct real_descriptor)); + + /* + * If the old and new LDTs are non-overlapping, fill the + * center in with null selectors. + */ + + if (old_ldt->start + old_ldt->count < start_sel) + bzero(&new_ldt->ldt[old_ldt->count], + (start_sel - (old_ldt->start + old_ldt->count)) * sizeof(struct real_descriptor)); + else if (old_ldt->start > start_sel + num_sels) + bzero(&new_ldt->ldt[num_sels], + (old_ldt->start - (start_sel + num_sels)) * sizeof(struct real_descriptor)); } - else if (thr_act == current_thread()) { - struct real_descriptor template = {0, 0, 0, ACC_P, 0, 0 ,0}; - - for (dp = &new_ldt->ldt[0], i = 0; i < first_desc; i++, dp++) { - if (i < LDTSZ) - *dp = *(struct real_descriptor *) &ldt[i]; - else - *dp = template; + + /* + * 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; } + } else { + bzero(&new_ldt->ldt[start_sel - begin_sel], num_sels * sizeof(struct real_descriptor)); } - temp = old_ldt; - old_ldt = new_ldt; /* use new LDT from now on */ - new_ldt = temp; /* discard old LDT */ - - pcb->ims.ldt = old_ldt; /* new LDT for thread */ + /* + * Validate descriptors. + * Only allow descriptors with user priviledges. + */ + for (i = 0, dp = (struct real_descriptor *) &new_ldt->ldt[start_sel - begin_sel]; + i < num_sels; + i++, dp++) + { + switch (dp->access & ~ACC_A) { + case 0: + case ACC_P: + /* valid empty descriptor */ + break; + case ACC_P | ACC_PL_U | ACC_DATA: + case ACC_P | ACC_PL_U | ACC_DATA_W: + case ACC_P | ACC_PL_U | ACC_DATA_E: + case ACC_P | ACC_PL_U | ACC_DATA_EW: + case ACC_P | ACC_PL_U | ACC_CODE: + case ACC_P | ACC_PL_U | ACC_CODE_R: + case ACC_P | ACC_PL_U | ACC_CODE_C: + case ACC_P | ACC_PL_U | ACC_CODE_CR: + case ACC_P | ACC_PL_U | ACC_CALL_GATE_16: + case ACC_P | ACC_PL_U | ACC_CALL_GATE: + break; + default: + task_unlock(task); + user_ldt_free(new_ldt); + return EACCES; + } + } } + task->i386_ldt = new_ldt; /* new LDT for task */ + /* - * Install new descriptors. + * Switch to new LDT. We need to do this on all CPUs, since + * another thread in this same task may be currently running, + * and we need to make sure the new LDT is in place + * throughout the task before returning to the user. */ - bcopy((char *)desc_list, - (char *)&old_ldt->ldt[first_desc], - count * sizeof(struct real_descriptor)); + mp_rendezvous_no_intrs(user_ldt_set_action, task); - simple_unlock(&pcb->lock); + task_unlock(task); - if (new_ldt) - kfree((vm_offset_t)new_ldt, - new_ldt->desc.limit_low+1+sizeof(struct real_descriptor)); - - /* - * Free the descriptor list. + /* free old LDT. We can't do this until after we've + * rendezvoused with all CPUs, in case another thread + * in this task was in the process of context switching. */ - (void) vm_map_remove(ipc_kernel_map, vm_map_trunc_page(desc_list), - vm_map_round_page(&desc_list[count]), - VM_MAP_REMOVE_KUNWIRE); - return KERN_SUCCESS; + if (old_ldt) + user_ldt_free(old_ldt); + + *retval = start_sel; + + return 0; } -kern_return_t +int i386_get_ldt( - thread_t thr_act, - int first_selector, - int selector_count, /* number wanted */ - descriptor_list_t *desc_list, /* in/out */ - mach_msg_type_number_t *count) /* in/out */ + uint32_t *retval, + uint32_t start_sel, + uint32_t descs, /* out */ + uint32_t num_sels) { - struct user_ldt *user_ldt; - pcb_t pcb = thr_act->machine.pcb; - int first_desc = sel_idx(first_selector); + user_ldt_t user_ldt; + task_t task = current_task(); unsigned int ldt_count; - vm_size_t ldt_size; - vm_size_t size, size_needed; - vm_offset_t addr; - thread_t thread; - - if (thr_act == THREAD_NULL) - return KERN_INVALID_ARGUMENT; - - if (first_desc < 0 || first_desc > 8191) - return KERN_INVALID_ARGUMENT; - if (first_desc + selector_count >= 8192) - return KERN_INVALID_ARGUMENT; - - addr = 0; - size = 0; - - for (;;) { - simple_lock(&pcb->lock); - user_ldt = pcb->ims.ldt; - if (user_ldt == 0) { - simple_unlock(&pcb->lock); - if (addr) - kmem_free(ipc_kernel_map, addr, size); - *count = 0; - return KERN_SUCCESS; - } + kern_return_t err; - /* - * Find how many descriptors we should return. - */ - ldt_count = (user_ldt->desc.limit_low + 1) / - sizeof (struct real_descriptor); - ldt_count -= first_desc; - if (ldt_count > selector_count) - ldt_count = selector_count; + if (start_sel >= 8192) + return EINVAL; + if (start_sel + num_sels > 8192) + return EINVAL; + if (descs == 0) + return EINVAL; - ldt_size = ldt_count * sizeof(struct real_descriptor); + task_lock(task); - /* - * Do we have the memory we need? - */ - if (ldt_count <= *count) - break; /* fits in-line */ + user_ldt = task->i386_ldt; + err = 0; - size_needed = round_page(ldt_size); - if (size_needed <= size) - break; + /* + * copy out the descriptors + */ + + if (user_ldt != 0) + ldt_count = user_ldt->start + user_ldt->count; + else + ldt_count = LDTSZ_MIN; - /* - * Unlock the pcb and allocate more memory - */ - simple_unlock(&pcb->lock); - if (size != 0) - kmem_free(ipc_kernel_map, addr, size); + if (start_sel < ldt_count) + { + unsigned int copy_sels = num_sels; - size = size_needed; + if (start_sel + num_sels > ldt_count) + copy_sels = ldt_count - start_sel; - if (kmem_alloc(ipc_kernel_map, &addr, size) - != KERN_SUCCESS) - return KERN_RESOURCE_SHORTAGE; + err = copyout((char *)(current_ldt() + start_sel), + descs, copy_sels * sizeof(struct real_descriptor)); } - /* - * copy out the descriptors - */ - bcopy((char *)&user_ldt->ldt[first_desc], - (char *)addr, - ldt_size); - *count = ldt_count; - simple_unlock(&pcb->lock); + task_unlock(task); - if (addr) { - vm_size_t size_used, size_left; - vm_map_copy_t memory; + *retval = ldt_count; - /* - * Free any unused memory beyond the end of the last page used - */ - size_used = round_page(ldt_size); - if (size_used != size) - kmem_free(ipc_kernel_map, - addr + size_used, size - size_used); + return err; +} - /* - * Zero the remainder of the page being returned. - */ - size_left = size_used - ldt_size; - if (size_left > 0) - bzero((char *)addr + ldt_size, size_left); +void +user_ldt_free( + user_ldt_t user_ldt) +{ + kfree(user_ldt, sizeof(struct user_ldt) + (user_ldt->count * sizeof(struct real_descriptor))); +} - /* - * Unwire the memory and make it into copyin form. - */ - (void) vm_map_unwire(ipc_kernel_map, vm_map_trunc_page(addr), - vm_map_round_page(addr + size_used), FALSE); - (void) vm_map_copyin(ipc_kernel_map, (vm_map_address_t)addr, - (vm_map_size_t)size_used, TRUE, &memory); - *desc_list = (descriptor_list_t) memory; +user_ldt_t +user_ldt_copy( + user_ldt_t user_ldt) +{ + if (user_ldt != NULL) { + size_t size = sizeof(struct user_ldt) + (user_ldt->count * sizeof(struct real_descriptor)); + user_ldt_t new_ldt = (user_ldt_t)kalloc(size); + if (new_ldt != NULL) + bcopy(user_ldt, new_ldt, size); + return new_ldt; } + + return 0; +} - return KERN_SUCCESS; +void +user_ldt_set_action( + void *arg) +{ + task_t arg_task = (task_t)arg; + + if (arg_task == current_task()) { + user_ldt_set(current_thread()); + } } -#endif +/* + * Set the LDT for the given thread on the current CPU. Should be invoked + * with interrupts disabled. + */ void -user_ldt_free( - user_ldt_t user_ldt) +user_ldt_set( + thread_t thread) { - kfree(user_ldt, - user_ldt->desc.limit_low+1+sizeof(struct real_descriptor)); + task_t task = thread->task; + user_ldt_t user_ldt; + + user_ldt = task->i386_ldt; + + if (user_ldt != 0) { + struct real_descriptor *ldtp = (struct real_descriptor *)current_ldt(); + + if (user_ldt->start > LDTSZ_MIN) { + bzero(&ldtp[LDTSZ_MIN], + sizeof(struct real_descriptor) * (user_ldt->start - LDTSZ_MIN)); + } + + bcopy(user_ldt->ldt, &ldtp[user_ldt->start], + sizeof(struct real_descriptor) * (user_ldt->count)); + + gdt_desc_p(USER_LDT)->limit_low = (sizeof(struct real_descriptor) * (user_ldt->start + user_ldt->count)) - 1; + + ml_cpu_set_ldt(USER_LDT); + } else { + ml_cpu_set_ldt(KERNEL_LDT); + } }