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