diff options
| author | Masami Hiramatsu <mhiramat@kernel.org> | 2019-05-14 21:38:18 -0800 |
|---|---|---|
| committer | Steven Rostedt (VMware) <rostedt@goodmis.org> | 2019-05-25 19:04:42 -0800 |
| commit | 3d7081822f7f9eab867d9bcc8fd635208ec438e0 (patch) | |
| tree | 8afb5e61c1f23305996d4a8e03dedd7c048de01d /mm | |
| parent | 2d8d8fac3b4eab035dcd0068e1f5a746a697fbb3 (diff) | |
| download | linux-stable-3d7081822f7f9eab867d9bcc8fd635208ec438e0.tar.gz linux-stable-3d7081822f7f9eab867d9bcc8fd635208ec438e0.tar.bz2 linux-stable-3d7081822f7f9eab867d9bcc8fd635208ec438e0.tar.xz linux-stable-3d7081822f7f9eab867d9bcc8fd635208ec438e0.zip | |
uaccess: Add non-pagefault user-space read functions
Add probe_user_read(), strncpy_from_unsafe_user() and
strnlen_unsafe_user() which allows caller to access user-space
in IRQ context.
Current probe_kernel_read() and strncpy_from_unsafe() are
not available for user-space memory, because it sets
KERNEL_DS while accessing data. On some arch, user address
space and kernel address space can be co-exist, but others
can not. In that case, setting KERNEL_DS means given
address is treated as a kernel address space.
Also strnlen_user() is only available from user context since
it can sleep if pagefault is enabled.
To access user-space memory without pagefault, we need
these new functions which sets USER_DS while accessing
the data.
Link: http://lkml.kernel.org/r/155789869802.26965.4940338412595759063.stgit@devnote2
Acked-by: Ingo Molnar <mingo@kernel.org>
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
Diffstat (limited to 'mm')
| -rw-r--r-- | mm/maccess.c | 122 |
1 files changed, 116 insertions, 6 deletions
diff --git a/mm/maccess.c b/mm/maccess.c index ec00be51a24f..19c8c3dc14df 100644 --- a/mm/maccess.c +++ b/mm/maccess.c | |||
| @@ -5,8 +5,20 @@ | |||
| 5 | #include <linux/mm.h> | 5 | #include <linux/mm.h> |
| 6 | #include <linux/uaccess.h> | 6 | #include <linux/uaccess.h> |
| 7 | 7 | ||
| 8 | static __always_inline long | ||
| 9 | probe_read_common(void *dst, const void __user *src, size_t size) | ||
| 10 | { | ||
| 11 | long ret; | ||
| 12 | |||
| 13 | pagefault_disable(); | ||
| 14 | ret = __copy_from_user_inatomic(dst, src, size); | ||
| 15 | pagefault_enable(); | ||
| 16 | |||
| 17 | return ret ? -EFAULT : 0; | ||
| 18 | } | ||
| 19 | |||
| 8 | /** | 20 | /** |
| 9 | * probe_kernel_read(): safely attempt to read from a location | 21 | * probe_kernel_read(): safely attempt to read from a kernel-space location |
| 10 | * @dst: pointer to the buffer that shall take the data | 22 | * @dst: pointer to the buffer that shall take the data |
| 11 | * @src: address to read from | 23 | * @src: address to read from |
| 12 | * @size: size of the data chunk | 24 | * @size: size of the data chunk |
| @@ -29,17 +41,41 @@ long __probe_kernel_read(void *dst, const void *src, size_t size) | |||
| 29 | mm_segment_t old_fs = get_fs(); | 41 | mm_segment_t old_fs = get_fs(); |
| 30 | 42 | ||
| 31 | set_fs(KERNEL_DS); | 43 | set_fs(KERNEL_DS); |
| 32 | pagefault_disable(); | 44 | ret = probe_read_common(dst, (__force const void __user *)src, size); |
| 33 | ret = __copy_from_user_inatomic(dst, | ||
| 34 | (__force const void __user *)src, size); | ||
| 35 | pagefault_enable(); | ||
| 36 | set_fs(old_fs); | 45 | set_fs(old_fs); |
| 37 | 46 | ||
| 38 | return ret ? -EFAULT : 0; | 47 | return ret; |
| 39 | } | 48 | } |
| 40 | EXPORT_SYMBOL_GPL(probe_kernel_read); | 49 | EXPORT_SYMBOL_GPL(probe_kernel_read); |
| 41 | 50 | ||
| 42 | /** | 51 | /** |
| 52 | * probe_user_read(): safely attempt to read from a user-space location | ||
| 53 | * @dst: pointer to the buffer that shall take the data | ||
| 54 | * @src: address to read from. This must be a user address. | ||
| 55 | * @size: size of the data chunk | ||
| 56 | * | ||
| 57 | * Safely read from user address @src to the buffer at @dst. If a kernel fault | ||
| 58 | * happens, handle that and return -EFAULT. | ||
| 59 | */ | ||
| 60 | |||
| 61 | long __weak probe_user_read(void *dst, const void __user *src, size_t size) | ||
| 62 | __attribute__((alias("__probe_user_read"))); | ||
| 63 | |||
| 64 | long __probe_user_read(void *dst, const void __user *src, size_t size) | ||
| 65 | { | ||
| 66 | long ret = -EFAULT; | ||
| 67 | mm_segment_t old_fs = get_fs(); | ||
| 68 | |||
| 69 | set_fs(USER_DS); | ||
| 70 | if (access_ok(src, size)) | ||
| 71 | ret = probe_read_common(dst, src, size); | ||
| 72 | set_fs(old_fs); | ||
| 73 | |||
| 74 | return ret; | ||
| 75 | } | ||
| 76 | EXPORT_SYMBOL_GPL(probe_user_read); | ||
| 77 | |||
| 78 | /** | ||
| 43 | * probe_kernel_write(): safely attempt to write to a location | 79 | * probe_kernel_write(): safely attempt to write to a location |
| 44 | * @dst: address to write to | 80 | * @dst: address to write to |
| 45 | * @src: pointer to the data that shall be written | 81 | * @src: pointer to the data that shall be written |
| @@ -66,6 +102,7 @@ long __probe_kernel_write(void *dst, const void *src, size_t size) | |||
| 66 | } | 102 | } |
| 67 | EXPORT_SYMBOL_GPL(probe_kernel_write); | 103 | EXPORT_SYMBOL_GPL(probe_kernel_write); |
| 68 | 104 | ||
| 105 | |||
| 69 | /** | 106 | /** |
| 70 | * strncpy_from_unsafe: - Copy a NUL terminated string from unsafe address. | 107 | * strncpy_from_unsafe: - Copy a NUL terminated string from unsafe address. |
| 71 | * @dst: Destination address, in kernel space. This buffer must be at | 108 | * @dst: Destination address, in kernel space. This buffer must be at |
| @@ -105,3 +142,76 @@ long strncpy_from_unsafe(char *dst, const void *unsafe_addr, long count) | |||
| 105 | 142 | ||
| 106 | return ret ? -EFAULT : src - unsafe_addr; | 143 | return ret ? -EFAULT : src - unsafe_addr; |
| 107 | } | 144 | } |
| 145 | |||
| 146 | /** | ||
| 147 | * strncpy_from_unsafe_user: - Copy a NUL terminated string from unsafe user | ||
| 148 | * address. | ||
| 149 | * @dst: Destination address, in kernel space. This buffer must be at | ||
| 150 | * least @count bytes long. | ||
| 151 | * @unsafe_addr: Unsafe user address. | ||
| 152 | * @count: Maximum number of bytes to copy, including the trailing NUL. | ||
| 153 | * | ||
| 154 | * Copies a NUL-terminated string from unsafe user address to kernel buffer. | ||
| 155 | * | ||
| 156 | * On success, returns the length of the string INCLUDING the trailing NUL. | ||
| 157 | * | ||
| 158 | * If access fails, returns -EFAULT (some data may have been copied | ||
| 159 | * and the trailing NUL added). | ||
| 160 | * | ||
| 161 | * If @count is smaller than the length of the string, copies @count-1 bytes, | ||
| 162 | * sets the last byte of @dst buffer to NUL and returns @count. | ||
| 163 | */ | ||
| 164 | long strncpy_from_unsafe_user(char *dst, const void __user *unsafe_addr, | ||
| 165 | long count) | ||
| 166 | { | ||
| 167 | mm_segment_t old_fs = get_fs(); | ||
| 168 | long ret; | ||
| 169 | |||
| 170 | if (unlikely(count <= 0)) | ||
| 171 | return 0; | ||
| 172 | |||
| 173 | set_fs(USER_DS); | ||
| 174 | pagefault_disable(); | ||
| 175 | ret = strncpy_from_user(dst, unsafe_addr, count); | ||
| 176 | pagefault_enable(); | ||
| 177 | set_fs(old_fs); | ||
| 178 | |||
| 179 | if (ret >= count) { | ||
| 180 | ret = count; | ||
| 181 | dst[ret - 1] = '\0'; | ||
| 182 | } else if (ret > 0) { | ||
| 183 | ret++; | ||
| 184 | } | ||
| 185 | |||
| 186 | return ret; | ||
| 187 | } | ||
| 188 | |||
| 189 | /** | ||
| 190 | * strnlen_unsafe_user: - Get the size of a user string INCLUDING final NUL. | ||
| 191 | * @unsafe_addr: The string to measure. | ||
| 192 | * @count: Maximum count (including NUL) | ||
| 193 | * | ||
| 194 | * Get the size of a NUL-terminated string in user space without pagefault. | ||
| 195 | * | ||
| 196 | * Returns the size of the string INCLUDING the terminating NUL. | ||
| 197 | * | ||
| 198 | * If the string is too long, returns a number larger than @count. User | ||
| 199 | * has to check the return value against "> count". | ||
| 200 | * On exception (or invalid count), returns 0. | ||
| 201 | * | ||
| 202 | * Unlike strnlen_user, this can be used from IRQ handler etc. because | ||
| 203 | * it disables pagefaults. | ||
| 204 | */ | ||
| 205 | long strnlen_unsafe_user(const void __user *unsafe_addr, long count) | ||
| 206 | { | ||
| 207 | mm_segment_t old_fs = get_fs(); | ||
| 208 | int ret; | ||
| 209 | |||
| 210 | set_fs(USER_DS); | ||
| 211 | pagefault_disable(); | ||
| 212 | ret = strnlen_user(unsafe_addr, count); | ||
| 213 | pagefault_enable(); | ||
| 214 | set_fs(old_fs); | ||
| 215 | |||
| 216 | return ret; | ||
| 217 | } | ||
