From: Mathieu Desnoyers Date: Thu, 24 Mar 2022 15:10:06 +0000 (-0400) Subject: Introduce wrapper lttng_copy_struct_from_user X-Git-Url: http://git.lttng.org./?a=commitdiff_plain;h=148f58fe47175d56d610a4461ddeb79b7d766b29;p=lttng-modules.git Introduce wrapper lttng_copy_struct_from_user Signed-off-by: Mathieu Desnoyers Change-Id: I67e3d0249bbfdb5bc6e9d469119220a1fe3f03fd --- diff --git a/include/wrapper/uaccess.h b/include/wrapper/uaccess.h index 4ccb26a0..eb1e4b20 100644 --- a/include/wrapper/uaccess.h +++ b/include/wrapper/uaccess.h @@ -26,4 +26,88 @@ #endif /* LTTNG_LINUX_VERSION_CODE >= LTTNG_KERNEL_VERSION(5,0,0) */ +#if LTTNG_LINUX_VERSION_CODE >= LTTNG_KERNEL_VERSION(5,4,0) +static __always_inline __must_check int +lttng_copy_struct_from_user(void *dst, size_t ksize, const void __user *src, + size_t usize) +{ + return copy_struct_from_user(dst, ksize, src, usize); +} +#else /* LTTNG_LINUX_VERSION_CODE >= LTTNG_KERNEL_VERSION(5,4,0) */ +/** + * lttng_check_zeroed_user: check if a userspace buffer only contains zero bytes + * @from: Source address, in userspace. + * @size: Size of buffer. + * + * This is effectively shorthand for "memchr_inv(from, 0, size) == NULL" for + * userspace addresses (and is more efficient because we don't care where the + * first non-zero byte is). + * + * Returns: + * * 0: There were non-zero bytes present in the buffer. + * * 1: The buffer was full of zero bytes. + * * -EFAULT: access to userspace failed. + */ +int lttng_check_zeroed_user(const void __user *from, size_t size) +{ + unsigned long val; + uintptr_t align = (uintptr_t) from % sizeof(unsigned long); + int ret; + + if (unlikely(size == 0)) + return 1; + + from -= align; + size += align; + + if (!lttng_access_ok(VERIFY_READ, from, size) + return -EFAULT; + + ret = get_user(val, (unsigned long __user *) from); + if (ret) + return ret; + if (align) + val &= ~aligned_byte_mask(align); + + while (size > sizeof(unsigned long)) { + if (unlikely(val)) + goto done; + + from += sizeof(unsigned long); + size -= sizeof(unsigned long); + + ret = get_user(val, (unsigned long __user *) from); + if (ret) + return ret; + } + + if (size < sizeof(unsigned long)) + val &= aligned_byte_mask(size); + +done: + return (val == 0); +} + +static __always_inline __must_check int +lttng_copy_struct_from_user(void *dst, size_t ksize, const void __user *src, + size_t usize) +{ + size_t size = min(ksize, usize); + size_t rest = max(ksize, usize) - size; + + /* Deal with trailing bytes. */ + if (usize < ksize) { + memset(dst + size, 0, rest); + } else if (usize > ksize) { + int ret = lttng_check_zeroed_user(src + size, rest); + if (ret <= 0) + return ret ?: -E2BIG; + } + /* Copy the interoperable parts of the struct. */ + if (copy_from_user(dst, src, size)) + return -EFAULT; + return 0; +} +#endif /* LTTNG_LINUX_VERSION_CODE >= LTTNG_KERNEL_VERSION(5,4,0) */ + #endif /* _LTTNG_WRAPPER_UACCESS_H */