summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorChristian Brauner <brauner@kernel.org>2024-06-25 14:33:45 +0200
committerChristian Brauner <brauner@kernel.org>2024-06-28 14:36:43 +0200
commitd842379313a2c205dae64dbfd0aa13dba142a867 (patch)
tree04a478c5376748b34098ade578bbce20d89d8b24 /fs
parenta7ebb0fe43edfc869db3725a5d984de3e47c646c (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.c160
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;