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