@@ -10,6 +10,7 @@
#include <linux/fdtable.h>
#include <linux/magic.h>
#include <linux/nospec.h>
+#include <linux/nsproxy.h>
#include "autofs_i.h"
@@ -179,32 +180,130 @@ static int autofs_dev_ioctl_protosubver(struct file *fp,
return 0;
}
+struct filter_autofs_data {
+ char *pathbuf;
+ const char *fpathname;
+ int (*test)(const struct path *path, void *data);
+ void *data;
+};
+
+static int filter_autofs(const struct path *path, void *p)
+{
+ struct filter_autofs_data *data = p;
+ char *name;
+ int err;
+
+ if (path->mnt->mnt_sb->s_magic != AUTOFS_SUPER_MAGIC)
+ return 0;
+
+ name = d_path(path, data->pathbuf, PATH_MAX);
+ if (IS_ERR(name)) {
+ err = PTR_ERR(name);
+ pr_err("d_path failed, errno %d\n", err);
+ return 0;
+ }
+
+ if (strncmp(data->fpathname, name, PATH_MAX))
+ return 0;
+
+ if (!data->test(path, data->data))
+ return 0;
+
+ return 1;
+}
+
/* Find the topmost mount satisfying test() */
static int find_autofs_mount(const char *pathname,
struct path *res,
int test(const struct path *path, void *data),
void *data)
{
- struct path path;
+ struct filter_autofs_data mdata = {
+ .pathbuf = NULL,
+ .test = test,
+ .data = data,
+ };
+ struct mnt_namespace *mnt_ns = current->nsproxy->mnt_ns;
+ struct path path = {};
+ char *fpathbuf = NULL;
int err;
+ /*
+ * In most cases user will provide full path to autofs mount point
+ * as it is in /proc/X/mountinfo. But if not, then we need to
+ * open provided relative path and calculate full path.
+ * It will not work in case when parent mount of autofs mount
+ * is overmounted:
+ * cd /root
+ * ./autofs_mount /root/autofs_yard/mnt
+ * mount -t tmpfs tmpfs /root/autofs_yard/mnt
+ * mount -t tmpfs tmpfs /root/autofs_yard
+ * ./call_ioctl /root/autofs_yard/mnt <- all fine here because we
+ * have full path and don't
+ * need to call kern_path()
+ * and d_path()
+ * ./call_ioctl autofs_yard/mnt <- will fail because kern_path()
+ * can't lookup /root/autofs_yard/mnt
+ * (/root/autofs_yard directory is
+ * empty)
+ *
+ * TO DISCUSS: we can write special algorithm for relative path case
+ * by getting cwd path combining it with relative path from user. But
+ * is it worth it? User also may use paths with symlinks in components
+ * of path.
+ *
+ */
err = kern_path(pathname, LOOKUP_MOUNTPOINT, &path);
- if (err)
- return err;
- err = -ENOENT;
- while (path.dentry == path.mnt->mnt_root) {
- if (path.dentry->d_sb->s_magic == AUTOFS_SUPER_MAGIC) {
- if (test(&path, data)) {
- path_get(&path);
- *res = path;
- err = 0;
- break;
- }
+ if (err) {
+ if (pathname[0] == '/') {
+ /*
+ * pathname looks like full path let's try to use it
+ * as it is when searching autofs mount
+ */
+ mdata.fpathname = pathname;
+ err = 0;
+ pr_debug("kern_path failed on %s, errno %d. Will use path as it is to search mount\n",
+ pathname, err);
+ } else {
+ pr_err("kern_path failed on %s, errno %d\n",
+ pathname, err);
+ return err;
+ }
+ } else {
+ pr_debug("find_autofs_mount: let's resolve full path %s\n",
+ pathname);
+
+ fpathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
+ if (!fpathbuf) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ /*
+ * We have pathname from user but it may be relative, we need to
+ * have full path because we want to compare it with mountpoints
+ * paths later.
+ */
+ mdata.fpathname = d_path(&path, fpathbuf, PATH_MAX);
+ if (IS_ERR(mdata.fpathname)) {
+ err = PTR_ERR(mdata.fpathname);
+ pr_err("d_path failed, errno %d\n", err);
+ goto err;
}
- if (!follow_up(&path))
- break;
}
+
+ mdata.pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
+ if (!mdata.pathbuf) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ err = lookup_mount_path(mnt_ns, res, filter_autofs, &mdata);
+
+err:
path_put(&path);
+ kfree(fpathbuf);
+ kfree(mdata.pathbuf);
return err;
}
@@ -1367,6 +1367,50 @@ void mnt_cursor_del(struct mnt_namespace *ns, struct mount *cursor)
}
#endif /* CONFIG_PROC_FS */
+/**
+ * lookup_mount_path - traverse all mounts in mount namespace
+ * and filter using test() probe callback
+ * As a result struct path will be provided.
+ * @ns: root of mount tree
+ * @res: struct path pointer where resulting path will be written
+ * @test: filter callback
+ * @data: will be provided as argument to test() callback
+ *
+ */
+int lookup_mount_path(struct mnt_namespace *ns,
+ struct path *res,
+ int test(const struct path *mnt, void *data),
+ void *data)
+{
+ struct mount *mnt;
+ int err = -ENOENT;
+
+ down_read(&namespace_sem);
+ lock_ns_list(ns);
+ list_for_each_entry(mnt, &ns->list, mnt_list) {
+ struct path tmppath;
+
+ if (mnt_is_cursor(mnt))
+ continue;
+
+ tmppath.dentry = mnt->mnt.mnt_root;
+ tmppath.mnt = &mnt->mnt;
+
+ if (test(&tmppath, data)) {
+ path_get(&tmppath);
+ *res = tmppath;
+ err = 0;
+ break;
+ }
+ }
+ unlock_ns_list(ns);
+ up_read(&namespace_sem);
+
+ return err;
+}
+
+EXPORT_SYMBOL(lookup_mount_path);
+
/**
* may_umount_tree - check if a mount tree is busy
* @mnt: root of mount tree
@@ -118,6 +118,11 @@ extern unsigned int sysctl_mount_max;
extern bool path_is_mountpoint(const struct path *path);
+extern int lookup_mount_path(struct mnt_namespace *ns,
+ struct path *res,
+ int test(const struct path *mnt, void *data),
+ void *data);
+
extern void kern_unmount_array(struct vfsmount *mnt[], unsigned int num);
#endif /* _LINUX_MOUNT_H */
It was discovered that find_autofs_mount() function in autofs not support cases when autofs mount parent is overmounted. In this case this function will always return -ENOENT. Real-life reproducer is fairly simple. Consider the following mounts on root mntns: -- 35 24 0:36 / /proc/sys/fs/binfmt_misc ... shared:16 - autofs systemd-1 ... 654 35 0:57 / /proc/sys/fs/binfmt_misc ... shared:322 - binfmt_misc ... -- and some process which calls ioctl(AUTOFS_DEV_IOCTL_OPENMOUNT) $ unshare -m -p --fork --mount-proc ./process-bin Due to "mount-proc" /proc will be overmounted and ioctl() will fail with -ENOENT Cc: Matthew Wilcox <willy@infradead.org> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Pavel Tikhomirov <ptikhomirov@virtuozzo.com> Cc: Kirill Tkhai <ktkhai@virtuozzo.com> Cc: autofs@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Alexander Mikhalitsyn <alexander.mikhalitsyn@virtuozzo.com> --- fs/autofs/dev-ioctl.c | 127 +++++++++++++++++++++++++++++++++++++----- fs/namespace.c | 44 +++++++++++++++ include/linux/mount.h | 5 ++ 3 files changed, 162 insertions(+), 14 deletions(-)