diff options
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/Makefile | 1 | ||||
| -rw-r--r-- | kernel/bpf/bpf_iter.c | 6 | ||||
| -rw-r--r-- | kernel/bpf/inode.c | 6 | ||||
| -rw-r--r-- | kernel/bpf/token.c | 6 | ||||
| -rw-r--r-- | kernel/capability.c | 4 | ||||
| -rw-r--r-- | kernel/exit.c | 15 | ||||
| -rw-r--r-- | kernel/fork.c | 68 | ||||
| -rw-r--r-- | kernel/kthread.c | 2 | ||||
| -rw-r--r-- | kernel/pid_namespace.c | 3 | ||||
| -rw-r--r-- | kernel/ptrace.c | 6 | ||||
| -rw-r--r-- | kernel/signal.c | 14 | ||||
| -rw-r--r-- | kernel/tests/.kunitconfig | 4 | ||||
| -rw-r--r-- | kernel/tests/user_ns_map_kunit.c | 98 | ||||
| -rw-r--r-- | kernel/user_namespace.c | 60 | ||||
| -rw-r--r-- | kernel/utsname.c | 1 |
15 files changed, 192 insertions, 102 deletions
diff --git a/kernel/Makefile b/kernel/Makefile index 1e1a31673577..2a64282749b8 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -141,6 +141,7 @@ obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o obj-$(CONFIG_RESOURCE_KUNIT_TEST) += resource_kunit.o obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o +obj-$(CONFIG_USER_NS_MAP_KUNIT_TEST) += tests/user_ns_map_kunit.o CFLAGS_kstack_erase.o += $(DISABLE_KSTACK_ERASE) CFLAGS_kstack_erase.o += $(call cc-option,-mgeneral-regs-only) diff --git a/kernel/bpf/bpf_iter.c b/kernel/bpf/bpf_iter.c index b40eb404adab..d9191df5de22 100644 --- a/kernel/bpf/bpf_iter.c +++ b/kernel/bpf/bpf_iter.c @@ -643,11 +643,11 @@ int bpf_iter_new_fd(struct bpf_link *link) flags = O_RDONLY | O_CLOEXEC; FD_PREPARE(fdf, flags, anon_inode_getfile("bpf_iter", &bpf_iter_fops, NULL, flags)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; iter_link = container_of(link, struct bpf_iter_link, link); - err = prepare_seq_file(fd_prepare_file(fdf), iter_link); + err = prepare_seq_file(fdf->file, iter_link); if (err) return err; /* Automatic cleanup handles fput */ diff --git a/kernel/bpf/inode.c b/kernel/bpf/inode.c index 7837968c0842..c6f328e4752e 100644 --- a/kernel/bpf/inode.c +++ b/kernel/bpf/inode.c @@ -176,7 +176,7 @@ static void bpf_dentry_finalize(struct dentry *dentry, struct inode *inode, inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); } -static struct dentry *bpf_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *bpf_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; @@ -424,7 +424,7 @@ bpf_lookup(struct inode *dir, struct dentry *dentry, unsigned flags) return simple_lookup(dir, dentry, flags); } -static int bpf_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int bpf_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *target) { struct inode *inode; @@ -874,7 +874,7 @@ enum { }; static int bpf_fs_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, struct dentry *unused, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) { diff --git a/kernel/bpf/token.c b/kernel/bpf/token.c index e85a179523f0..da915a4f972b 100644 --- a/kernel/bpf/token.c +++ b/kernel/bpf/token.c @@ -169,8 +169,8 @@ int bpf_token_create(union bpf_attr *attr) FD_PREPARE(fdf, O_CLOEXEC, alloc_file_pseudo(inode, path.mnt, BPF_TOKEN_INODE_NAME, O_RDWR, &bpf_token_fops)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; token = kzalloc_obj(*token, GFP_USER); if (!token) @@ -190,7 +190,7 @@ int bpf_token_create(union bpf_attr *attr) return err; get_user_ns(token->userns); - fd_prepare_file(fdf)->private_data = no_free_ptr(token); + fdf->file->private_data = no_free_ptr(token); return fd_publish(fdf); } diff --git a/kernel/capability.c b/kernel/capability.c index 90e6ab62f6db..a689dae590ff 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -470,7 +470,7 @@ EXPORT_SYMBOL(file_ns_capable); * Return true if the inode uid and gid are within the namespace. */ bool privileged_wrt_inode_uidgid(struct user_namespace *ns, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, const struct inode *inode) { return vfsuid_has_mapping(ns, i_uid_into_vfsuid(idmap, inode)) && @@ -487,7 +487,7 @@ bool privileged_wrt_inode_uidgid(struct user_namespace *ns, * its own user namespace and that the given inode's uid and gid are * mapped into the current user namespace. */ -bool capable_wrt_inode_uidgid(struct mnt_idmap *idmap, +bool capable_wrt_inode_uidgid(const struct mnt_idmap *idmap, const struct inode *inode, int cap) { struct user_namespace *ns = current_user_ns(); diff --git a/kernel/exit.c b/kernel/exit.c index 282328d2b4cf..29e853a36602 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -17,6 +17,7 @@ #include <linux/module.h> #include <linux/capability.h> #include <linux/completion.h> +#include <linux/wait_bit.h> #include <linux/personality.h> #include <linux/tty.h> #include <linux/iocontext.h> @@ -436,15 +437,14 @@ static void coredump_task_exit(struct task_struct *tsk, self.task = tsk; if (self.task->flags & PF_SIGNALED) - self.next = xchg(&core_state->dumper.next, &self); + self.next = xchg(&core_state->tasks, &self); else self.task = NULL; /* * Implies mb(), the result of xchg() must be visible - * to core_state->dumper. + * to the dumper. */ - if (atomic_dec_and_test(&core_state->nr_threads)) - complete(&core_state->startup); + atomic_dec_and_wake_up(&core_state->threads_remaining); for (;;) { set_current_state(TASK_IDLE|TASK_FREEZABLE); @@ -892,7 +892,7 @@ static void synchronize_group_exit(struct task_struct *tsk, long code) * Serialize with any possible pending coredump. * We must hold siglock around checking core_state * and setting PF_POSTCOREDUMP. The core-inducing thread - * will increment ->nr_threads for each thread in the + * will increment ->threads_remaining for each thread in the * group without PF_POSTCOREDUMP set. */ tsk->flags |= PF_POSTCOREDUMP; @@ -978,10 +978,11 @@ void __noreturn do_exit(long code) exit_sem(tsk); exit_shm(tsk); - exit_files(tsk); - exit_fs(tsk); + /* Hang the tty up before the last close of it can clear the session. */ if (group_dead) disassociate_ctty(1); + exit_files(tsk); + exit_fs(tsk); exit_nsproxy_namespaces(tsk); exit_task_work(tsk); exit_thread(tsk); diff --git a/kernel/fork.c b/kernel/fork.c index 10f2d05d816a..5a4cf4cf767a 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1677,6 +1677,7 @@ static int copy_files(u64 clone_flags, struct task_struct *tsk, if (clone_flags & CLONE_FILES) { atomic_inc(&oldf->count); + tsk->files = oldf; return 0; } @@ -2146,12 +2147,9 @@ __latent_entropy struct task_struct *copy_process( if (args->kthread) p->flags |= PF_KTHREAD; if (args->user_worker) { - /* - * Mark us a user worker, and block any signal that isn't - * fatal or STOP - */ + /* A user worker takes only the signals nobody can block. */ p->flags |= PF_USER_WORKER; - siginitsetinv(&p->blocked, sigmask(SIGKILL)|sigmask(SIGSTOP)); + siginitsetinv(&p->blocked, SIG_KERNEL_ONLY_MASK); } if (args->io_thread) p->flags |= PF_IO_WORKER; @@ -2200,6 +2198,8 @@ __latent_entropy struct task_struct *copy_process( INIT_LIST_HEAD(&p->sibling); rcu_copy_process(p); p->vfork_done = NULL; + /* Set by copy_files(), exit_files() on the error path skips NULL. */ + p->files = NULL; spin_lock_init(&p->alloc_lock); init_sigpending(&p->pending); @@ -2301,7 +2301,7 @@ __latent_entropy struct task_struct *copy_process( goto bad_fork_cleanup_semundo; retval = copy_fs(clone_flags, p, args->umh); if (retval) - goto bad_fork_cleanup_files; + goto bad_fork_cleanup_semundo; retval = copy_sighand(clone_flags, p); if (retval) goto bad_fork_cleanup_fs; @@ -2615,8 +2615,6 @@ bad_fork_cleanup_sighand: __cleanup_sighand(p->sighand); bad_fork_cleanup_fs: exit_fs(p); /* blocking */ -bad_fork_cleanup_files: - exit_files(p); /* blocking */ bad_fork_cleanup_semundo: exit_sem(p); bad_fork_cleanup_security: @@ -2627,6 +2625,8 @@ bad_fork_cleanup_perf: perf_event_free_task(p); bad_fork_sched_cancel_fork: sched_cancel_fork(p); + /* ->release() of a file may need scx_fork_rwsem for write. */ + exit_files(p); /* blocking */ bad_fork_cleanup_policy: lockdep_free_task(p); #ifdef CONFIG_NUMA @@ -2705,6 +2705,10 @@ struct task_struct *create_io_thread(int (*fn)(void *), void *arg, int node) .user_worker = 1, }; + /* A creator past its fatal signal or its coredump point gets no thread. */ + if (current->flags & (PF_SIGNALED | PF_POSTCOREDUMP)) + return ERR_PTR(-EINTR); + return copy_process(NULL, 0, node, &args); } @@ -3213,24 +3217,6 @@ static int unshare_fs(unsigned long unshare_flags, struct fs_struct **new_fsp) } /* - * Unshare file descriptor table if it is being shared - */ -static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp) -{ - struct files_struct *fd = current->files; - - if ((unshare_flags & CLONE_FILES) && - (fd && atomic_read(&fd->count) > 1)) { - fd = dup_fd(fd, NULL); - if (IS_ERR(fd)) - return PTR_ERR(fd); - *new_fdp = fd; - } - - return 0; -} - -/* * unshare allows a process to 'unshare' part of the process * context which was originally shared using clone. copy_* * functions used by kernel_clone() cannot be used here directly @@ -3325,10 +3311,8 @@ int ksys_unshare(unsigned long unshare_flags) if (new_fs) new_fs = switch_fs_struct(new_fs); - if (new_fd) { - guard(task_lock)(current); - swap(current->files, new_fd); - } + if (new_fd) + switch_files_struct(current, no_free_ptr(new_fd)); if (new_cred) { /* Install the new user namespace */ @@ -3361,30 +3345,6 @@ SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags) return ksys_unshare(unshare_flags); } -/* - * Helper to unshare the files of the current task. - * We don't want to expose copy_files internals to - * the exec layer of the kernel. - */ - -int unshare_files(void) -{ - struct task_struct *task = current; - struct files_struct *old, *copy = NULL; - int error; - - error = unshare_fd(CLONE_FILES, ©); - if (error || !copy) - return error; - - old = task->files; - task_lock(task); - task->files = copy; - task_unlock(task); - put_files_struct(old); - return 0; -} - static int sysctl_max_threads(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { diff --git a/kernel/kthread.c b/kernel/kthread.c index a3f95c90456b..cc8cb5d3eab7 100644 --- a/kernel/kthread.c +++ b/kernel/kthread.c @@ -81,7 +81,7 @@ enum KTHREAD_BITS { static inline struct kthread *to_kthread(struct task_struct *k) { - WARN_ON(!(k->flags & PF_KTHREAD)); + WARN_ON(!(READ_ONCE(k->flags) & PF_KTHREAD)); return k->worker_private; } diff --git a/kernel/pid_namespace.c b/kernel/pid_namespace.c index d36afc58ee1d..8bc9edb40b78 100644 --- a/kernel/pid_namespace.c +++ b/kernel/pid_namespace.c @@ -238,9 +238,10 @@ void zap_pid_ns_processes(struct pid_namespace *pid_ns) * kernel_wait4() will also block until our children traced from the * parent namespace are detached and become EXIT_DEAD. */ + /* Task work must not busy-loop the reaper, see signal_pending(). */ + guard(no_notify_signal)(); do { clear_thread_flag(TIF_SIGPENDING); - clear_thread_flag(TIF_NOTIFY_SIGNAL); rc = kernel_wait4(-1, NULL, __WALL, NULL); } while (rc != -ECHILD); diff --git a/kernel/ptrace.c b/kernel/ptrace.c index d041645d9d17..4e9822a87aab 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -1227,6 +1227,12 @@ int ptrace_request(struct task_struct *child, long request, case PTRACE_SETSIGMASK: { sigset_t new_set; + /* A user worker only ever takes SIGKILL and SIGSTOP. */ + if (child->flags & PF_USER_WORKER) { + ret = -EPERM; + break; + } + if (addr != sizeof(sigset_t)) { ret = -EINVAL; break; diff --git a/kernel/signal.c b/kernel/signal.c index d31ebcb6ed4d..e433de93b430 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -3170,6 +3170,10 @@ static void retarget_shared_pending(struct task_struct *tsk, sigset_t *which) sigset_t retarget; struct task_struct *t; + /* Nobody dequeues them in a dying group, see get_signal(). */ + if (tsk->signal->flags & SIGNAL_GROUP_EXIT) + return; + sigandsets(&retarget, &tsk->signal->shared_pending.signal, which); if (sigisemptyset(&retarget)) return; @@ -3255,6 +3259,16 @@ long do_no_restart_syscall(struct restart_block *param) static void __set_task_blocked(struct task_struct *tsk, const sigset_t *newset) { + sigset_t floor, floored; + + /* A user worker never unblocks anything but SIGKILL and SIGSTOP. */ + if (unlikely(tsk->flags & PF_USER_WORKER)) { + siginitsetinv(&floor, SIG_KERNEL_ONLY_MASK); + sigorsets(&floored, newset, &floor); + WARN_ON_ONCE(!sigequalsets(&floored, newset)); + newset = &floored; + } + if (task_sigpending(tsk) && !thread_group_empty(tsk)) { sigset_t newblocked; /* A set of now blocked but previously unblocked signals. */ diff --git a/kernel/tests/.kunitconfig b/kernel/tests/.kunitconfig new file mode 100644 index 000000000000..b3d1206fd81a --- /dev/null +++ b/kernel/tests/.kunitconfig @@ -0,0 +1,4 @@ +CONFIG_KUNIT=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_USER_NS_MAP_KUNIT_TEST=y diff --git a/kernel/tests/user_ns_map_kunit.c b/kernel/tests/user_ns_map_kunit.c new file mode 100644 index 000000000000..033dccc6a535 --- /dev/null +++ b/kernel/tests/user_ns_map_kunit.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for user namespace map insertion and sorting. + */ + +#define pr_fmt(fmt) "user_namespace: " fmt + +#include <kunit/test.h> +#include <linux/user_namespace.h> + +#define NR_EXTENTS (UID_GID_MAP_MAX_BASE_EXTENTS + 5) + +static void user_ns_map_insert(struct kunit *test) +{ + struct uid_gid_map map; + struct uid_gid_extent extent; + int i, ret; + + memset(&map, 0, sizeof(map)); + + /* Insert up to UID_GID_MAP_MAX_BASE_EXTENTS elements */ + for (i = 0; i < UID_GID_MAP_MAX_BASE_EXTENTS; i++) { + extent.first = i * 10; + extent.lower_first = i * 100; + extent.count = 5; + + ret = uid_gid_map_insert_extent(&map, &extent); + KUNIT_ASSERT_EQ(test, ret, 0); + } + + KUNIT_EXPECT_EQ(test, map.nr_extents, UID_GID_MAP_MAX_BASE_EXTENTS); + + /* Verify the elements ended up in the 'extent' array */ + for (i = 0; i < UID_GID_MAP_MAX_BASE_EXTENTS; i++) { + KUNIT_EXPECT_EQ(test, map.extent[i].first, i * 10); + KUNIT_EXPECT_EQ(test, map.extent[i].lower_first, i * 100); + KUNIT_EXPECT_EQ(test, map.extent[i].count, 5); + } +} + +static void user_ns_map_insert_extended(struct kunit *test) +{ + struct uid_gid_map map; + struct uid_gid_extent extent; + int i, ret; + + memset(&map, 0, sizeof(map)); + + /* Insert more than UID_GID_MAP_MAX_BASE_EXTENTS elements */ + for (i = 0; i < NR_EXTENTS; i++) { + int value = 9 - i; + + extent.first = value * 10; + extent.lower_first = value * 100; + extent.count = 5; + + ret = uid_gid_map_insert_extent(&map, &extent); + KUNIT_ASSERT_EQ(test, ret, 0); + } + + KUNIT_EXPECT_EQ(test, map.nr_extents, NR_EXTENTS); + + /* Now sort the map to set up reverse mapping */ + ret = uid_gid_map_sort(&map); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, map.reverse); + + /* Verify the elements are in 'forward' and that sorting is correct */ + for (i = 0; i < map.nr_extents; i++) { + KUNIT_EXPECT_EQ(test, map.forward[i].first, i * 10); + KUNIT_EXPECT_EQ(test, map.forward[i].lower_first, i * 100); + KUNIT_EXPECT_EQ(test, map.forward[i].count, 5); + + KUNIT_EXPECT_EQ(test, map.reverse[i].first, i * 10); + KUNIT_EXPECT_EQ(test, map.reverse[i].lower_first, i * 100); + KUNIT_EXPECT_EQ(test, map.reverse[i].count, 5); + } + + kfree(map.forward); + kfree(map.reverse); +} + +static struct kunit_case user_ns_map_test_cases[] = { + KUNIT_CASE(user_ns_map_insert), + KUNIT_CASE(user_ns_map_insert_extended), + {} +}; + +static struct kunit_suite user_ns_map_test_suite = { + .name = "user_ns_map", + .test_cases = user_ns_map_test_cases, +}; + +kunit_test_suite(user_ns_map_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("KUnit test for user namespace map insertion"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 0bed462e9b2a..1b23d819d398 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only +#include <kunit/visibility.h> #include <linux/export.h> #include <linux/nsproxy.h> #include <linux/slab.h> @@ -162,9 +163,6 @@ int create_user_ns(struct cred *new) ns_tree_add(ns); return 0; fail_keyring: -#ifdef CONFIG_PERSISTENT_KEYRINGS - key_put(ns->persistent_keyring_register); -#endif ns_common_free(ns); fail_free: kmem_cache_free(user_ns_cachep, ns); @@ -278,8 +276,8 @@ static int cmp_map_id(const void *k, const void *e) * map_id_range_down_max - Find idmap via binary search in ordered idmap array. * Can only be called if number of mappings exceeds UID_GID_MAP_MAX_BASE_EXTENTS. */ -static struct uid_gid_extent * -map_id_range_down_max(unsigned extents, struct uid_gid_map *map, u32 id, u32 count) +static const struct uid_gid_extent * +map_id_range_down_max(unsigned extents, const struct uid_gid_map *map, u32 id, u32 count) { struct idmap_key key; @@ -296,8 +294,8 @@ map_id_range_down_max(unsigned extents, struct uid_gid_map *map, u32 id, u32 cou * Can only be called if number of mappings is equal or less than * UID_GID_MAP_MAX_BASE_EXTENTS. */ -static struct uid_gid_extent * -map_id_range_down_base(unsigned extents, struct uid_gid_map *map, u32 id, u32 count) +static const struct uid_gid_extent * +map_id_range_down_base(unsigned extents, const struct uid_gid_map *map, u32 id, u32 count) { unsigned idx; u32 first, last, id2; @@ -315,9 +313,9 @@ map_id_range_down_base(unsigned extents, struct uid_gid_map *map, u32 id, u32 co return NULL; } -static u32 map_id_range_down(struct uid_gid_map *map, u32 id, u32 count) +static u32 map_id_range_down(const struct uid_gid_map *map, u32 id, u32 count) { - struct uid_gid_extent *extent; + const struct uid_gid_extent *extent; unsigned extents = map->nr_extents; smp_rmb(); @@ -335,7 +333,7 @@ static u32 map_id_range_down(struct uid_gid_map *map, u32 id, u32 count) return id; } -u32 map_id_down(struct uid_gid_map *map, u32 id) +u32 map_id_down(const struct uid_gid_map *map, u32 id) { return map_id_range_down(map, id, 1); } @@ -345,8 +343,8 @@ u32 map_id_down(struct uid_gid_map *map, u32 id) * Can only be called if number of mappings is equal or less than * UID_GID_MAP_MAX_BASE_EXTENTS. */ -static struct uid_gid_extent * -map_id_range_up_base(unsigned extents, struct uid_gid_map *map, u32 id, u32 count) +static const struct uid_gid_extent * +map_id_range_up_base(unsigned extents, const struct uid_gid_map *map, u32 id, u32 count) { unsigned idx; u32 first, last, id2; @@ -368,8 +366,8 @@ map_id_range_up_base(unsigned extents, struct uid_gid_map *map, u32 id, u32 coun * map_id_up_max - Find idmap via binary search in ordered idmap array. * Can only be called if number of mappings exceeds UID_GID_MAP_MAX_BASE_EXTENTS. */ -static struct uid_gid_extent * -map_id_range_up_max(unsigned extents, struct uid_gid_map *map, u32 id, u32 count) +static const struct uid_gid_extent * +map_id_range_up_max(unsigned extents, const struct uid_gid_map *map, u32 id, u32 count) { struct idmap_key key; @@ -381,9 +379,9 @@ map_id_range_up_max(unsigned extents, struct uid_gid_map *map, u32 id, u32 count sizeof(struct uid_gid_extent), cmp_map_id); } -u32 map_id_range_up(struct uid_gid_map *map, u32 id, u32 count) +u32 map_id_range_up(const struct uid_gid_map *map, u32 id, u32 count) { - struct uid_gid_extent *extent; + const struct uid_gid_extent *extent; unsigned extents = map->nr_extents; smp_rmb(); @@ -401,7 +399,7 @@ u32 map_id_range_up(struct uid_gid_map *map, u32 id, u32 count) return id; } -u32 map_id_up(struct uid_gid_map *map, u32 id) +u32 map_id_up(const struct uid_gid_map *map, u32 id) { return map_id_range_up(map, id, 1); } @@ -782,11 +780,13 @@ static bool mappings_overlap(struct uid_gid_map *new_map, } /* - * insert_extent - Safely insert a new idmap extent into struct uid_gid_map. + * uid_gid_map_insert_extent - Safely insert a new idmap extent into + * struct uid_gid_map. * Takes care to allocate a 4K block of memory if the number of mappings exceeds * UID_GID_MAP_MAX_BASE_EXTENTS. */ -static int insert_extent(struct uid_gid_map *map, struct uid_gid_extent *extent) +VISIBLE_IF_KUNIT int uid_gid_map_insert_extent(struct uid_gid_map *map, + struct uid_gid_extent *extent) { struct uid_gid_extent *dest; @@ -809,15 +809,20 @@ static int insert_extent(struct uid_gid_map *map, struct uid_gid_extent *extent) map->reverse = NULL; } - if (map->nr_extents < UID_GID_MAP_MAX_BASE_EXTENTS) - dest = &map->extent[map->nr_extents]; + /* + * nr_extents must be updated before the extent and forward arrays are + * accessed, otherwise KSAN will assert an out-of-bounds error. + */ + map->nr_extents++; + if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS) + dest = &map->extent[map->nr_extents - 1]; else - dest = &map->forward[map->nr_extents]; + dest = &map->forward[map->nr_extents - 1]; *dest = *extent; - map->nr_extents++; return 0; } +EXPORT_SYMBOL_IF_KUNIT(uid_gid_map_insert_extent); /* cmp function to sort() forward mappings */ static int cmp_extents_forward(const void *a, const void *b) @@ -850,10 +855,10 @@ static int cmp_extents_reverse(const void *a, const void *b) } /* - * sort_idmaps - Sorts an array of idmap entries. + * uid_gid_map_sort - Sorts an array of idmap entries. * Can only be called if number of mappings exceeds UID_GID_MAP_MAX_BASE_EXTENTS. */ -static int sort_idmaps(struct uid_gid_map *map) +VISIBLE_IF_KUNIT int uid_gid_map_sort(struct uid_gid_map *map) { if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS) return 0; @@ -874,6 +879,7 @@ static int sort_idmaps(struct uid_gid_map *map) return 0; } +EXPORT_SYMBOL_IF_KUNIT(uid_gid_map_sort); /** * verify_root_map() - check the uid 0 mapping @@ -1042,7 +1048,7 @@ static ssize_t map_write(struct file *file, const char __user *buf, (next_line != NULL)) goto out; - ret = insert_extent(&new_map, &extent); + ret = uid_gid_map_insert_extent(&new_map, &extent); if (ret < 0) goto out; ret = -EINVAL; @@ -1086,7 +1092,7 @@ static ssize_t map_write(struct file *file, const char __user *buf, * If we want to use binary search for lookup, this clones the extent * array and sorts both copies. */ - ret = sort_idmaps(&new_map); + ret = uid_gid_map_sort(&new_map); if (ret < 0) goto out; diff --git a/kernel/utsname.c b/kernel/utsname.c index ebbfc578a9d3..1ebf87e24607 100644 --- a/kernel/utsname.c +++ b/kernel/utsname.c @@ -81,7 +81,6 @@ struct uts_namespace *copy_utsname(u64 flags, { struct uts_namespace *new_ns; - BUG_ON(!old_ns); get_uts_ns(old_ns); if (!(flags & CLONE_NEWUTS)) |
