Skip to content

Commit a5ca574

Browse files
committed
Merge tag 'vfs-6.13.usercopy' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull copy_struct_to_user helper from Christian Brauner: "This adds a copy_struct_to_user() helper which is a companion helper to the already widely used copy_struct_from_user(). It copies a struct from kernel space to userspace, in a way that guarantees backwards-compatibility for struct syscall arguments as long as future struct extensions are made such that all new fields are appended to the old struct, and zeroed-out new fields have the same meaning as the old struct. The first user is sched_getattr() system call but the new extensible pidfs ioctl will be ported to it as well" * tag 'vfs-6.13.usercopy' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: sched_getattr: port to copy_struct_to_user uaccess: add copy_struct_to_user helper
2 parents 909d3b5 + 112cca0 commit a5ca574

File tree

2 files changed

+99
-40
lines changed

2 files changed

+99
-40
lines changed

include/linux/uaccess.h

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,103 @@ copy_struct_from_user(void *dst, size_t ksize, const void __user *src,
403403
return 0;
404404
}
405405

406+
/**
407+
* copy_struct_to_user: copy a struct to userspace
408+
* @dst: Destination address, in userspace. This buffer must be @ksize
409+
* bytes long.
410+
* @usize: (Alleged) size of @dst struct.
411+
* @src: Source address, in kernel space.
412+
* @ksize: Size of @src struct.
413+
* @ignored_trailing: Set to %true if there was a non-zero byte in @src that
414+
* userspace cannot see because they are using an smaller struct.
415+
*
416+
* Copies a struct from kernel space to userspace, in a way that guarantees
417+
* backwards-compatibility for struct syscall arguments (as long as future
418+
* struct extensions are made such that all new fields are *appended* to the
419+
* old struct, and zeroed-out new fields have the same meaning as the old
420+
* struct).
421+
*
422+
* Some syscalls may wish to make sure that userspace knows about everything in
423+
* the struct, and if there is a non-zero value that userspce doesn't know
424+
* about, they want to return an error (such as -EMSGSIZE) or have some other
425+
* fallback (such as adding a "you're missing some information" flag). If
426+
* @ignored_trailing is non-%NULL, it will be set to %true if there was a
427+
* non-zero byte that could not be copied to userspace (ie. was past @usize).
428+
*
429+
* While unconditionally returning an error in this case is the simplest
430+
* solution, for maximum backward compatibility you should try to only return
431+
* -EMSGSIZE if the user explicitly requested the data that couldn't be copied.
432+
* Note that structure sizes can change due to header changes and simple
433+
* recompilations without code changes(!), so if you care about
434+
* @ignored_trailing you probably want to make sure that any new field data is
435+
* associated with a flag. Otherwise you might assume that a program knows
436+
* about data it does not.
437+
*
438+
* @ksize is just sizeof(*src), and @usize should've been passed by userspace.
439+
* The recommended usage is something like the following:
440+
*
441+
* SYSCALL_DEFINE2(foobar, struct foo __user *, uarg, size_t, usize)
442+
* {
443+
* int err;
444+
* bool ignored_trailing;
445+
* struct foo karg = {};
446+
*
447+
* if (usize > PAGE_SIZE)
448+
* return -E2BIG;
449+
* if (usize < FOO_SIZE_VER0)
450+
* return -EINVAL;
451+
*
452+
* // ... modify karg somehow ...
453+
*
454+
* err = copy_struct_to_user(uarg, usize, &karg, sizeof(karg),
455+
* &ignored_trailing);
456+
* if (err)
457+
* return err;
458+
* if (ignored_trailing)
459+
* return -EMSGSIZE:
460+
*
461+
* // ...
462+
* }
463+
*
464+
* There are three cases to consider:
465+
* * If @usize == @ksize, then it's copied verbatim.
466+
* * If @usize < @ksize, then the kernel is trying to pass userspace a newer
467+
* struct than it supports. Thus we only copy the interoperable portions
468+
* (@usize) and ignore the rest (but @ignored_trailing is set to %true if
469+
* any of the trailing (@ksize - @usize) bytes are non-zero).
470+
* * If @usize > @ksize, then the kernel is trying to pass userspace an older
471+
* struct than userspace supports. In order to make sure the
472+
* unknown-to-the-kernel fields don't contain garbage values, we zero the
473+
* trailing (@usize - @ksize) bytes.
474+
*
475+
* Returns (in all cases, some data may have been copied):
476+
* * -EFAULT: access to userspace failed.
477+
*/
478+
static __always_inline __must_check int
479+
copy_struct_to_user(void __user *dst, size_t usize, const void *src,
480+
size_t ksize, bool *ignored_trailing)
481+
{
482+
size_t size = min(ksize, usize);
483+
size_t rest = max(ksize, usize) - size;
484+
485+
/* Double check if ksize is larger than a known object size. */
486+
if (WARN_ON_ONCE(ksize > __builtin_object_size(src, 1)))
487+
return -E2BIG;
488+
489+
/* Deal with trailing bytes. */
490+
if (usize > ksize) {
491+
if (clear_user(dst + size, rest))
492+
return -EFAULT;
493+
}
494+
if (ignored_trailing)
495+
*ignored_trailing = ksize < usize &&
496+
memchr_inv(src + size, 0, rest) != NULL;
497+
/* Copy the interoperable parts of the struct. */
498+
if (copy_to_user(dst, src, size))
499+
return -EFAULT;
500+
return 0;
501+
}
502+
406503
bool copy_from_kernel_nofault_allowed(const void *unsafe_src, size_t size);
407504

