Received: by 2002:a05:6a10:8c0a:0:0:0:0 with SMTP id go10csp1127347pxb; Thu, 4 Mar 2021 04:08:57 -0800 (PST) X-Google-Smtp-Source: ABdhPJxV1bRklaz0rAiTsS5f970aI9VduItRNmMd7z5eIx/pXntZwc82LKDaUipPBIi9HZ/3HW+b X-Received: by 2002:a17:906:1fd2:: with SMTP id e18mr4035217ejt.49.1614859737195; Thu, 04 Mar 2021 04:08:57 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1614859737; cv=none; d=google.com; s=arc-20160816; b=ycm4R42XII+ZBcMf6KzQSNtKX8Fe3TnWQ0uR0qgjq6XM53h8hZUPPc81PH6baIjx1G kZ0Uz2+/yfnKwYHNJmX23T5qd9dQ7h9MfQDW0gGWcK0Zua+z6von7NMEOARVC2seJnxl 96FeV6s/fU4WcxU+8+00rg7xAmC0pHTGkKcu1Dg1FCtvyMHok9FJUiXJlKMG323y813D S1FN+cHgIZsm0bijpLf//e7UccYHqninaIZTWjEtmxI+Hh3H+/nS5J5Eelr9td2Pt+mW DFhSa48131EbP5X/z15U6v5/VRSWJsRlteGf8Ad0lTqXZkFmWEdEvI/uiTqjk3BN5zbI AGxQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :message-id:date:subject:cc:to:from:dkim-signature; bh=ytMuFTYJbOuVzYAB8UcxHSbHYeMMy8+JvA85KaeMmIU=; b=biLDq4CuiwDrx0SkTmz9LrdZe/iS69rqfSi/I05jh2rLREPKLGsnJkmGlygE7nJrPs +B6dSaCYletpCfxoOVeMbwMkHFUP5ELhHwTqk5b1+kzJCowFRerA9goNrWrX8L4V86uY qM2PUohPS7MPaBokXajqVXisNmxRKC8f0PGqqts6rbl3mEV+DO22+zp0zxoUCDiRv9ek pYNXmAeDaaERxobEUU/iGqLWEdQtzrhNjCol+w65C7G+8nj07+T0T1m2Hla+KF150Wt8 pA9RokeFhvg/zm8wYyfs5Ok8ZbGm99DuPPxbdUKYmDJWT6AvIah9UUO+48GIyuVfsyPm zpSg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=g8uozpPq; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id h7si1847756edt.553.2021.03.04.04.08.35; Thu, 04 Mar 2021 04:08:57 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=g8uozpPq; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1578772AbhCCSRz (ORCPT + 99 others); Wed, 3 Mar 2021 13:17:55 -0500 Received: from relay.sw.ru ([185.231.240.75]:34926 "EHLO relay.sw.ru" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1445187AbhCCQSc (ORCPT ); Wed, 3 Mar 2021 11:18:32 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=virtuozzo.com; s=relay; h=MIME-Version:Message-Id:Date:Subject:From: Content-Type; bh=ytMuFTYJbOuVzYAB8UcxHSbHYeMMy8+JvA85KaeMmIU=; b=g8uozpPqx1C0 3iniseSFPsuNf9qb1sPPVJB3Vos6T9/wmyTzMZE2dvgS8Fz9f3a6O8ojrRyrOwr1T9xACeFKkmEqo mKsdzX5yAaUM4hDDYdsIzhJQ/rG6RqoLCSMxWR/BaU0KlrpWxy+oiPXMsytOVWCDqW2INy9iNWXsM fnAoI=; Received: from amikhalitsyn.sw.ru ([10.93.0.33] helo=dhcp-172-16-24-175.sw.ru) by relay.sw.ru with esmtp (Exim 4.94) (envelope-from ) id 1lHUBd-002fYQ-MF; Wed, 03 Mar 2021 19:17:21 +0300 From: Alexander Mikhalitsyn To: raven@themaw.net Cc: Alexander Mikhalitsyn , Matthew Wilcox , Al Viro , Pavel Tikhomirov , Kirill Tkhai , autofs@vger.kernel.org, linux-kernel@vger.kernel.org, Miklos Szeredi , Christian Brauner , Ross Zwisler , Aleksa Sarai , Eric Biggers , Mattias Nissler , linux-fsdevel@vger.kernel.org Subject: [RFC PATCH] autofs: find_autofs_mount overmounted parent support Date: Wed, 3 Mar 2021 19:17:03 +0300 Message-Id: <20210303161705.776241-1-alexander.mikhalitsyn@virtuozzo.com> X-Mailer: git-send-email 2.28.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 Cc: Al Viro Cc: Pavel Tikhomirov Cc: Kirill Tkhai Cc: autofs@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Alexander Mikhalitsyn --- fs/autofs/dev-ioctl.c | 127 +++++++++++++++++++++++++++++++++++++----- fs/namespace.c | 44 +++++++++++++++ include/linux/mount.h | 5 ++ 3 files changed, 162 insertions(+), 14 deletions(-) diff --git a/fs/autofs/dev-ioctl.c b/fs/autofs/dev-ioctl.c index 5bf781ea6d67..55edd3eba8ce 100644 --- a/fs/autofs/dev-ioctl.c +++ b/fs/autofs/dev-ioctl.c @@ -10,6 +10,7 @@ #include #include #include +#include #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; } diff --git a/fs/namespace.c b/fs/namespace.c index 56bb5a5fdc0d..e1d006dbdfe2 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -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 diff --git a/include/linux/mount.h b/include/linux/mount.h index 5d92a7e1a742..a79e6392e38e 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -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 */ -- 2.28.0