Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Copyright (C) 2001, 2004 Hewlett-Packard Co | |
3 | * David Mosberger-Tang <davidm@hpl.hp.com> | |
4 | * | |
5 | * Adapted from arch/i386/kernel/ldt.c | |
6 | */ | |
7 | ||
8 | #include <linux/errno.h> | |
9 | #include <linux/sched.h> | |
10 | #include <linux/string.h> | |
11 | #include <linux/mm.h> | |
12 | #include <linux/smp.h> | |
1da177e4 LT |
13 | #include <linux/vmalloc.h> |
14 | ||
15 | #include <asm/uaccess.h> | |
16 | ||
17 | #include "ia32priv.h" | |
18 | ||
19 | /* | |
20 | * read_ldt() is not really atomic - this is not a problem since synchronization of reads | |
21 | * and writes done to the LDT has to be assured by user-space anyway. Writes are atomic, | |
22 | * to protect the security checks done on new descriptors. | |
23 | */ | |
24 | static int | |
25 | read_ldt (void __user *ptr, unsigned long bytecount) | |
26 | { | |
27 | unsigned long bytes_left, n; | |
28 | char __user *src, *dst; | |
29 | char buf[256]; /* temporary buffer (don't overflow kernel stack!) */ | |
30 | ||
31 | if (bytecount > IA32_LDT_ENTRIES*IA32_LDT_ENTRY_SIZE) | |
32 | bytecount = IA32_LDT_ENTRIES*IA32_LDT_ENTRY_SIZE; | |
33 | ||
34 | bytes_left = bytecount; | |
35 | ||
36 | src = (void __user *) IA32_LDT_OFFSET; | |
37 | dst = ptr; | |
38 | ||
39 | while (bytes_left) { | |
40 | n = sizeof(buf); | |
41 | if (n > bytes_left) | |
42 | n = bytes_left; | |
43 | ||
44 | /* | |
45 | * We know we're reading valid memory, but we still must guard against | |
46 | * running out of memory. | |
47 | */ | |
48 | if (__copy_from_user(buf, src, n)) | |
49 | return -EFAULT; | |
50 | ||
51 | if (copy_to_user(dst, buf, n)) | |
52 | return -EFAULT; | |
53 | ||
54 | src += n; | |
55 | dst += n; | |
56 | bytes_left -= n; | |
57 | } | |
58 | return bytecount; | |
59 | } | |
60 | ||
61 | static int | |
62 | read_default_ldt (void __user * ptr, unsigned long bytecount) | |
63 | { | |
64 | unsigned long size; | |
65 | int err; | |
66 | ||
67 | /* XXX fix me: should return equivalent of default_ldt[0] */ | |
68 | err = 0; | |
69 | size = 8; | |
70 | if (size > bytecount) | |
71 | size = bytecount; | |
72 | ||
73 | err = size; | |
74 | if (clear_user(ptr, size)) | |
75 | err = -EFAULT; | |
76 | ||
77 | return err; | |
78 | } | |
79 | ||
80 | static int | |
81 | write_ldt (void __user * ptr, unsigned long bytecount, int oldmode) | |
82 | { | |
83 | struct ia32_user_desc ldt_info; | |
84 | __u64 entry; | |
85 | int ret; | |
86 | ||
87 | if (bytecount != sizeof(ldt_info)) | |
88 | return -EINVAL; | |
89 | if (copy_from_user(&ldt_info, ptr, sizeof(ldt_info))) | |
90 | return -EFAULT; | |
91 | ||
92 | if (ldt_info.entry_number >= IA32_LDT_ENTRIES) | |
93 | return -EINVAL; | |
94 | if (ldt_info.contents == 3) { | |
95 | if (oldmode) | |
96 | return -EINVAL; | |
97 | if (ldt_info.seg_not_present == 0) | |
98 | return -EINVAL; | |
99 | } | |
100 | ||
101 | if (ldt_info.base_addr == 0 && ldt_info.limit == 0 | |
102 | && (oldmode || (ldt_info.contents == 0 && ldt_info.read_exec_only == 1 | |
103 | && ldt_info.seg_32bit == 0 && ldt_info.limit_in_pages == 0 | |
104 | && ldt_info.seg_not_present == 1 && ldt_info.useable == 0))) | |
105 | /* allow LDTs to be cleared by the user */ | |
106 | entry = 0; | |
107 | else | |
108 | /* we must set the "Accessed" bit as IVE doesn't emulate it */ | |
109 | entry = IA32_SEG_DESCRIPTOR(ldt_info.base_addr, ldt_info.limit, | |
110 | (((ldt_info.read_exec_only ^ 1) << 1) | |
111 | | (ldt_info.contents << 2)) | 1, | |
112 | 1, 3, ldt_info.seg_not_present ^ 1, | |
113 | (oldmode ? 0 : ldt_info.useable), | |
114 | ldt_info.seg_32bit, | |
115 | ldt_info.limit_in_pages); | |
116 | /* | |
117 | * Install the new entry. We know we're accessing valid (mapped) user-level | |
118 | * memory, but we still need to guard against out-of-memory, hence we must use | |
119 | * put_user(). | |
120 | */ | |
121 | ret = __put_user(entry, (__u64 __user *) IA32_LDT_OFFSET + ldt_info.entry_number); | |
122 | ia32_load_segment_descriptors(current); | |
123 | return ret; | |
124 | } | |
125 | ||
126 | asmlinkage int | |
127 | sys32_modify_ldt (int func, unsigned int ptr, unsigned int bytecount) | |
128 | { | |
129 | int ret = -ENOSYS; | |
130 | ||
131 | switch (func) { | |
132 | case 0: | |
133 | ret = read_ldt(compat_ptr(ptr), bytecount); | |
134 | break; | |
135 | case 1: | |
136 | ret = write_ldt(compat_ptr(ptr), bytecount, 1); | |
137 | break; | |
138 | case 2: | |
139 | ret = read_default_ldt(compat_ptr(ptr), bytecount); | |
140 | break; | |
141 | case 0x11: | |
142 | ret = write_ldt(compat_ptr(ptr), bytecount, 0); | |
143 | break; | |
144 | } | |
145 | return ret; | |
146 | } |