mirror of
https://github.com/torvalds/linux.git
synced 2024-12-30 14:52:05 +00:00
f5a1a536fa
A common pattern for syscall extensions is increasing the size of a
struct passed from userspace, such that the zero-value of the new fields
result in the old kernel behaviour (allowing for a mix of userspace and
kernel vintages to operate on one another in most cases).
While this interface exists for communication in both directions, only
one interface is straightforward to have reasonable semantics for
(userspace passing a struct to the kernel). For kernel returns to
userspace, what the correct semantics are (whether there should be an
error if userspace is unaware of a new extension) is very
syscall-dependent and thus probably cannot be unified between syscalls
(a good example of this problem is [1]).
Previously there was no common lib/ function that implemented
the necessary extension-checking semantics (and different syscalls
implemented them slightly differently or incompletely[2]). Future
patches replace common uses of this pattern to make use of
copy_struct_from_user().
Some in-kernel selftests that insure that the handling of alignment and
various byte patterns are all handled identically to memchr_inv() usage.
[1]: commit 1251201c0d
("sched/core: Fix uclamp ABI bug, clean up and
robustify sched_read_attr() ABI logic and code")
[2]: For instance {sched_setattr,perf_event_open,clone3}(2) all do do
similar checks to copy_struct_from_user() while rt_sigprocmask(2)
always rejects differently-sized struct arguments.
Suggested-by: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Christian Brauner <christian.brauner@ubuntu.com>
Link: https://lore.kernel.org/r/20191001011055.19283-2-cyphar@cyphar.com
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
121 lines
3.3 KiB
C
121 lines
3.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/kernel.h>
|
|
#include <linux/export.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/bitops.h>
|
|
|
|
#include <asm/word-at-a-time.h>
|
|
|
|
/*
|
|
* Do a strnlen, return length of string *with* final '\0'.
|
|
* 'count' is the user-supplied count, while 'max' is the
|
|
* address space maximum.
|
|
*
|
|
* Return 0 for exceptions (which includes hitting the address
|
|
* space maximum), or 'count+1' if hitting the user-supplied
|
|
* maximum count.
|
|
*
|
|
* NOTE! We can sometimes overshoot the user-supplied maximum
|
|
* if it fits in a aligned 'long'. The caller needs to check
|
|
* the return value against "> max".
|
|
*/
|
|
static inline long do_strnlen_user(const char __user *src, unsigned long count, unsigned long max)
|
|
{
|
|
const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS;
|
|
unsigned long align, res = 0;
|
|
unsigned long c;
|
|
|
|
/*
|
|
* Truncate 'max' to the user-specified limit, so that
|
|
* we only have one limit we need to check in the loop
|
|
*/
|
|
if (max > count)
|
|
max = count;
|
|
|
|
/*
|
|
* Do everything aligned. But that means that we
|
|
* need to also expand the maximum..
|
|
*/
|
|
align = (sizeof(unsigned long) - 1) & (unsigned long)src;
|
|
src -= align;
|
|
max += align;
|
|
|
|
unsafe_get_user(c, (unsigned long __user *)src, efault);
|
|
c |= aligned_byte_mask(align);
|
|
|
|
for (;;) {
|
|
unsigned long data;
|
|
if (has_zero(c, &data, &constants)) {
|
|
data = prep_zero_mask(c, data, &constants);
|
|
data = create_zero_mask(data);
|
|
return res + find_zero(data) + 1 - align;
|
|
}
|
|
res += sizeof(unsigned long);
|
|
/* We already handled 'unsigned long' bytes. Did we do it all ? */
|
|
if (unlikely(max <= sizeof(unsigned long)))
|
|
break;
|
|
max -= sizeof(unsigned long);
|
|
unsafe_get_user(c, (unsigned long __user *)(src+res), efault);
|
|
}
|
|
res -= align;
|
|
|
|
/*
|
|
* Uhhuh. We hit 'max'. But was that the user-specified maximum
|
|
* too? If so, return the marker for "too long".
|
|
*/
|
|
if (res >= count)
|
|
return count+1;
|
|
|
|
/*
|
|
* Nope: we hit the address space limit, and we still had more
|
|
* characters the caller would have wanted. That's 0.
|
|
*/
|
|
efault:
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* strnlen_user: - Get the size of a user string INCLUDING final NUL.
|
|
* @str: The string to measure.
|
|
* @count: Maximum count (including NUL character)
|
|
*
|
|
* Context: User context only. This function may sleep if pagefaults are
|
|
* enabled.
|
|
*
|
|
* Get the size of a NUL-terminated string in user space.
|
|
*
|
|
* Returns the size of the string INCLUDING the terminating NUL.
|
|
* If the string is too long, returns a number larger than @count. User
|
|
* has to check the return value against "> count".
|
|
* On exception (or invalid count), returns 0.
|
|
*
|
|
* NOTE! You should basically never use this function. There is
|
|
* almost never any valid case for using the length of a user space
|
|
* string, since the string can be changed at any time by other
|
|
* threads. Use "strncpy_from_user()" instead to get a stable copy
|
|
* of the string.
|
|
*/
|
|
long strnlen_user(const char __user *str, long count)
|
|
{
|
|
unsigned long max_addr, src_addr;
|
|
|
|
if (unlikely(count <= 0))
|
|
return 0;
|
|
|
|
max_addr = user_addr_max();
|
|
src_addr = (unsigned long)untagged_addr(str);
|
|
if (likely(src_addr < max_addr)) {
|
|
unsigned long max = max_addr - src_addr;
|
|
long retval;
|
|
|
|
if (user_access_begin(str, max)) {
|
|
retval = do_strnlen_user(str, count, max);
|
|
user_access_end();
|
|
return retval;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(strnlen_user);
|