|  | /* | 
|  | * 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; | 
|  | } |