Commit b4c2bea8 authored by Miklos Szeredi's avatar Miklos Szeredi Committed by Christian Brauner

add listmount(2) syscall

Add way to query the children of a particular mount.  This is a more
flexible way to iterate the mount tree than having to parse
/proc/self/mountinfo.

Lookup the mount by the new 64bit mount ID. If a mount needs to be
queried based on path, then statx(2) can be used to first query the
mount ID belonging to the path.

Return an array of new (64bit) mount ID's. Without privileges only
mounts are listed which are reachable from the task's root.

Folded into this patch are several later improvements. Keeping them
separate would make the history pointlessly confusing:

* Recursive listing of mounts is the default now (cf. [1]).
* Remove explicit LISTMOUNT_UNREACHABLE flag (cf. [1]) and fail if mount
  is unreachable from current root. This also makes permission checking
  consistent with statmount() (cf. [3]).
* Start listing mounts in unique mount ID order (cf. [2]) to allow
  continuing listmount() from a midpoint.
* Allow to continue listmount(). The @request_mask parameter is renamed
  and to @param to be usable by both statmount() and listmount().
  If @param is set to a mount id then listmount() will continue listing
  mounts from that id on. This allows listing mounts in multiple
  listmount invocations without having to resize the buffer. If @param
  is zero then the listing starts from the beginning (cf. [4]).
* Don't return EOVERFLOW, instead return the buffer size which allows to
  detect a full buffer as well (cf. [4]).