408505
long copy_from_kernel_nofault(void *dst, const void *src, size_t size);

kernel/sched/syscalls.c

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,45 +1081,6 @@ SYSCALL_DEFINE2(sched_getparam, pid_t, pid, struct sched_param __user *, param)
10811081
return copy_to_user(param, &lp, sizeof(*param)) ? -EFAULT : 0;
10821082
}
10831083

1084-
/*
1085-
* Copy the kernel size attribute structure (which might be larger
1086-
* than what user-space knows about) to user-space.
1087-
*
1088-
* Note that all cases are valid: user-space buffer can be larger or
1089-
* smaller than the kernel-space buffer. The usual case is that both
1090-
* have the same size.
1091-
*/
1092-
static int
1093-
sched_attr_copy_to_user(struct sched_attr __user *uattr,
1094-
struct sched_attr *kattr,
1095-
unsigned int usize)
1096-
{
1097-
unsigned int ksize = sizeof(*kattr);
1098-
1099-
if (!access_ok(uattr, usize))
1100-
return -EFAULT;
1101-
1102-
/*
1103-
* sched_getattr() ABI forwards and backwards compatibility:
1104-
*
1105-
* If usize == ksize then we just copy everything to user-space and all is good.
1106-
*
1107-
* If usize < ksize then we only copy as much as user-space has space for,
1108-
* this keeps ABI compatibility as well. We skip the rest.
1109-
*
1110-
* If usize > ksize then user-space is using a newer version of the ABI,
1111-
* which part the kernel doesn't know about. Just ignore it - tooling can
1112-
* detect the kernel's knowledge of attributes from the attr->size value
1113-
* which is set to ksize in this case.
1114-
*/
1115-
kattr->size = min(usize, ksize);
1116-
1117-
if (copy_to_user(uattr, kattr, kattr->size))
1118-
return -EFAULT;
1119-
1120-
return 0;
1121-
}
1122-
11231084
/**
11241085
* sys_sched_getattr - similar to sched_getparam, but with sched_attr
11251086
* @pid: the pid in question.
@@ -1164,7 +1125,8 @@ SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr,
11641125
#endif
11651126
}
11661127

1167-
return sched_attr_copy_to_user(uattr, &kattr, usize);
1128+
kattr.size = min(usize, sizeof(kattr));
1129+
return copy_struct_to_user(uattr, usize, &kattr, sizeof(kattr), NULL);
11681130
}
11691131

11701132
#ifdef CONFIG_SMP

0 commit comments

Comments
 (0)