diff options
Diffstat (limited to 'arch/ia64/ia32/ia32_ldt.c')
-rw-r--r-- | arch/ia64/ia32/ia32_ldt.c | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/arch/ia64/ia32/ia32_ldt.c b/arch/ia64/ia32/ia32_ldt.c new file mode 100644 index 0000000..a152738 --- /dev/null +++ b/arch/ia64/ia32/ia32_ldt.c @@ -0,0 +1,147 @@ +/* + * 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; +} |