* [PATCH 0/3 v2] fs: allow to use dirfd as root for openat and other *at syscalls
@ 2016-06-28 17:38 Andrey Vagin
2016-06-28 17:38 ` [PATCH 1/3] namei: add LOOKUP_DFD_ROOT to use dfd as root Andrey Vagin
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Andrey Vagin @ 2016-06-28 17:38 UTC (permalink / raw)
To: linux-fsdevel
Cc: linux-kernel, criu, containers, Andrey Vagin, Alexander Viro,
Eric W. Biederman, Arnd Bergmann, J. Bruce Fields,
Miklos Szeredi, NeilBrown, Shuah Khan
The problem is that a pathname can contain absolute symlinks and now
they are resolved relative to the current root.
It may be a problem if you want to open a file in another namespace.
For example, you open /proc/PID/root for a process from the target
namespace and then you use openat() to open a file from this namespace.
If a path to the file contains an absolute symlink, you will open a file
from the current namespace, because a symlink will be resolved relative
to the current root.
A proposed solution adds a new flag which means that dirfd should be
set as a root for a current system call (openat(), statat(), etc).
Here are examples how we can open a file in a contex of another process.
How we can do this without these changes:
old_root = open("/", O_PATH);
old_cwd = open(".", O_PATH);
chroot("/proc/PID/root");
fd = open(pathname, O_RDONLY);
fchdir(old_root); /* emulate fchroot() */
chroot(".");
fchdir(old_cwd);
close(old_cwd);
close(old_root);
How this code is simplified with new flags:
dirfd = open("/proc/PID/root", O_PATH);
fd = open(dirfd, pathname, O_RDONLY | O_ATROOT);
close(dirfd);
One more thing is that chroot isn't available for unprivileged users.
We met this problem, when we tryed to dump an ubuntu container and
failed to resolve /proc/PID/root/var/run/mysqld/mysqld.sock, because
/var/run was a symlink to /run.
Changes since the first version:
- change a value of O_ATROOT to not intersect with other constants.
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: "J. Bruce Fields" <bfields@redhat.com>
Cc: Miklos Szeredi <mszeredi@redhat.com>
Cc: NeilBrown <neilb@suse.de>
Cc: Shuah Khan <shuahkh@osg.samsung.com>
Signed-off-by: Andrey Vagin <avagin@openvz.org>
Andrey Vagin (3):
namei: add LOOKUP_DFD_ROOT to use dfd as root
fs: allow to use dirfd as root for openat and other *at syscalls
selftests: check O_ATROOT and AT_FDROOT flags
fs/exec.c | 4 +-
fs/namei.c | 22 ++++++--
fs/open.c | 6 ++-
fs/stat.c | 4 +-
fs/utimes.c | 4 +-
include/linux/namei.h | 2 +
include/uapi/asm-generic/fcntl.h | 4 ++
include/uapi/linux/fcntl.h | 1 +
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/lookup/.gitignore | 1 +
tools/testing/selftests/lookup/Makefile | 8 +++
tools/testing/selftests/lookup/lookup_at_root.c | 69 +++++++++++++++++++++++++
tools/testing/selftests/lookup/run.sh | 14 +++++
13 files changed, 131 insertions(+), 9 deletions(-)
create mode 100644 tools/testing/selftests/lookup/.gitignore
create mode 100644 tools/testing/selftests/lookup/Makefile
create mode 100644 tools/testing/selftests/lookup/lookup_at_root.c
create mode 100755 tools/testing/selftests/lookup/run.sh
--
2.5.5
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 1/3] namei: add LOOKUP_DFD_ROOT to use dfd as root 2016-06-28 17:38 [PATCH 0/3 v2] fs: allow to use dirfd as root for openat and other *at syscalls Andrey Vagin @ 2016-06-28 17:38 ` Andrey Vagin 2016-07-02 0:55 ` Omar Sandoval 2016-06-28 17:38 ` [PATCH 2/3] fs: allow to use dirfd as root for openat and other *at syscalls Andrey Vagin 2016-06-28 17:38 ` [PATCH 3/3] selftests: check O_ATROOT and AT_FDROOT flags Andrey Vagin 2 siblings, 1 reply; 8+ messages in thread From: Andrey Vagin @ 2016-06-28 17:38 UTC (permalink / raw) To: linux-fsdevel Cc: linux-kernel, criu, containers, Andrey Vagin, Alexander Viro, Eric W. Biederman, Arnd Bergmann, J. Bruce Fields, Miklos Szeredi, NeilBrown, Shuah Khan The problem is that a pathname can contain absolute symlinks and now they are resolved relative to the current root. If we want to open a file in another mount namespaces and we have a file descriptor to its root directory, we probably want to resolve pathname in the target mount namespace. For this we add this new flag. If LOOKUP_DFD_ROOT is set, path_init() initializes nd->root and nd->path to the same value. Signed-off-by: Andrey Vagin <avagin@openvz.org> --- fs/namei.c | 12 +++++++++++- include/linux/namei.h | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/fs/namei.c b/fs/namei.c index 70580ab..5f08b69 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2148,7 +2148,7 @@ static const char *path_init(struct nameidata *nd, unsigned flags) nd->path.dentry = NULL; nd->m_seq = read_seqbegin(&mount_lock); - if (*s == '/') { + if (*s == '/' && !(flags & LOOKUP_DFD_ROOT)) { if (flags & LOOKUP_RCU) rcu_read_lock(); set_root(nd); @@ -2174,6 +2174,11 @@ static const char *path_init(struct nameidata *nd, unsigned flags) get_fs_pwd(current->fs, &nd->path); nd->inode = nd->path.dentry->d_inode; } + if (flags & LOOKUP_DFD_ROOT) { + nd->root = nd->path; + if (!(flags & LOOKUP_RCU)) + path_get(&nd->root); + } return s; } else { /* Caller must check execute permissions on the starting path component */ @@ -2202,6 +2207,11 @@ static const char *path_init(struct nameidata *nd, unsigned flags) nd->inode = nd->path.dentry->d_inode; } fdput(f); + if (flags & LOOKUP_DFD_ROOT) { + nd->root = nd->path; + if (!(flags & LOOKUP_RCU)) + path_get(&nd->root); + } return s; } } diff --git a/include/linux/namei.h b/include/linux/namei.h index d3d0398..ca2ea13 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -45,6 +45,8 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND}; #define LOOKUP_ROOT 0x2000 #define LOOKUP_EMPTY 0x4000 +#define LOOKUP_DFD_ROOT 0x8000 + extern int path_pts(struct path *path); extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty); -- 2.5.5 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/3] namei: add LOOKUP_DFD_ROOT to use dfd as root 2016-06-28 17:38 ` [PATCH 1/3] namei: add LOOKUP_DFD_ROOT to use dfd as root Andrey Vagin @ 2016-07-02 0:55 ` Omar Sandoval 2016-07-05 21:26 ` [PATCH 1/3 v2] " Andrey Vagin 2016-07-05 21:36 ` [PATCH 1/3] " Andrey Vagin 0 siblings, 2 replies; 8+ messages in thread From: Omar Sandoval @ 2016-07-02 0:55 UTC (permalink / raw) To: Andrey Vagin Cc: linux-fsdevel, Miklos Szeredi, J. Bruce Fields, Arnd Bergmann, NeilBrown, containers, linux-kernel, Shuah Khan, criu, Eric W. Biederman, Alexander Viro On Tue, Jun 28, 2016 at 10:38:28AM -0700, Andrey Vagin wrote: > The problem is that a pathname can contain absolute symlinks and now > they are resolved relative to the current root. > > If we want to open a file in another mount namespaces and we have a file > descriptor to its root directory, we probably want to resolve pathname > in the target mount namespace. For this we add this new flag. > > If LOOKUP_DFD_ROOT is set, path_init() initializes nd->root and nd->path > to the same value. > > Signed-off-by: Andrey Vagin <avagin@openvz.org> Hi, Andrey, Seems like a useful feature. Make sure to cc linux-api@vger.kernel.org for new userspace interfaces. One comment on the implementation below. > --- > fs/namei.c | 12 +++++++++++- > include/linux/namei.h | 2 ++ > 2 files changed, 13 insertions(+), 1 deletion(-) > > diff --git a/fs/namei.c b/fs/namei.c > index 70580ab..5f08b69 100644 > --- a/fs/namei.c > +++ b/fs/namei.c > @@ -2148,7 +2148,7 @@ static const char *path_init(struct nameidata *nd, unsigned flags) > nd->path.dentry = NULL; > > nd->m_seq = read_seqbegin(&mount_lock); > - if (*s == '/') { > + if (*s == '/' && !(flags & LOOKUP_DFD_ROOT)) { > if (flags & LOOKUP_RCU) > rcu_read_lock(); > set_root(nd); > @@ -2174,6 +2174,11 @@ static const char *path_init(struct nameidata *nd, unsigned flags) > get_fs_pwd(current->fs, &nd->path); > nd->inode = nd->path.dentry->d_inode; > } > + if (flags & LOOKUP_DFD_ROOT) { > + nd->root = nd->path; > + if (!(flags & LOOKUP_RCU)) > + path_get(&nd->root); You're not initializing nd->root_seq here. That means that if we end up going through unlazy_walk(), we're going to call legitimize_path() (and thus read_seqcount_retry()) with stack garbage, get a spurious ECHILD, and do an unnecessary restart of the path lookup instead of dropping into ref-walk mode. > + } > return s; > } else { > /* Caller must check execute permissions on the starting path component */ > @@ -2202,6 +2207,11 @@ static const char *path_init(struct nameidata *nd, unsigned flags) > nd->inode = nd->path.dentry->d_inode; > } > fdput(f); > + if (flags & LOOKUP_DFD_ROOT) { > + nd->root = nd->path; > + if (!(flags & LOOKUP_RCU)) > + path_get(&nd->root); > + } Same here. The following should do the trick: diff --git a/fs/namei.c b/fs/namei.c index 9958b605e822..101d1fb8d3cb 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2176,7 +2176,9 @@ static const char *path_init(struct nameidata *nd, unsigned flags) } if (flags & LOOKUP_DFD_ROOT) { nd->root = nd->path; - if (!(flags & LOOKUP_RCU)) + if (flags & LOOKUP_RCU) + nd->root_seq = nd->seq; + else path_get(&nd->root); } return s; @@ -2209,7 +2211,9 @@ static const char *path_init(struct nameidata *nd, unsigned flags) fdput(f); if (flags & LOOKUP_DFD_ROOT) { nd->root = nd->path; - if (!(flags & LOOKUP_RCU)) + if (flags & LOOKUP_RCU) + nd->root_seq = nd->seq; + else path_get(&nd->root); } return s; -- Omar ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/3 v2] namei: add LOOKUP_DFD_ROOT to use dfd as root 2016-07-02 0:55 ` Omar Sandoval @ 2016-07-05 21:26 ` Andrey Vagin 2016-07-05 21:36 ` [PATCH 1/3] " Andrey Vagin 1 sibling, 0 replies; 8+ messages in thread From: Andrey Vagin @ 2016-07-05 21:26 UTC (permalink / raw) To: linux-fsdevel Cc: linux-kernel, criu, containers, Alexander Viro, Eric W. Biederman, Arnd Bergmann, J. Bruce Fields, Miklos Szeredi, NeilBrown, Shuah Khan, Omar Sandoval, Andrey Vagin The problem is that a pathname can contain absolute symlinks and now they are resolved relative to the current root. If we want to open a file in another mount namespaces and we have a file descriptor to its root directory, we probably want to resolve pathname in the target mount namespace. For this we add this new flag. If LOOKUP_DFD_ROOT is set, path_init() initializes nd->root and nd->path to the same value. Changes since v1: * initialize nd->root_seq (thanks to Omar Sandoval for reporting and fixing this issue) Cc: Omar Sandoval <osandov@osandov.com> Signed-off-by: Andrey Vagin <avagin@openvz.org> --- fs/namei.c | 16 +++++++++++++++- include/linux/namei.h | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/fs/namei.c b/fs/namei.c index 70580ab..d52541a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2148,7 +2148,7 @@ static const char *path_init(struct nameidata *nd, unsigned flags) nd->path.dentry = NULL; nd->m_seq = read_seqbegin(&mount_lock); - if (*s == '/') { + if (*s == '/' && !(flags & LOOKUP_DFD_ROOT)) { if (flags & LOOKUP_RCU) rcu_read_lock(); set_root(nd); @@ -2174,6 +2174,13 @@ static const char *path_init(struct nameidata *nd, unsigned flags) get_fs_pwd(current->fs, &nd->path); nd->inode = nd->path.dentry->d_inode; } + if (flags & LOOKUP_DFD_ROOT) { + nd->root = nd->path; + if (flags & LOOKUP_RCU) + nd->root_seq = nd->seq; + else + path_get(&nd->root); + } return s; } else { /* Caller must check execute permissions on the starting path component */ @@ -2202,6 +2209,13 @@ static const char *path_init(struct nameidata *nd, unsigned flags) nd->inode = nd->path.dentry->d_inode; } fdput(f); + if (flags & LOOKUP_DFD_ROOT) { + nd->root = nd->path; + if (flags & LOOKUP_RCU) + nd->root_seq = nd->seq; + else + path_get(&nd->root); + } return s; } } diff --git a/include/linux/namei.h b/include/linux/namei.h index d3d0398..ca2ea13 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -45,6 +45,8 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND}; #define LOOKUP_ROOT 0x2000 #define LOOKUP_EMPTY 0x4000 +#define LOOKUP_DFD_ROOT 0x8000 + extern int path_pts(struct path *path); extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty); -- 2.5.5 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/3] namei: add LOOKUP_DFD_ROOT to use dfd as root 2016-07-02 0:55 ` Omar Sandoval 2016-07-05 21:26 ` [PATCH 1/3 v2] " Andrey Vagin @ 2016-07-05 21:36 ` Andrey Vagin 1 sibling, 0 replies; 8+ messages in thread From: Andrey Vagin @ 2016-07-05 21:36 UTC (permalink / raw) To: Omar Sandoval Cc: Miklos Szeredi, J. Bruce Fields, Arnd Bergmann, NeilBrown, Linux Containers, LKML, Shuah Khan, criu, Eric W. Biederman, linux-fsdevel, Alexander Viro On Fri, Jul 1, 2016 at 5:55 PM, Omar Sandoval <osandov@osandov.com> wrote: > On Tue, Jun 28, 2016 at 10:38:28AM -0700, Andrey Vagin wrote: >> The problem is that a pathname can contain absolute symlinks and now >> they are resolved relative to the current root. >> >> If we want to open a file in another mount namespaces and we have a file >> descriptor to its root directory, we probably want to resolve pathname >> in the target mount namespace. For this we add this new flag. >> >> If LOOKUP_DFD_ROOT is set, path_init() initializes nd->root and nd->path >> to the same value. >> >> Signed-off-by: Andrey Vagin <avagin@openvz.org> > > Hi, Andrey, > > Seems like a useful feature. Make sure to cc linux-api@vger.kernel.org > for new userspace interfaces. One comment on the implementation below. Hi Omar, Thank you for the patch. I have sent a new version with your changes. If there will not be other comments in a few days, I will resent a whole seires with linux-api@vger.kernel.org in CC. Thanks, Andrew > >> --- >> fs/namei.c | 12 +++++++++++- >> include/linux/namei.h | 2 ++ >> 2 files changed, 13 insertions(+), 1 deletion(-) >> >> diff --git a/fs/namei.c b/fs/namei.c >> index 70580ab..5f08b69 100644 >> --- a/fs/namei.c >> +++ b/fs/namei.c >> @@ -2148,7 +2148,7 @@ static const char *path_init(struct nameidata *nd, unsigned flags) >> nd->path.dentry = NULL; >> >> nd->m_seq = read_seqbegin(&mount_lock); >> - if (*s == '/') { >> + if (*s == '/' && !(flags & LOOKUP_DFD_ROOT)) { >> if (flags & LOOKUP_RCU) >> rcu_read_lock(); >> set_root(nd); >> @@ -2174,6 +2174,11 @@ static const char *path_init(struct nameidata *nd, unsigned flags) >> get_fs_pwd(current->fs, &nd->path); >> nd->inode = nd->path.dentry->d_inode; >> } >> + if (flags & LOOKUP_DFD_ROOT) { >> + nd->root = nd->path; >> + if (!(flags & LOOKUP_RCU)) >> + path_get(&nd->root); > > You're not initializing nd->root_seq here. That means that if we end up > going through unlazy_walk(), we're going to call legitimize_path() (and > thus read_seqcount_retry()) with stack garbage, get a spurious ECHILD, > and do an unnecessary restart of the path lookup instead of dropping > into ref-walk mode. > >> + } >> return s; >> } else { >> /* Caller must check execute permissions on the starting path component */ >> @@ -2202,6 +2207,11 @@ static const char *path_init(struct nameidata *nd, unsigned flags) >> nd->inode = nd->path.dentry->d_inode; >> } >> fdput(f); >> + if (flags & LOOKUP_DFD_ROOT) { >> + nd->root = nd->path; >> + if (!(flags & LOOKUP_RCU)) >> + path_get(&nd->root); >> + } > > Same here. > > The following should do the trick: > > diff --git a/fs/namei.c b/fs/namei.c > index 9958b605e822..101d1fb8d3cb 100644 > --- a/fs/namei.c > +++ b/fs/namei.c > @@ -2176,7 +2176,9 @@ static const char *path_init(struct nameidata *nd, unsigned flags) > } > if (flags & LOOKUP_DFD_ROOT) { > nd->root = nd->path; > - if (!(flags & LOOKUP_RCU)) > + if (flags & LOOKUP_RCU) > + nd->root_seq = nd->seq; > + else > path_get(&nd->root); > } > return s; > @@ -2209,7 +2211,9 @@ static const char *path_init(struct nameidata *nd, unsigned flags) > fdput(f); > if (flags & LOOKUP_DFD_ROOT) { > nd->root = nd->path; > - if (!(flags & LOOKUP_RCU)) > + if (flags & LOOKUP_RCU) > + nd->root_seq = nd->seq; > + else > path_get(&nd->root); > } > return s; > > -- > Omar > _______________________________________________ > Containers mailing list > Containers@lists.linux-foundation.org > https://lists.linuxfoundation.org/mailman/listinfo/containers ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/3] fs: allow to use dirfd as root for openat and other *at syscalls 2016-06-28 17:38 [PATCH 0/3 v2] fs: allow to use dirfd as root for openat and other *at syscalls Andrey Vagin 2016-06-28 17:38 ` [PATCH 1/3] namei: add LOOKUP_DFD_ROOT to use dfd as root Andrey Vagin @ 2016-06-28 17:38 ` Andrey Vagin 2016-06-28 17:38 ` [PATCH 3/3] selftests: check O_ATROOT and AT_FDROOT flags Andrey Vagin 2 siblings, 0 replies; 8+ messages in thread From: Andrey Vagin @ 2016-06-28 17:38 UTC (permalink / raw) To: linux-fsdevel Cc: linux-kernel, criu, containers, Andrey Vagin, Alexander Viro, Eric W. Biederman, Arnd Bergmann, J. Bruce Fields, Miklos Szeredi, NeilBrown, Shuah Khan The problem is that a pathname can contain absolute symlinks and now they are resolved relative to the current root. But if we want to open a file in another mount namespace and we have a file descriptor to its root directory, we want that the pathname is resolved in the target mount namespace and in this case we need these new flags O_ATROOT or AT_FDROOT. If O_ATROOT is set for openat() or AT_FDROOT is set for fstatat, linkat, unlinkat, path_init is executed with the LOOKUP_DFD_ROOT flag. v2: fix a value of O_ATROOT to not intersect with other constans Signed-off-by: Andrey Vagin <avagin@openvz.org> --- fs/exec.c | 4 +++- fs/namei.c | 26 +++++++++++++++++--------- fs/open.c | 6 +++++- fs/stat.c | 4 +++- fs/utimes.c | 4 +++- include/uapi/asm-generic/fcntl.h | 4 ++++ include/uapi/linux/fcntl.h | 1 + 7 files changed, 36 insertions(+), 13 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 887c1c9..473b709 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -775,12 +775,14 @@ static struct file *do_open_execat(int fd, struct filename *name, int flags) .lookup_flags = LOOKUP_FOLLOW, }; - if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0) + if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH | AT_FDROOT)) != 0) return ERR_PTR(-EINVAL); if (flags & AT_SYMLINK_NOFOLLOW) open_exec_flags.lookup_flags &= ~LOOKUP_FOLLOW; if (flags & AT_EMPTY_PATH) open_exec_flags.lookup_flags |= LOOKUP_EMPTY; + if (flags & AT_FDROOT) + open_exec_flags.lookup_flags |= LOOKUP_DFD_ROOT; file = do_filp_open(fd, name, &open_exec_flags); if (IS_ERR(file)) diff --git a/fs/namei.c b/fs/namei.c index 5f08b69..9958b60 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2581,7 +2581,8 @@ user_path_parent(int dfd, const char __user *path, unsigned int flags) { /* only LOOKUP_REVAL is allowed in extra flags */ - return filename_parentat(dfd, getname(path), flags & LOOKUP_REVAL, + return filename_parentat(dfd, getname(path), + flags & (LOOKUP_REVAL | LOOKUP_DFD_ROOT), parent, last, type); } @@ -3577,7 +3578,7 @@ static struct dentry *filename_create(int dfd, struct filename *name, * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any * other flags passed in are ignored! */ - lookup_flags &= LOOKUP_REVAL; + lookup_flags &= LOOKUP_REVAL | LOOKUP_DFD_ROOT; name = filename_parentat(dfd, name, lookup_flags, path, &last, &type); if (IS_ERR(name)) @@ -3975,7 +3976,8 @@ EXPORT_SYMBOL(vfs_unlink); * writeout happening, and we don't want to prevent access to the directory * while waiting on the I/O. */ -static long do_unlinkat(int dfd, const char __user *pathname) +static long do_unlinkat(int dfd, const char __user *pathname, + unsigned int lookup_flags) { int error; struct filename *name; @@ -3985,7 +3987,6 @@ static long do_unlinkat(int dfd, const char __user *pathname) int type; struct inode *inode = NULL; struct inode *delegated_inode = NULL; - unsigned int lookup_flags = 0; retry: name = user_path_parent(dfd, pathname, &path, &last, &type, lookup_flags); @@ -4050,18 +4051,23 @@ slashes: SYSCALL_DEFINE3(unlinkat, int, dfd, const char __user *, pathname, int, flag) { - if ((flag & ~AT_REMOVEDIR) != 0) + unsigned int lookup_flags = 0; + + if ((flag & ~(AT_REMOVEDIR | AT_FDROOT)) != 0) return -EINVAL; if (flag & AT_REMOVEDIR) return do_rmdir(dfd, pathname); - return do_unlinkat(dfd, pathname); + if (flag & AT_FDROOT) + lookup_flags |= LOOKUP_DFD_ROOT; + + return do_unlinkat(dfd, pathname, lookup_flags); } SYSCALL_DEFINE1(unlink, const char __user *, pathname) { - return do_unlinkat(AT_FDCWD, pathname); + return do_unlinkat(AT_FDCWD, pathname, 0); } int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname) @@ -4212,7 +4218,7 @@ SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname, int how = 0; int error; - if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) != 0) + if ((flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH | AT_FDROOT)) != 0) return -EINVAL; /* * To use null names we require CAP_DAC_READ_SEARCH @@ -4227,13 +4233,15 @@ SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname, if (flags & AT_SYMLINK_FOLLOW) how |= LOOKUP_FOLLOW; + if (flags & AT_FDROOT) + how |= LOOKUP_DFD_ROOT; retry: error = user_path_at(olddfd, oldname, how, &old_path); if (error) return error; new_dentry = user_path_create(newdfd, newname, &new_path, - (how & LOOKUP_REVAL)); + (how & (LOOKUP_REVAL | LOOKUP_DFD_ROOT))); error = PTR_ERR(new_dentry); if (IS_ERR(new_dentry)) goto out; diff --git a/fs/open.c b/fs/open.c index 93ae3cd..e0bc8d0 100644 --- a/fs/open.c +++ b/fs/open.c @@ -613,12 +613,14 @@ SYSCALL_DEFINE5(fchownat, int, dfd, const char __user *, filename, uid_t, user, int error = -EINVAL; int lookup_flags; - if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0) + if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH | AT_FDROOT)) != 0) goto out; lookup_flags = (flag & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW; if (flag & AT_EMPTY_PATH) lookup_flags |= LOOKUP_EMPTY; + if (flag & AT_FDROOT) + lookup_flags |= LOOKUP_DFD_ROOT; retry: error = user_path_at(dfd, filename, lookup_flags, &path); if (error) @@ -941,6 +943,8 @@ static inline int build_open_flags(int flags, umode_t mode, struct open_flags *o lookup_flags |= LOOKUP_DIRECTORY; if (!(flags & O_NOFOLLOW)) lookup_flags |= LOOKUP_FOLLOW; + if (flags & O_ATROOT) + lookup_flags |= LOOKUP_DFD_ROOT; op->lookup_flags = lookup_flags; return 0; } diff --git a/fs/stat.c b/fs/stat.c index bc045c7..d71e7f2 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -95,13 +95,15 @@ int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat, unsigned int lookup_flags = 0; if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | - AT_EMPTY_PATH)) != 0) + AT_EMPTY_PATH | AT_FDROOT)) != 0) goto out; if (!(flag & AT_SYMLINK_NOFOLLOW)) lookup_flags |= LOOKUP_FOLLOW; if (flag & AT_EMPTY_PATH) lookup_flags |= LOOKUP_EMPTY; + if (flag & AT_FDROOT) + lookup_flags |= LOOKUP_DFD_ROOT; retry: error = user_path_at(dfd, filename, lookup_flags, &path); if (error) diff --git a/fs/utimes.c b/fs/utimes.c index 85c40f4..78a9eb9 100644 --- a/fs/utimes.c +++ b/fs/utimes.c @@ -143,7 +143,7 @@ long do_utimes(int dfd, const char __user *filename, struct timespec *times, goto out; } - if (flags & ~AT_SYMLINK_NOFOLLOW) + if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_FDROOT)) goto out; if (filename == NULL && dfd != AT_FDCWD) { @@ -165,6 +165,8 @@ long do_utimes(int dfd, const char __user *filename, struct timespec *times, if (!(flags & AT_SYMLINK_NOFOLLOW)) lookup_flags |= LOOKUP_FOLLOW; + if (flags & AT_FDROOT) + lookup_flags |= LOOKUP_DFD_ROOT; retry: error = user_path_at(dfd, filename, lookup_flags, &path); if (error) diff --git a/include/uapi/asm-generic/fcntl.h b/include/uapi/asm-generic/fcntl.h index e063eff..0436b1d 100644 --- a/include/uapi/asm-generic/fcntl.h +++ b/include/uapi/asm-generic/fcntl.h @@ -88,6 +88,10 @@ #define __O_TMPFILE 020000000 #endif +#ifndef O_ATROOT +#define O_ATROOT 040000000 /* dfd is a root */ +#endif + /* a horrid kludge trying to make sure that this will fail on old kernels */ #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) #define O_TMPFILE_MASK (__O_TMPFILE | O_DIRECTORY | O_CREAT) diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h index beed138..4f3b631 100644 --- a/include/uapi/linux/fcntl.h +++ b/include/uapi/linux/fcntl.h @@ -62,6 +62,7 @@ #define AT_SYMLINK_FOLLOW 0x400 /* Follow symbolic links. */ #define AT_NO_AUTOMOUNT 0x800 /* Suppress terminal automount traversal */ #define AT_EMPTY_PATH 0x1000 /* Allow empty relative pathname */ +#define AT_FDROOT 0x2000 /* Resolve a path as if dirfd is root */ #endif /* _UAPI_LINUX_FCNTL_H */ -- 2.5.5 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 3/3] selftests: check O_ATROOT and AT_FDROOT flags 2016-06-28 17:38 [PATCH 0/3 v2] fs: allow to use dirfd as root for openat and other *at syscalls Andrey Vagin 2016-06-28 17:38 ` [PATCH 1/3] namei: add LOOKUP_DFD_ROOT to use dfd as root Andrey Vagin 2016-06-28 17:38 ` [PATCH 2/3] fs: allow to use dirfd as root for openat and other *at syscalls Andrey Vagin @ 2016-06-28 17:38 ` Andrey Vagin 2 siblings, 0 replies; 8+ messages in thread From: Andrey Vagin @ 2016-06-28 17:38 UTC (permalink / raw) To: linux-fsdevel Cc: linux-kernel, criu, containers, Andrey Vagin, Alexander Viro, Eric W. Biederman, Arnd Bergmann, J. Bruce Fields, Miklos Szeredi, NeilBrown, Shuah Khan These flags mean that the first argument "dirfd" of *at syscall-s set as root for the current operation. With these flags absolute symlinks have to be resolved relative to dirfd. This test create a file and an absolute symlink on it, which can be resoled only if a proper root is set. Signed-off-by: Andrey Vagin <avagin@openvz.org> --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/lookup/.gitignore | 1 + tools/testing/selftests/lookup/Makefile | 8 +++ tools/testing/selftests/lookup/lookup_at_root.c | 71 +++++++++++++++++++++++++ tools/testing/selftests/lookup/run.sh | 14 +++++ 5 files changed, 95 insertions(+) create mode 100644 tools/testing/selftests/lookup/.gitignore create mode 100644 tools/testing/selftests/lookup/Makefile create mode 100644 tools/testing/selftests/lookup/lookup_at_root.c create mode 100755 tools/testing/selftests/lookup/run.sh diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index ff9e5f2..72555c8 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -9,6 +9,7 @@ TARGETS += futex TARGETS += ipc TARGETS += kcmp TARGETS += lib +TARGETS += lookup TARGETS += membarrier TARGETS += memfd TARGETS += memory-hotplug diff --git a/tools/testing/selftests/lookup/.gitignore b/tools/testing/selftests/lookup/.gitignore new file mode 100644 index 0000000..c9a963f --- /dev/null +++ b/tools/testing/selftests/lookup/.gitignore @@ -0,0 +1 @@ +lookup_at_root diff --git a/tools/testing/selftests/lookup/Makefile b/tools/testing/selftests/lookup/Makefile new file mode 100644 index 0000000..042b26f --- /dev/null +++ b/tools/testing/selftests/lookup/Makefile @@ -0,0 +1,8 @@ +TEST_PROGS := run.sh + +all: lookup_at_root + +clean: + $(RM) lookup_at_root +include ../lib.mk + diff --git a/tools/testing/selftests/lookup/lookup_at_root.c b/tools/testing/selftests/lookup/lookup_at_root.c new file mode 100644 index 0000000..6723dc8 --- /dev/null +++ b/tools/testing/selftests/lookup/lookup_at_root.c @@ -0,0 +1,71 @@ +#include <unistd.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/prctl.h> +#include <linux/limits.h> + +#define pr_err(fmt, ...) \ + ({ \ + fprintf(stderr, "%s:%d:" fmt ": %m\n", \ + __func__, __LINE__, ##__VA_ARGS__); \ + 1; \ + }) + +#ifndef O_ATROOT +#define O_ATROOT 040000000 /* dfd is a root */ +#endif +#ifndef AT_FDROOT +#define AT_FDROOT 0x2000 /* Resolve a path as if dirfd is root */ +#endif + +int main(int argc, char **argv) +{ + struct stat st; + int fd, dfd; + char path[PATH_MAX]; + + dfd = open(argv[1], O_RDONLY); + if (dfd < 0) + return pr_err("open"); + + snprintf(path, sizeof(path), "%s/test", argv[1]); + if (mkdir(path, 755)) + return pr_err("mkdir"); + + if (symlinkat("/test", dfd, "./test.link")) + return pr_err("symlinkat"); + + fd = openat(dfd, "test.link", O_RDONLY | O_ATROOT); + if (fd < 0) + return pr_err("open"); + + if (fchdir(dfd)) + return pr_err("fchdir"); + + fd = openat(AT_FDCWD, "test.link", O_RDONLY | O_ATROOT); + if (fd < 0) + return pr_err("open"); + close(fd); + + fd = openat(AT_FDCWD, "/test.link", O_RDONLY | O_ATROOT); + if (fd < 0) + return pr_err("open"); + close(fd); + + if (fstatat(AT_FDCWD, "test.link", &st, AT_FDROOT)) + return pr_err("fstatat"); + if (fstatat(dfd, "test.link", &st, AT_FDROOT)) + return pr_err("fstatat"); + if (mknodat(dfd, "./test/test.file", 0644 | S_IFREG, 0)) + return pr_err("mknod"); + if (linkat(dfd, "./test.link/test.file", + dfd, "./test.link/test.file.link", AT_FDROOT)) + return pr_err("linkat"); + if (unlinkat(dfd, "./test.link/test.file.link", AT_FDROOT)) + return pr_err("unlinkat"); + + return 0; +} diff --git a/tools/testing/selftests/lookup/run.sh b/tools/testing/selftests/lookup/run.sh new file mode 100755 index 0000000..86cc51b --- /dev/null +++ b/tools/testing/selftests/lookup/run.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +test_dir=`mktemp -d /tmp/lookup_test.XXXXXX` +mount -t tmpfs lookup_at_root $test_dir + +ret=0 +./lookup_at_root $test_dir || ret=$? + +umount $test_dir +rmdir $test_dir + +exit $ret -- 2.5.5 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 0/3 v3] fs: allow to use dirfd as root for openat and other *at syscalls
@ 2016-07-20 20:42 Andrey Vagin
2016-07-20 20:42 ` [PATCH 3/3] selftests: check O_ATROOT and AT_FDROOT flags Andrey Vagin
0 siblings, 1 reply; 8+ messages in thread
From: Andrey Vagin @ 2016-07-20 20:42 UTC (permalink / raw)
To: linux-fsdevel, Alexander Viro
Cc: linux-kernel, linux-api, containers, criu, Andrey Vagin,
Eric W. Biederman, Arnd Bergmann, J. Bruce Fields,
Miklos Szeredi, NeilBrown, Shuah Khan, Omar Sandoval
The problem is that a pathname can contain absolute symlinks and now
they are resolved relative to the current root.
It may be a problem if you want to open a file in another namespace.
For example, you open /proc/PID/root for a process from the target
namespace and then you use openat() to open a file from this namespace.
If a path to the file contains an absolute symlink, you will open a file
from the current namespace, because a symlink will be resolved relative
to the current root.
A proposed solution adds a new flag which means that dirfd should be
set as a root for a current system call (openat(), statat(), etc).
Here are examples how we can open a file in a contex of another process.
How we can do this without these changes:
old_root = open("/", O_PATH);
old_cwd = open(".", O_PATH);
chroot("/proc/PID/root");
fd = open(pathname, O_RDONLY);
fchdir(old_root); /* emulate fchroot() */
chroot(".");
fchdir(old_cwd);
close(old_cwd);
close(old_root);
How this code is simplified with new flags:
dirfd = open("/proc/PID/root", O_PATH);
fd = open(dirfd, pathname, O_RDONLY | O_ATROOT);
close(dirfd);
One more thing is that chroot isn't available for unprivileged users.
We met this problem, when we tryed to dump an ubuntu container and
failed to resolve /proc/PID/root/var/run/mysqld/mysqld.sock, because
/var/run was a symlink to /run.
Changes since the first version:
- change a value of O_ATROOT to not intersect with other constants.
Changes since the second version:
- initialize nd->root_seq (thanks to Omar Sandoval for reporting and
fixing this issue)
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: "J. Bruce Fields" <bfields@redhat.com>
Cc: Miklos Szeredi <mszeredi@redhat.com>
Cc: NeilBrown <neilb@suse.de>
Cc: Shuah Khan <shuahkh@osg.samsung.com>
Cc: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Andrey Vagin <avagin@openvz.org>
Andrey Vagin (3):
namei: add LOOKUP_DFD_ROOT to use dfd as root
fs: allow to use dirfd as root for openat and other *at syscalls
selftests: check O_ATROOT and AT_FDROOT flags
fs/exec.c | 4 +-
fs/namei.c | 42 +++++++++++----
fs/open.c | 6 ++-
fs/stat.c | 4 +-
fs/utimes.c | 4 +-
include/linux/namei.h | 2 +
include/uapi/asm-generic/fcntl.h | 4 ++
include/uapi/linux/fcntl.h | 1 +
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/lookup/.gitignore | 1 +
tools/testing/selftests/lookup/Makefile | 8 +++
tools/testing/selftests/lookup/lookup_at_root.c | 71 +++++++++++++++++++++++++
tools/testing/selftests/lookup/run.sh | 14 +++++
13 files changed, 148 insertions(+), 14 deletions(-)
create mode 100644 tools/testing/selftests/lookup/.gitignore
create mode 100644 tools/testing/selftests/lookup/Makefile
create mode 100644 tools/testing/selftests/lookup/lookup_at_root.c
create mode 100755 tools/testing/selftests/lookup/run.sh
--
2.5.5
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 3/3] selftests: check O_ATROOT and AT_FDROOT flags 2016-07-20 20:42 [PATCH 0/3 v3] fs: allow to use dirfd as root for openat and other *at syscalls Andrey Vagin @ 2016-07-20 20:42 ` Andrey Vagin 0 siblings, 0 replies; 8+ messages in thread From: Andrey Vagin @ 2016-07-20 20:42 UTC (permalink / raw) To: linux-fsdevel, Alexander Viro Cc: linux-kernel, linux-api, containers, criu, Andrey Vagin, Eric W. Biederman, Arnd Bergmann, J. Bruce Fields, Miklos Szeredi, NeilBrown, Shuah Khan, Omar Sandoval These flags mean that the first argument "dirfd" of *at syscall-s set as root for the current operation. With these flags absolute symlinks have to be resolved relative to dirfd. This test create a file and an absolute symlink on it, which can be resoled only if a proper root is set. Signed-off-by: Andrey Vagin <avagin@openvz.org> --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/lookup/.gitignore | 1 + tools/testing/selftests/lookup/Makefile | 8 +++ tools/testing/selftests/lookup/lookup_at_root.c | 71 +++++++++++++++++++++++++ tools/testing/selftests/lookup/run.sh | 14 +++++ 5 files changed, 95 insertions(+) create mode 100644 tools/testing/selftests/lookup/.gitignore create mode 100644 tools/testing/selftests/lookup/Makefile create mode 100644 tools/testing/selftests/lookup/lookup_at_root.c create mode 100755 tools/testing/selftests/lookup/run.sh diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index ff9e5f2..72555c8 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -9,6 +9,7 @@ TARGETS += futex TARGETS += ipc TARGETS += kcmp TARGETS += lib +TARGETS += lookup TARGETS += membarrier TARGETS += memfd TARGETS += memory-hotplug diff --git a/tools/testing/selftests/lookup/.gitignore b/tools/testing/selftests/lookup/.gitignore new file mode 100644 index 0000000..c9a963f --- /dev/null +++ b/tools/testing/selftests/lookup/.gitignore @@ -0,0 +1 @@ +lookup_at_root diff --git a/tools/testing/selftests/lookup/Makefile b/tools/testing/selftests/lookup/Makefile new file mode 100644 index 0000000..042b26f --- /dev/null +++ b/tools/testing/selftests/lookup/Makefile @@ -0,0 +1,8 @@ +TEST_PROGS := run.sh + +all: lookup_at_root + +clean: + $(RM) lookup_at_root +include ../lib.mk + diff --git a/tools/testing/selftests/lookup/lookup_at_root.c b/tools/testing/selftests/lookup/lookup_at_root.c new file mode 100644 index 0000000..6723dc8 --- /dev/null +++ b/tools/testing/selftests/lookup/lookup_at_root.c @@ -0,0 +1,71 @@ +#include <unistd.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/prctl.h> +#include <linux/limits.h> + +#define pr_err(fmt, ...) \ + ({ \ + fprintf(stderr, "%s:%d:" fmt ": %m\n", \ + __func__, __LINE__, ##__VA_ARGS__); \ + 1; \ + }) + +#ifndef O_ATROOT +#define O_ATROOT 040000000 /* dfd is a root */ +#endif +#ifndef AT_FDROOT +#define AT_FDROOT 0x2000 /* Resolve a path as if dirfd is root */ +#endif + +int main(int argc, char **argv) +{ + struct stat st; + int fd, dfd; + char path[PATH_MAX]; + + dfd = open(argv[1], O_RDONLY); + if (dfd < 0) + return pr_err("open"); + + snprintf(path, sizeof(path), "%s/test", argv[1]); + if (mkdir(path, 755)) + return pr_err("mkdir"); + + if (symlinkat("/test", dfd, "./test.link")) + return pr_err("symlinkat"); + + fd = openat(dfd, "test.link", O_RDONLY | O_ATROOT); + if (fd < 0) + return pr_err("open"); + + if (fchdir(dfd)) + return pr_err("fchdir"); + + fd = openat(AT_FDCWD, "test.link", O_RDONLY | O_ATROOT); + if (fd < 0) + return pr_err("open"); + close(fd); + + fd = openat(AT_FDCWD, "/test.link", O_RDONLY | O_ATROOT); + if (fd < 0) + return pr_err("open"); + close(fd); + + if (fstatat(AT_FDCWD, "test.link", &st, AT_FDROOT)) + return pr_err("fstatat"); + if (fstatat(dfd, "test.link", &st, AT_FDROOT)) + return pr_err("fstatat"); + if (mknodat(dfd, "./test/test.file", 0644 | S_IFREG, 0)) + return pr_err("mknod"); + if (linkat(dfd, "./test.link/test.file", + dfd, "./test.link/test.file.link", AT_FDROOT)) + return pr_err("linkat"); + if (unlinkat(dfd, "./test.link/test.file.link", AT_FDROOT)) + return pr_err("unlinkat"); + + return 0; +} diff --git a/tools/testing/selftests/lookup/run.sh b/tools/testing/selftests/lookup/run.sh new file mode 100755 index 0000000..86cc51b --- /dev/null +++ b/tools/testing/selftests/lookup/run.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +test_dir=`mktemp -d /tmp/lookup_test.XXXXXX` +mount -t tmpfs lookup_at_root $test_dir + +ret=0 +./lookup_at_root $test_dir || ret=$? + +umount $test_dir +rmdir $test_dir + +exit $ret -- 2.5.5 ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2016-07-20 20:44 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2016-06-28 17:38 [PATCH 0/3 v2] fs: allow to use dirfd as root for openat and other *at syscalls Andrey Vagin 2016-06-28 17:38 ` [PATCH 1/3] namei: add LOOKUP_DFD_ROOT to use dfd as root Andrey Vagin 2016-07-02 0:55 ` Omar Sandoval 2016-07-05 21:26 ` [PATCH 1/3 v2] " Andrey Vagin 2016-07-05 21:36 ` [PATCH 1/3] " Andrey Vagin 2016-06-28 17:38 ` [PATCH 2/3] fs: allow to use dirfd as root for openat and other *at syscalls Andrey Vagin 2016-06-28 17:38 ` [PATCH 3/3] selftests: check O_ATROOT and AT_FDROOT flags Andrey Vagin 2016-07-20 20:42 [PATCH 0/3 v3] fs: allow to use dirfd as root for openat and other *at syscalls Andrey Vagin 2016-07-20 20:42 ` [PATCH 3/3] selftests: check O_ATROOT and AT_FDROOT flags Andrey Vagin
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®