#include <kern/kalloc.h>
#include <kern/thread.h>
#include <kern/misc_protos.h>
#include <vm/vm_kern.h>
#include <i386/machdep_call.h>
#include <i386/user_ldt.h>
#include <i386/mp.h>
#include <i386/machine_routines.h>
#include <i386/proc_reg.h>
#include <i386/mp_desc.h>
#include <i386/seg.h>
#include <i386/thread.h>
#include <IOKit/IOBSD.h>
#include <sys/csr.h>
#include <sys/errno.h>
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 LDT_IN_64BITPROC_ENTITLEMENT "com.apple.security.ldt-in-64bit-process"
static int
i386_set_ldt_impl(
uint32_t *retval,
uint64_t start_sel,
uint64_t descs,
uint64_t num_sels)
{
user_ldt_t new_ldt, old_ldt;
struct real_descriptor *dp;
unsigned int i;
unsigned int min_selector = LDTSZ_MIN;
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));
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;
}
} else {
null_count = 0;
}
}
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 {
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;
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;
if (old_ldt) {
bcopy(&old_ldt->ldt[0],
&new_ldt->ldt[old_ldt->start - begin_sel],
old_ldt->count * sizeof(struct real_descriptor));
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));
}
}
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));
}
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:
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;
}
if (dp->granularity & SZ_64) {
task_unlock(task);
user_ldt_free(new_ldt);
return EACCES;
}
}
}
task->i386_ldt = new_ldt;
mp_broadcast(user_ldt_set_action, task);
task_unlock(task);
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,
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;
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(user_ldt, sizeof(struct user_ldt) + (user_ldt->count * sizeof(struct real_descriptor)));
}
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;
}
void
user_ldt_set_action(
void *arg)
{
task_t arg_task = (task_t)arg;
if (arg_task == current_task()) {
user_ldt_set(current_thread());
}
}
void
user_ldt_set(
thread_t thread)
{
task_t task = thread->task;
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);
}
}
int
i386_set_ldt(
uint32_t *retval,
uint32_t start_sel,
uint32_t descs,
uint32_t num_sels)
{
return i386_set_ldt_impl(retval, (uint64_t)start_sel, (uint64_t)descs,
(uint64_t)num_sels);
}
int
i386_set_ldt64(
uint32_t *retval,
uint64_t start_sel,
uint64_t descs,
uint64_t num_sels)
{
if (csr_check(CSR_ALLOW_UNTRUSTED_KEXTS) != 0 &&
!IOTaskHasEntitlement(current_task(), LDT_IN_64BITPROC_ENTITLEMENT)) {
return EPERM;
}
return i386_set_ldt_impl(retval, start_sel, descs, num_sels);
}
int
i386_get_ldt(
uint32_t *retval,
uint32_t start_sel,
uint32_t descs,
uint32_t num_sels)
{
return i386_get_ldt_impl(retval, (uint64_t)start_sel, (uint64_t)descs,
(uint64_t)num_sels);
}
int
i386_get_ldt64(
uint32_t *retval,
uint64_t start_sel,
uint64_t descs,
uint64_t num_sels)
{
if (csr_check(CSR_ALLOW_UNTRUSTED_KEXTS) != 0 &&
!IOTaskHasEntitlement(current_task(), LDT_IN_64BITPROC_ENTITLEMENT)) {
return EPERM;
}
return i386_get_ldt_impl(retval, start_sel, descs, num_sels);
}