1 /* 2 * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) 3 * Licensed under the GPL 4 */ 5 6 #include "linux/err.h" 7 #include "linux/highmem.h" 8 #include "linux/mm.h" 9 #include "asm/current.h" 10 #include "asm/page.h" 11 #include "asm/pgtable.h" 12 #include "kern_util.h" 13 #include "os.h" 14 15 extern void *um_virt_to_phys(struct task_struct *task, unsigned long addr, 16 pte_t *pte_out); 17 18 static unsigned long maybe_map(unsigned long virt, int is_write) 19 { 20 pte_t pte; 21 int err; 22 23 void *phys = um_virt_to_phys(current, virt, &pte); 24 int dummy_code; 25 26 if (IS_ERR(phys) || (is_write && !pte_write(pte))) { 27 err = handle_page_fault(virt, 0, is_write, 1, &dummy_code); 28 if (err) 29 return -1UL; 30 phys = um_virt_to_phys(current, virt, NULL); 31 } 32 if (IS_ERR(phys)) 33 phys = (void *) -1; 34 35 return (unsigned long) phys; 36 } 37 38 static int do_op_one_page(unsigned long addr, int len, int is_write, 39 int (*op)(unsigned long addr, int len, void *arg), void *arg) 40 { 41 struct page *page; 42 int n; 43 44 addr = maybe_map(addr, is_write); 45 if (addr == -1UL) 46 return -1; 47 48 page = phys_to_page(addr); 49 addr = (unsigned long) kmap_atomic(page, KM_UML_USERCOPY) + 50 (addr & ~PAGE_MASK); 51 52 n = (*op)(addr, len, arg); 53 54 kunmap_atomic(page, KM_UML_USERCOPY); 55 56 return n; 57 } 58 59 static void do_buffer_op(void *jmpbuf, void *arg_ptr) 60 { 61 va_list args; 62 unsigned long addr; 63 int len, is_write, size, remain, n; 64 int (*op)(unsigned long, int, void *); 65 void *arg; 66 int *res; 67 68 va_copy(args, *(va_list *)arg_ptr); 69 addr = va_arg(args, unsigned long); 70 len = va_arg(args, int); 71 is_write = va_arg(args, int); 72 op = va_arg(args, void *); 73 arg = va_arg(args, void *); 74 res = va_arg(args, int *); 75 va_end(args); 76 size = min(PAGE_ALIGN(addr) - addr, (unsigned long) len); 77 remain = len; 78 79 current->thread.fault_catcher = jmpbuf; 80 n = do_op_one_page(addr, size, is_write, op, arg); 81 if (n != 0) { 82 *res = (n < 0 ? remain : 0); 83 goto out; 84 } 85 86 addr += size; 87 remain -= size; 88 if (remain == 0) { 89 *res = 0; 90 goto out; 91 } 92 93 while(addr < ((addr + remain) & PAGE_MASK)) { 94 n = do_op_one_page(addr, PAGE_SIZE, is_write, op, arg); 95 if (n != 0) { 96 *res = (n < 0 ? remain : 0); 97 goto out; 98 } 99 100 addr += PAGE_SIZE; 101 remain -= PAGE_SIZE; 102 } 103 if (remain == 0) { 104 *res = 0; 105 goto out; 106 } 107 108 n = do_op_one_page(addr, remain, is_write, op, arg); 109 if (n != 0) 110 *res = (n < 0 ? remain : 0); 111 else *res = 0; 112 out: 113 current->thread.fault_catcher = NULL; 114 } 115 116 static int buffer_op(unsigned long addr, int len, int is_write, 117 int (*op)(unsigned long addr, int len, void *arg), 118 void *arg) 119 { 120 int faulted, res; 121 122 faulted = setjmp_wrapper(do_buffer_op, addr, len, is_write, op, arg, 123 &res); 124 if (!faulted) 125 return res; 126 127 return addr + len - (unsigned long) current->thread.fault_addr; 128 } 129 130 static int copy_chunk_from_user(unsigned long from, int len, void *arg) 131 { 132 unsigned long *to_ptr = arg, to = *to_ptr; 133 134 memcpy((void *) to, (void *) from, len); 135 *to_ptr += len; 136 return 0; 137 } 138 139 int copy_from_user(void *to, const void __user *from, int n) 140 { 141 if (segment_eq(get_fs(), KERNEL_DS)) { 142 memcpy(to, (__force void*)from, n); 143 return 0; 144 } 145 146 return access_ok(VERIFY_READ, from, n) ? 147 buffer_op((unsigned long) from, n, 0, copy_chunk_from_user, &to): 148 n; 149 } 150 151 static int copy_chunk_to_user(unsigned long to, int len, void *arg) 152 { 153 unsigned long *from_ptr = arg, from = *from_ptr; 154 155 memcpy((void *) to, (void *) from, len); 156 *from_ptr += len; 157 return 0; 158 } 159 160 int copy_to_user(void __user *to, const void *from, int n) 161 { 162 if (segment_eq(get_fs(), KERNEL_DS)) { 163 memcpy((__force void *) to, from, n); 164 return 0; 165 } 166 167 return access_ok(VERIFY_WRITE, to, n) ? 168 buffer_op((unsigned long) to, n, 1, copy_chunk_to_user, &from) : 169 n; 170 } 171 172 static int strncpy_chunk_from_user(unsigned long from, int len, void *arg) 173 { 174 char **to_ptr = arg, *to = *to_ptr; 175 int n; 176 177 strncpy(to, (void *) from, len); 178 n = strnlen(to, len); 179 *to_ptr += n; 180 181 if (n < len) 182 return 1; 183 return 0; 184 } 185 186 int strncpy_from_user(char *dst, const char __user *src, int count) 187 { 188 int n; 189 char *ptr = dst; 190 191 if (segment_eq(get_fs(), KERNEL_DS)) { 192 strncpy(dst, (__force void *) src, count); 193 return strnlen(dst, count); 194 } 195 196 if (!access_ok(VERIFY_READ, src, 1)) 197 return -EFAULT; 198 199 n = buffer_op((unsigned long) src, count, 0, strncpy_chunk_from_user, 200 &ptr); 201 if (n != 0) 202 return -EFAULT; 203 return strnlen(dst, count); 204 } 205 206 static int clear_chunk(unsigned long addr, int len, void *unused) 207 { 208 memset((void *) addr, 0, len); 209 return 0; 210 } 211 212 int __clear_user(void __user *mem, int len) 213 { 214 return buffer_op((unsigned long) mem, len, 1, clear_chunk, NULL); 215 } 216 217 int clear_user(void __user *mem, int len) 218 { 219 if (segment_eq(get_fs(), KERNEL_DS)) { 220 memset((__force void*)mem, 0, len); 221 return 0; 222 } 223 224 return access_ok(VERIFY_WRITE, mem, len) ? 225 buffer_op((unsigned long) mem, len, 1, clear_chunk, NULL) : len; 226 } 227 228 static int strnlen_chunk(unsigned long str, int len, void *arg) 229 { 230 int *len_ptr = arg, n; 231 232 n = strnlen((void *) str, len); 233 *len_ptr += n; 234 235 if (n < len) 236 return 1; 237 return 0; 238 } 239 240 int strnlen_user(const void __user *str, int len) 241 { 242 int count = 0, n; 243 244 if (segment_eq(get_fs(), KERNEL_DS)) 245 return strnlen((__force char*)str, len) + 1; 246 247 n = buffer_op((unsigned long) str, len, 0, strnlen_chunk, &count); 248 if (n == 0) 249 return count + 1; 250 return -EFAULT; 251 } 252