Signed-off-by: default avatarMiklos Szeredi <mszeredi@redhat.com>
Link: https://lore.kernel.org/r/20231025140205.3586473-6-mszeredi@redhat.comReviewed-by: default avatarIan Kent <raven@themaw.net>
Link: https://lore.kernel.org/r/20231128160337.29094-2-mszeredi@redhat.com [1] (folded)
Link: https://lore.kernel.org/r/20231128160337.29094-3-mszeredi@redhat.com [2] (folded)
Link: https://lore.kernel.org/r/20231128160337.29094-4-mszeredi@redhat.com [3] (folded)
Link: https://lore.kernel.org/r/20231128160337.29094-5-mszeredi@redhat.com [4] (folded)
[Christian Brauner <brauner@kernel.org>: various smaller fixes]
Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parent 68385d77
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include <linux/fs_context.h> #include <linux/fs_context.h>
#include <linux/shmem_fs.h> #include <linux/shmem_fs.h>
#include <linux/mnt_idmapping.h> #include <linux/mnt_idmapping.h>
#include <linux/nospec.h>
#include "pnode.h" #include "pnode.h"
#include "internal.h" #include "internal.h"
...@@ -1009,7 +1010,7 @@ void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct m ...@@ -1009,7 +1010,7 @@ void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct m
static inline struct mount *node_to_mount(struct rb_node *node) static inline struct mount *node_to_mount(struct rb_node *node)
{ {
return rb_entry(node, struct mount, mnt_node); return node ? rb_entry(node, struct mount, mnt_node) : NULL;
} }
static void mnt_add_to_ns(struct mnt_namespace *ns, struct mount *mnt) static void mnt_add_to_ns(struct mnt_namespace *ns, struct mount *mnt)
...@@ -4945,7 +4946,7 @@ static int prepare_kstatmount(struct kstatmount *ks, struct mnt_id_req *kreq, ...@@ -4945,7 +4946,7 @@ static int prepare_kstatmount(struct kstatmount *ks, struct mnt_id_req *kreq,
return -EFAULT; return -EFAULT;
memset(ks, 0, sizeof(*ks)); memset(ks, 0, sizeof(*ks));
ks->mask = kreq->request_mask; ks->mask = kreq->param;
ks->buf = buf; ks->buf = buf;
ks->bufsize = bufsize; ks->bufsize = bufsize;
ks->seq.size = seq_size; ks->seq.size = seq_size;
...@@ -4999,6 +5000,87 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req, ...@@ -4999,6 +5000,87 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req,
return ret; return ret;
} }
static struct mount *listmnt_next(struct mount *curr)
{
return node_to_mount(rb_next(&curr->mnt_node));
}
static ssize_t do_listmount(struct mount *first, struct path *orig, u64 mnt_id,
u64 __user *buf, size_t bufsize,
const struct path *root)
{
struct mount *r;
ssize_t ctr;
int err;
/*
* Don't trigger audit denials. We just want to determine what
* mounts to show users.
*/
if (!is_path_reachable(real_mount(orig->mnt), orig->dentry, root) &&
!ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN))
return -EPERM;
err = security_sb_statfs(orig->dentry);
if (err)
return err;
for (ctr = 0, r = first; r && ctr < bufsize; r = listmnt_next(r)) {
if (r->mnt_id_unique == mnt_id)
continue;
if (!is_path_reachable(r, r->mnt.mnt_root, orig))
continue;
ctr = array_index_nospec(ctr, bufsize);
if (put_user(r->mnt_id_unique, buf + ctr))
return -EFAULT;
if (check_add_overflow(ctr, 1, &ctr))
return -ERANGE;
}
return ctr;
}
SYSCALL_DEFINE4(listmount, const struct mnt_id_req __user *, req,
u64 __user *, buf, size_t, bufsize, unsigned int, flags)
{
struct mnt_namespace *ns = current->nsproxy->mnt_ns;
struct mnt_id_req kreq;
struct mount *first;
struct path root, orig;
u64 mnt_id, last_mnt_id;
ssize_t ret;
if (flags)
return -EINVAL;
if (copy_from_user(&kreq, req, sizeof(kreq)))
return -EFAULT;
mnt_id = kreq.mnt_id;
last_mnt_id = kreq.param;
down_read(&namespace_sem);
get_fs_root(current->fs, &root);
if (mnt_id == LSMT_ROOT) {
orig = root;
} else {
ret = -ENOENT;
orig.mnt = lookup_mnt_in_ns(mnt_id, ns);
if (!orig.mnt)
goto err;
orig.dentry = orig.mnt->mnt_root;
}
if (!last_mnt_id)
first = node_to_mount(rb_first(&ns->mounts));
else
first = mnt_find_id_at(ns, last_mnt_id + 1);
ret = do_listmount(first, &orig, mnt_id, buf, bufsize, &root);
err:
path_put(&root);
up_read(&namespace_sem);
return ret;
}
static void __init init_mount_tree(void) static void __init init_mount_tree(void)
{ {
struct vfsmount *mnt; struct vfsmount *mnt;
......
...@@ -412,6 +412,9 @@ asmlinkage long sys_fstatfs64(unsigned int fd, size_t sz, ...@@ -412,6 +412,9 @@ asmlinkage long sys_fstatfs64(unsigned int fd, size_t sz,
asmlinkage long sys_statmount(const struct mnt_id_req __user *req, asmlinkage long sys_statmount(const struct mnt_id_req __user *req,
struct statmount __user *buf, size_t bufsize, struct statmount __user *buf, size_t bufsize,
unsigned int flags); unsigned int flags);
asmlinkage long sys_listmount(const struct mnt_id_req __user *req,
u64 __user *buf, size_t bufsize,
unsigned int flags);
asmlinkage long sys_truncate(const char __user *path, long length); asmlinkage long sys_truncate(const char __user *path, long length);
asmlinkage long sys_ftruncate(unsigned int fd, unsigned long length); asmlinkage long sys_ftruncate(unsigned int fd, unsigned long length);
#if BITS_PER_LONG == 32 #if BITS_PER_LONG == 32
......
...@@ -176,9 +176,16 @@ struct statmount { ...@@ -176,9 +176,16 @@ struct statmount {
char str[]; /* Variable size part containing strings */ char str[]; /* Variable size part containing strings */
}; };
/*
* Structure for passing mount ID and miscellaneous parameters to statmount(2)
* and listmount(2).
*
* For statmount(2) @param represents the request mask.
* For listmount(2) @param represents the last listed mount id (or zero).
*/
struct mnt_id_req { struct mnt_id_req {
__u64 mnt_id; __u64 mnt_id;
__u64 request_mask; __u64 param;
}; };
/* /*
...@@ -191,4 +198,9 @@ struct mnt_id_req { ...@@ -191,4 +198,9 @@ struct mnt_id_req {
#define STATMOUNT_MNT_POINT 0x00000010U /* Want/got mnt_point */ #define STATMOUNT_MNT_POINT 0x00000010U /* Want/got mnt_point */
#define STATMOUNT_FS_TYPE 0x00000020U /* Want/got fs_type */ #define STATMOUNT_FS_TYPE 0x00000020U /* Want/got fs_type */
/*
* Special @mnt_id values that can be passed to listmount
*/
#define LSMT_ROOT 0xffffffffffffffff /* root mount */
#endif /* _UAPI_LINUX_MOUNT_H */ #endif /* _UAPI_LINUX_MOUNT_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment