[PATCH 21/34] open: handle idmapped mounts

Christian Brauner
2020-10-29T00:42:15Z

For core file operations such as changing directories or chrooting, determining
file access, changing mode or ownership the vfs will verify that the caller is
privileged over the inode. Extend the various helpers to handle idmapped
mounts. If the inode is accessed through an idmapped mount it is mapped
according to the mount's user namespace. Afterwards the permissions checks are
identical to non-idmapped mounts. When changing file ownership we need to map
the mount from the mount's user namespace. If the initial user namespace is
passed all mapping operations are a nop so non-idmapped mounts will not see a
change in behavior and will also not see any performance impact.

Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
fs/open.c | 31 ++++++++++++++++++++++++-------
1 file changed, 24 insertions(+), 7 deletions(-)

diff --git a/fs/open.c b/fs/open.c
index efa462b6b9c7..ca113399010a 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -401,6 +401,7 @@ static const struct cred *access_override_creds(void)

static long do_faccessat(int dfd, const char __user *filename, int mode, int flags)
{
+ struct user_namespace *user_ns;
struct path path;
struct inode *inode;
int res;
@@ -441,7 +442,8 @@ static long do_faccessat(int dfd, const char __user *filename, int mode, int fla
goto out_path_release;
}

- res = inode_permission(inode, mode | MAY_ACCESS);
+ user_ns = mnt_user_ns(path.mnt);
+ res = mapped_inode_permission(user_ns, inode, mode | MAY_ACCESS);
/* SuS v2 requires we report a read only fs too */
if (res || !(mode & S_IWOTH) || special_file(inode->i_mode))
goto out_path_release;
@@ -489,6 +491,7 @@ SYSCALL_DEFINE2(access, const char __user *, filename, int, mode)

SYSCALL_DEFINE1(chdir, const char __user *, filename)
{
+ struct user_namespace *user_ns;
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
@@ -497,7 +500,8 @@ SYSCALL_DEFINE1(chdir, const char __user *, filename)
if (error)
goto out;

- error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
+ user_ns = mnt_user_ns(path.mnt);
+ error = mapped_inode_permission(user_ns, path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
if (error)
goto dput_and_out;

@@ -515,6 +519,7 @@ SYSCALL_DEFINE1(chdir, const char __user *, filename)

SYSCALL_DEFINE1(fchdir, unsigned int, fd)
{
+ struct user_namespace *user_ns;
struct fd f = fdget_raw(fd);
int error;

@@ -526,7 +531,8 @@ SYSCALL_DEFINE1(fchdir, unsigned int, fd)
if (!d_can_lookup(f.file->f_path.dentry))
goto out_putf;

- error = inode_permission(file_inode(f.file), MAY_EXEC | MAY_CHDIR);
+ user_ns = mnt_user_ns(f.file->f_path.mnt);
+ error = mapped_inode_permission(user_ns, file_inode(f.file), MAY_EXEC | MAY_CHDIR);
if (!error)
set_fs_pwd(current->fs, &f.file->f_path);
out_putf:
@@ -537,6 +543,7 @@ SYSCALL_DEFINE1(fchdir, unsigned int, fd)

SYSCALL_DEFINE1(chroot, const char __user *, filename)
{
+ struct user_namespace *user_ns;
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
@@ -545,7 +552,8 @@ SYSCALL_DEFINE1(chroot, const char __user *, filename)
if (error)
goto out;

- error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
+ user_ns = mnt_user_ns(path.mnt);
+ error = mapped_inode_permission(user_ns, path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
if (error)
goto dput_and_out;

@@ -570,6 +578,7 @@ SYSCALL_DEFINE1(chroot, const char __user *, filename)

int chmod_common(const struct path *path, umode_t mode)
{
+ struct user_namespace *user_ns;
struct inode *inode = path->dentry->d_inode;
struct inode *delegated_inode = NULL;
struct iattr newattrs;
@@ -585,7 +594,8 @@ int chmod_common(const struct path *path, umode_t mode)
goto out_unlock;
newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
- error = notify_change(path->dentry, &newattrs, &delegated_inode);
+ user_ns = mnt_user_ns(path->mnt);
+ error = notify_mapped_change(user_ns, path->dentry, &newattrs, &delegated_inode);
out_unlock:
inode_unlock(inode);
if (delegated_inode) {
@@ -646,6 +656,7 @@ SYSCALL_DEFINE2(chmod, const char __user *, filename, umode_t, mode)

int chown_common(const struct path *path, uid_t user, gid_t group)
{
+ struct user_namespace *user_ns;
struct inode *inode = path->dentry->d_inode;
struct inode *delegated_inode = NULL;
int error;
@@ -656,6 +667,12 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
uid = make_kuid(current_user_ns(), user);
gid = make_kgid(current_user_ns(), group);

+ user_ns = mnt_user_ns(path->mnt);
+ if (mnt_idmapped(path->mnt)) {
+ uid = kuid_from_mnt(user_ns, uid);
+ gid = kgid_from_mnt(user_ns, gid);
+ }
+
retry_deleg:
newattrs.ia_valid = ATTR_CTIME;
if (user != (uid_t) -1) {
@@ -676,7 +693,7 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
inode_lock(inode);
error = security_path_chown(path, uid, gid);
if (!error)
- error = notify_change(path->dentry, &newattrs, &delegated_inode);
+ error = notify_mapped_change(user_ns, path->dentry, &newattrs, &delegated_inode);
inode_unlock(inode);
if (delegated_inode) {
error = break_deleg_wait(&delegated_inode);
@@ -1133,7 +1150,7 @@ struct file *filp_open(const char *filename, int flags, umode_t mode)
{
struct filename *name = getname_kernel(filename);
struct file *file = ERR_CAST(name);
-
+
if (!IS_ERR(name)) {
file = file_open_name(name, flags, mode);
putname(name);
--
2.29.0