148 lines
3.5 KiB
C
148 lines
3.5 KiB
C
/*
|
|
* Copyright (C) 2001, 2004 Hewlett-Packard Co
|
|
* David Mosberger-Tang <davidm@hpl.hp.com>
|
|
*
|
|
* Adapted from arch/i386/kernel/ldt.c
|
|
*/
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/string.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/smp_lock.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
#include "ia32priv.h"
|
|
|
|
/*
|
|
* read_ldt() is not really atomic - this is not a problem since synchronization of reads
|
|
* and writes done to the LDT has to be assured by user-space anyway. Writes are atomic,
|
|
* to protect the security checks done on new descriptors.
|
|
*/
|
|
static int
|
|
read_ldt (void __user *ptr, unsigned long bytecount)
|
|
{
|
|
unsigned long bytes_left, n;
|
|
char __user *src, *dst;
|
|
char buf[256]; /* temporary buffer (don't overflow kernel stack!) */
|
|
|
|
if (bytecount > IA32_LDT_ENTRIES*IA32_LDT_ENTRY_SIZE)
|
|
bytecount = IA32_LDT_ENTRIES*IA32_LDT_ENTRY_SIZE;
|
|
|
|
bytes_left = bytecount;
|
|
|
|
src = (void __user *) IA32_LDT_OFFSET;
|
|
dst = ptr;
|
|
|
|
while (bytes_left) {
|
|
n = sizeof(buf);
|
|
if (n > bytes_left)
|
|
n = bytes_left;
|
|
|
|
/*
|
|
* We know we're reading valid memory, but we still must guard against
|
|
* running out of memory.
|
|
*/
|
|
if (__copy_from_user(buf, src, n))
|
|
return -EFAULT;
|
|
|
|
if (copy_to_user(dst, buf, n))
|
|
return -EFAULT;
|
|
|
|
src += n;
|
|
dst += n;
|
|
bytes_left -= n;
|
|
}
|
|
return bytecount;
|
|
}
|
|
|
|
static int
|
|
read_default_ldt (void __user * ptr, unsigned long bytecount)
|
|
{
|
|
unsigned long size;
|
|
int err;
|
|
|
|
/* XXX fix me: should return equivalent of default_ldt[0] */
|
|
err = 0;
|
|
size = 8;
|
|
if (size > bytecount)
|
|
size = bytecount;
|
|
|
|
err = size;
|
|
if (clear_user(ptr, size))
|
|
err = -EFAULT;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
write_ldt (void __user * ptr, unsigned long bytecount, int oldmode)
|
|
{
|
|
struct ia32_user_desc ldt_info;
|
|
__u64 entry;
|
|
int ret;
|
|
|
|
if (bytecount != sizeof(ldt_info))
|
|
return -EINVAL;
|
|
if (copy_from_user(&ldt_info, ptr, sizeof(ldt_info)))
|
|
return -EFAULT;
|
|
|
|
if (ldt_info.entry_number >= IA32_LDT_ENTRIES)
|
|
return -EINVAL;
|
|
if (ldt_info.contents == 3) {
|
|
if (oldmode)
|
|
return -EINVAL;
|
|
if (ldt_info.seg_not_present == 0)
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ldt_info.base_addr == 0 && ldt_info.limit == 0
|
|
&& (oldmode || (ldt_info.contents == 0 && ldt_info.read_exec_only == 1
|
|
&& ldt_info.seg_32bit == 0 && ldt_info.limit_in_pages == 0
|
|
&& ldt_info.seg_not_present == 1 && ldt_info.useable == 0)))
|
|
/* allow LDTs to be cleared by the user */
|
|
entry = 0;
|
|
else
|
|
/* we must set the "Accessed" bit as IVE doesn't emulate it */
|
|
entry = IA32_SEG_DESCRIPTOR(ldt_info.base_addr, ldt_info.limit,
|
|
(((ldt_info.read_exec_only ^ 1) << 1)
|
|
| (ldt_info.contents << 2)) | 1,
|
|
1, 3, ldt_info.seg_not_present ^ 1,
|
|
(oldmode ? 0 : ldt_info.useable),
|
|
ldt_info.seg_32bit,
|
|
ldt_info.limit_in_pages);
|
|
/*
|
|
* Install the new entry. We know we're accessing valid (mapped) user-level
|
|
* memory, but we still need to guard against out-of-memory, hence we must use
|
|
* put_user().
|
|
*/
|
|
ret = __put_user(entry, (__u64 __user *) IA32_LDT_OFFSET + ldt_info.entry_number);
|
|
ia32_load_segment_descriptors(current);
|
|
return ret;
|
|
}
|
|
|
|
asmlinkage int
|
|
sys32_modify_ldt (int func, unsigned int ptr, unsigned int bytecount)
|
|
{
|
|
int ret = -ENOSYS;
|
|
|
|
switch (func) {
|
|
case 0:
|
|
ret = read_ldt(compat_ptr(ptr), bytecount);
|
|
break;
|
|
case 1:
|
|
ret = write_ldt(compat_ptr(ptr), bytecount, 1);
|
|
break;
|
|
case 2:
|
|
ret = read_default_ldt(compat_ptr(ptr), bytecount);
|
|
break;
|
|
case 0x11:
|
|
ret = write_ldt(compat_ptr(ptr), bytecount, 0);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|