diff options
author | Christian Brauner <brauner@kernel.org> | 2024-06-25 14:33:45 +0200 |
---|---|---|
committer | Christian Brauner <brauner@kernel.org> | 2024-06-28 14:36:43 +0200 |
commit | d842379313a2c205dae64dbfd0aa13dba142a867 (patch) | |
tree | 04a478c5376748b34098ade578bbce20d89d8b24 /fs | |
parent | a7ebb0fe43edfc869db3725a5d984de3e47c646c (diff) |
fs: use guard for namespace_sem in statmount()
Signed-off-by: Christian Brauner <brauner@kernel.org>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/namespace.c | 160 |
1 files changed, 83 insertions, 77 deletions
diff --git a/fs/namespace.c b/fs/namespace.c index e871f73c4c8c..a989e89b0a10 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -5040,17 +5040,70 @@ static int copy_statmount_to_user(struct kstatmount *s) return 0; } -static int do_statmount(struct kstatmount *s) +static struct mount *listmnt_next(struct mount *curr, bool reverse) { - struct mount *m = real_mount(s->mnt); - struct mnt_namespace *ns = m->mnt_ns; + struct rb_node *node; + + if (reverse) + node = rb_prev(&curr->mnt_node); + else + node = rb_next(&curr->mnt_node); + + return node_to_mount(node); +} + +static int grab_requested_root(struct mnt_namespace *ns, struct path *root) +{ + struct mount *first; + + rwsem_assert_held(&namespace_sem); + + /* We're looking at our own ns, just use get_fs_root. */ + if (ns == current->nsproxy->mnt_ns) { + get_fs_root(current->fs, root); + return 0; + } + + /* + * We have to find the first mount in our ns and use that, however it + * may not exist, so handle that properly. + */ + if (RB_EMPTY_ROOT(&ns->mounts)) + return -ENOENT; + + first = listmnt_next(ns->root, false); + if (!first) + return -ENOENT; + root->mnt = mntget(&first->mnt); + root->dentry = dget(root->mnt->mnt_root); + return 0; +} + +static int do_statmount(struct kstatmount *s, u64 mnt_id, u64 mnt_ns_id, + struct mnt_namespace *ns) +{ + struct path root __free(path_put) = {}; + struct mount *m; int err; + /* Has the namespace already been emptied? */ + if (mnt_ns_id && RB_EMPTY_ROOT(&ns->mounts)) + return -ENOENT; + + s->mnt = lookup_mnt_in_ns(mnt_id, ns); + if (!s->mnt) + return -ENOENT; + + err = grab_requested_root(ns, &root); + if (err) + return err; + /* * Don't trigger audit denials. We just want to determine what * mounts to show users. */ - if (!is_path_reachable(m, m->mnt.mnt_root, &s->root) && + m = real_mount(s->mnt); + if (!is_path_reachable(m, m->mnt.mnt_root, &root) && !ns_capable_noaudit(ns->user_ns, CAP_SYS_ADMIN)) return -EPERM; @@ -5058,6 +5111,7 @@ static int do_statmount(struct kstatmount *s) if (err) return err; + s->root = root; if (s->mask & STATMOUNT_SB_BASIC) statmount_sb_basic(s); @@ -5096,6 +5150,9 @@ static inline bool retry_statmount(const long ret, size_t *seq_size) return true; } +#define STATMOUNT_STRING_REQ (STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT | \ + STATMOUNT_FS_TYPE) + static int prepare_kstatmount(struct kstatmount *ks, struct mnt_id_req *kreq, struct statmount __user *buf, size_t bufsize, size_t seq_size) @@ -5107,10 +5164,18 @@ static int prepare_kstatmount(struct kstatmount *ks, struct mnt_id_req *kreq, ks->mask = kreq->param; ks->buf = buf; ks->bufsize = bufsize; - ks->seq.size = seq_size; - ks->seq.buf = kvmalloc(seq_size, GFP_KERNEL_ACCOUNT); - if (!ks->seq.buf) - return -ENOMEM; + + if (ks->mask & STATMOUNT_STRING_REQ) { + if (bufsize == sizeof(ks->sm)) + return -EOVERFLOW; + + ks->seq.buf = kvmalloc(seq_size, GFP_KERNEL_ACCOUNT); + if (!ks->seq.buf) + return -ENOMEM; + + ks->seq.size = seq_size; + } + return 0; } @@ -5138,45 +5203,6 @@ static int copy_mnt_id_req(const struct mnt_id_req __user *req, return 0; } -static struct mount *listmnt_next(struct mount *curr, bool reverse) -{ - struct rb_node *node; - - if (reverse) - node = rb_prev(&curr->mnt_node); - else - node = rb_next(&curr->mnt_node); - - return node_to_mount(node); -} - -static int grab_requested_root(struct mnt_namespace *ns, struct path *root) -{ - struct mount *first; - - rwsem_assert_held(&namespace_sem); - - /* We're looking at our own ns, just use get_fs_root. */ - if (ns == current->nsproxy->mnt_ns) { - get_fs_root(current->fs, root); - return 0; - } - - /* - * We have to find the first mount in our ns and use that, however it - * may not exist, so handle that properly. - */ - if (RB_EMPTY_ROOT(&ns->mounts)) - return -ENOENT; - - first = listmnt_next(ns->root, false); - if (!first) - return -ENOENT; - root->mnt = mntget(&first->mnt); - root->dentry = dget(root->mnt->mnt_root); - return 0; -} - /* * If the user requested a specific mount namespace id, look that up and return * that, or if not simply grab a passive reference on our mount namespace and @@ -5195,9 +5221,8 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req, unsigned int, flags) { struct mnt_namespace *ns __free(mnt_ns_release) = NULL; - struct vfsmount *mnt; + struct kstatmount *ks __free(kfree) = NULL; struct mnt_id_req kreq; - struct kstatmount ks; /* We currently support retrieval of 3 strings. */ size_t seq_size = 3 * PATH_MAX; int ret; @@ -5217,40 +5242,21 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req, !ns_capable_noaudit(ns->user_ns, CAP_SYS_ADMIN)) return -ENOENT; + ks = kmalloc(sizeof(*ks), GFP_KERNEL_ACCOUNT); + if (!ks) + return -ENOMEM; + retry: - ret = prepare_kstatmount(&ks, &kreq, buf, bufsize, seq_size); + ret = prepare_kstatmount(ks, &kreq, buf, bufsize, seq_size); if (ret) return ret; - down_read(&namespace_sem); - /* Has the namespace already been emptied? */ - if (kreq.mnt_ns_id && RB_EMPTY_ROOT(&ns->mounts)) { - up_read(&namespace_sem); - kvfree(ks.seq.buf); - return -ENOENT; - } - - mnt = lookup_mnt_in_ns(kreq.mnt_id, ns); - if (!mnt) { - up_read(&namespace_sem); - kvfree(ks.seq.buf); - return -ENOENT; - } - - ks.mnt = mnt; - ret = grab_requested_root(ns, &ks.root); - if (ret) { - up_read(&namespace_sem); - kvfree(ks.seq.buf); - return ret; - } - ret = do_statmount(&ks); - path_put(&ks.root); - up_read(&namespace_sem); + scoped_guard(rwsem_read, &namespace_sem) + ret = do_statmount(ks, kreq.mnt_id, kreq.mnt_ns_id, ns); if (!ret) - ret = copy_statmount_to_user(&ks); - kvfree(ks.seq.buf); + ret = copy_statmount_to_user(ks); + kvfree(ks->seq.buf); if (retry_statmount(ret, &seq_size)) goto retry; return ret; |