/* * Copyright (c) 2000-2009 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * @OSF_COPYRIGHT@ */ /* * Mach Operating System * Copyright (c) 1991 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ /* */ /* * User LDT management. * Each task may have its own LDT. */ #include #include #include #include #include #include #include #include #include #include #include #include #include static void user_ldt_set_action(void *); static int i386_set_ldt_impl(uint32_t *retval, uint64_t start_sel, uint64_t descs, uint64_t num_sels); static int i386_get_ldt_impl(uint32_t *retval, uint64_t start_sel, uint64_t descs, uint64_t num_sels); #define USER_LDT_SIZE(descriptors) \ (sizeof(struct user_ldt) + (descriptors * sizeof(struct real_descriptor))) /* * Add the descriptors to the LDT, starting with * the descriptor for 'first_selector'. */ static int i386_set_ldt_impl( uint32_t *retval, uint64_t start_sel, uint64_t descs, /* out */ uint64_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; if (start_sel != LDT_AUTO_ALLOC && (start_sel != 0 || num_sels != 0) && (start_sel < min_selector || start_sel >= LDTSZ || num_sels > LDTSZ)) { return EINVAL; } if (start_sel != LDT_AUTO_ALLOC && start_sel + num_sels > LDTSZ) { return EINVAL; } 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; } } if (start_sel == 0 && num_sels == 0) { new_ldt = NULL; } else { /* * Allocate new LDT */ unsigned int begin_sel = (unsigned int)start_sel; unsigned int end_sel = (unsigned int)begin_sel + (unsigned int)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; /* XXX allocation under task lock */ new_ldt = (user_ldt_t)kalloc_data(USER_LDT_SIZE(ldt_count), Z_WAITOK); 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. */ if (old_ldt) { 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)); } } /* * Install new descriptors. */ if (descs != 0) { /* XXX copyin under task lock */ 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)); } /* * Validate descriptors. * Only allow descriptors with user privileges. */ 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, clear Present preemptively */ dp->access &= (~ACC_P & 0xff); 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: break; default: task_unlock(task); user_ldt_free(new_ldt); return EACCES; } /* Reject attempts to create segments with 64-bit granules */ /* Note this restriction is still correct, even when * executing as a 64-bit process (we want to maintain a single * 64-bit selector (located in the GDT)). */ if (dp->granularity & SZ_64) { task_unlock(task); user_ldt_free(new_ldt); return EACCES; } } } task->i386_ldt = new_ldt; /* new LDT for task */ /* * 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. */ mp_broadcast(user_ldt_set_action, task); task_unlock(task); /* 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. */ if (old_ldt) { user_ldt_free(old_ldt); } *retval = (uint32_t)start_sel; return 0; } static int i386_get_ldt_impl( uint32_t *retval, uint64_t start_sel, uint64_t descs, /* out */ uint64_t num_sels) { user_ldt_t user_ldt; task_t task = current_task(); unsigned int ldt_count; kern_return_t err; if (start_sel >= LDTSZ || num_sels > LDTSZ) { return EINVAL; } if (start_sel + num_sels > LDTSZ) { return EINVAL; } if (descs == 0) { return EINVAL; } task_lock(task); user_ldt = task->i386_ldt; err = 0; /* * copy out the descriptors */ if (user_ldt != 0) { ldt_count = user_ldt->start + user_ldt->count; } else { ldt_count = LDTSZ_MIN; } if (start_sel < ldt_count) { unsigned int copy_sels = (unsigned int)num_sels; if (start_sel + num_sels > ldt_count) { copy_sels = ldt_count - (unsigned int)start_sel; } err = copyout((char *)(current_ldt() + start_sel), descs, copy_sels * sizeof(struct real_descriptor)); } task_unlock(task); *retval = ldt_count; return err; } void user_ldt_free( user_ldt_t user_ldt) { kfree_data(user_ldt, USER_LDT_SIZE(user_ldt->count)); } user_ldt_t user_ldt_copy( user_ldt_t user_ldt) { if (user_ldt != NULL) { size_t size = USER_LDT_SIZE(user_ldt->count); user_ldt_t new_ldt = (user_ldt_t)kalloc_data(size, Z_WAITOK); if (new_ldt != NULL) { bcopy(user_ldt, new_ldt, size); } return new_ldt; } return 0; } void user_ldt_set_action( void *arg) { task_t arg_task = (task_t)arg; if (arg_task == current_task()) { user_ldt_set(current_thread()); } } /* * Set the LDT for the given thread on the current CPU. Should be invoked * with interrupts disabled. */ void user_ldt_set( thread_t thread) { task_t task = get_threadtask(thread); user_ldt_t user_ldt; user_ldt = task->i386_ldt; if (user_ldt != 0) { struct real_descriptor *ldtp = 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 = (uint16_t)((sizeof(struct real_descriptor) * (user_ldt->start + user_ldt->count)) - 1); ml_cpu_set_ldt(USER_LDT); } else { ml_cpu_set_ldt(KERNEL_LDT); } } /* For 32-bit processes, called via machdep_syscall() */ int i386_set_ldt( uint32_t *retval, uint32_t start_sel, uint32_t descs, /* out */ uint32_t num_sels) { return i386_set_ldt_impl(retval, (uint64_t)start_sel, (uint64_t)descs, (uint64_t)num_sels); } /* For 64-bit processes, called via machdep_syscall64() */ int i386_set_ldt64( uint32_t *retval, uint64_t start_sel, uint64_t descs, /* out */ uint64_t num_sels) { return i386_set_ldt_impl(retval, start_sel, descs, num_sels); } /* For 32-bit processes, called via machdep_syscall() */ int i386_get_ldt( uint32_t *retval, uint32_t start_sel, uint32_t descs, /* out */ uint32_t num_sels) { return i386_get_ldt_impl(retval, (uint64_t)start_sel, (uint64_t)descs, (uint64_t)num_sels); } /* For 64-bit processes, called via machdep_syscall64() */ int i386_get_ldt64( uint32_t *retval, uint64_t start_sel, uint64_t descs, /* out */ uint64_t num_sels) { return i386_get_ldt_impl(retval, start_sel, descs, num_sels); }