Skip to content
Permalink
Browse files
attr: handle idmapped mounts
When file attributes are changed filesystems mostly rely on the
setattr_prepare(), setattr_copy(), and notify_change() helpers for
initialization and permission checking. Let them handle idmapped mounts. If
the inode is accessed through an idmapped mount we need to map it according
to the mount's user namespace. Afterwards the checks are identical to
non-idmapped mounts. If the initial user namespace is passed all operations
are a nop so non-idmapped mounts will not see a change in behavior and will
also not see any performance impact. Helpers that perform checks on the
ia_uid and ia_gid fields in struct iattr assume that ia_uid and ia_gid are
intended values and so they won't be mapped according to the mount's user
namespace. This is more transparent to the caller.
If the initial user namespace is passed all operations are a nop so
non-idmapped mounts will not see a change in behavior and will not see any
performance impact.

Cc: Christoph Hellwig <hch@lst.de>
Cc: David Howells <dhowells@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
  • Loading branch information
brauner authored and intel-lab-lkp committed Nov 15, 2020
1 parent 6a23695 commit d2e8305428c77e99475427f5da2e400a1451bdf8
Show file tree
Hide file tree
Showing 57 changed files with 151 additions and 132 deletions.
@@ -98,7 +98,7 @@ spufs_setattr(struct dentry *dentry, struct iattr *attr)
if ((attr->ia_valid & ATTR_SIZE) &&
(attr->ia_size != inode->i_size))
return -EINVAL;
setattr_copy(inode, attr);
setattr_copy(&init_user_ns, inode, attr);
mark_inode_dirty(inode);
return 0;
}
@@ -221,7 +221,7 @@ static int handle_create(const char *nodename, umode_t mode, kuid_t uid,
newattrs.ia_gid = gid;
newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID;
inode_lock(d_inode(dentry));
notify_change(dentry, &newattrs, NULL);
notify_change(&init_user_ns, dentry, &newattrs, NULL);
inode_unlock(d_inode(dentry));

/* mark as kernel-created inode */
@@ -328,7 +328,7 @@ static int handle_remove(const char *nodename, struct device *dev)
newattrs.ia_valid =
ATTR_UID|ATTR_GID|ATTR_MODE;
inode_lock(d_inode(dentry));
notify_change(dentry, &newattrs, NULL);
notify_change(&init_user_ns, dentry, &newattrs, NULL);
inode_unlock(d_inode(dentry));
err = vfs_unlink(d_inode(parent.dentry), dentry, NULL);
if (!err || err == -ENOENT)
@@ -1040,7 +1040,7 @@ static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr)
struct p9_wstat wstat;

p9_debug(P9_DEBUG_VFS, "\n");
retval = setattr_prepare(dentry, iattr);
retval = setattr_prepare(&init_user_ns, dentry, iattr);
if (retval)
return retval;

@@ -1090,7 +1090,7 @@ static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr)

v9fs_invalidate_inode_attr(d_inode(dentry));

setattr_copy(d_inode(dentry), iattr);
setattr_copy(&init_user_ns, d_inode(dentry), iattr);
mark_inode_dirty(d_inode(dentry));
return 0;
}
@@ -546,7 +546,7 @@ int v9fs_vfs_setattr_dotl(struct dentry *dentry, struct iattr *iattr)

p9_debug(P9_DEBUG_VFS, "\n");

retval = setattr_prepare(dentry, iattr);
retval = setattr_prepare(&init_user_ns, dentry, iattr);
if (retval)
return retval;

@@ -582,7 +582,7 @@ int v9fs_vfs_setattr_dotl(struct dentry *dentry, struct iattr *iattr)
truncate_setsize(inode, iattr->ia_size);

v9fs_invalidate_inode_attr(inode);
setattr_copy(inode, iattr);
setattr_copy(&init_user_ns, inode, iattr);
mark_inode_dirty(inode);
if (iattr->ia_valid & ATTR_MODE) {
/* We also want to update ACL when we update mode bits */
@@ -299,7 +299,7 @@ adfs_notify_change(struct dentry *dentry, struct iattr *attr)
unsigned int ia_valid = attr->ia_valid;
int error;

error = setattr_prepare(dentry, attr);
error = setattr_prepare(&init_user_ns, dentry, attr);

/*
* we can't change the UID or GID of any file -
@@ -223,7 +223,7 @@ affs_notify_change(struct dentry *dentry, struct iattr *attr)

pr_debug("notify_change(%lu,0x%x)\n", inode->i_ino, attr->ia_valid);

error = setattr_prepare(dentry, attr);
error = setattr_prepare(&init_user_ns, dentry, attr);
if (error)
goto out;

@@ -249,7 +249,7 @@ affs_notify_change(struct dentry *dentry, struct iattr *attr)
affs_truncate(inode);
}

setattr_copy(inode, attr);
setattr_copy(&init_user_ns, inode, attr);
mark_inode_dirty(inode);

if (attr->ia_valid & ATTR_MODE)
@@ -18,34 +18,39 @@
#include <linux/evm.h>
#include <linux/ima.h>

static bool chown_ok(const struct inode *inode, kuid_t uid)
static bool chown_ok(struct user_namespace *user_ns,
const struct inode *inode,
kuid_t uid)
{
if (uid_eq(current_fsuid(), inode->i_uid) &&
uid_eq(uid, inode->i_uid))
kuid_t kuid = i_uid_into_mnt(user_ns, inode);
if (uid_eq(current_fsuid(), kuid) && uid_eq(uid, kuid))
return true;
if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_CHOWN))
if (capable_wrt_inode_uidgid(user_ns, inode, CAP_CHOWN))
return true;
if (uid_eq(inode->i_uid, INVALID_UID) &&
if (uid_eq(kuid, INVALID_UID) &&
ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
return true;
return false;
}

static bool chgrp_ok(const struct inode *inode, kgid_t gid)
static bool chgrp_ok(struct user_namespace *user_ns,
const struct inode *inode, kgid_t gid)
{
if (uid_eq(current_fsuid(), inode->i_uid) &&
(in_group_p(gid) || gid_eq(gid, inode->i_gid)))
kgid_t kgid = i_gid_into_mnt(user_ns, inode);
if (uid_eq(current_fsuid(), i_uid_into_mnt(user_ns, inode)) &&
(in_group_p(gid) || gid_eq(gid, kgid)))
return true;
if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_CHOWN))
if (capable_wrt_inode_uidgid(user_ns, inode, CAP_CHOWN))
return true;
if (gid_eq(inode->i_gid, INVALID_GID) &&
if (gid_eq(kgid, INVALID_GID) &&
ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
return true;
return false;
}

/**
* setattr_prepare - check if attribute changes to a dentry are allowed
* @user_ns: user namespace of the mount
* @dentry: dentry to check
* @attr: attributes to change
*
@@ -58,7 +63,8 @@ static bool chgrp_ok(const struct inode *inode, kgid_t gid)
* Should be called as the first thing in ->setattr implementations,
* possibly after taking additional locks.
*/
int setattr_prepare(struct dentry *dentry, struct iattr *attr)
int setattr_prepare(struct user_namespace *user_ns, struct dentry *dentry,
struct iattr *attr)
{
struct inode *inode = d_inode(dentry);
unsigned int ia_valid = attr->ia_valid;
@@ -78,27 +84,27 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr)
goto kill_priv;

/* Make sure a caller can chown. */
if ((ia_valid & ATTR_UID) && !chown_ok(inode, attr->ia_uid))
if ((ia_valid & ATTR_UID) && !chown_ok(user_ns, inode, attr->ia_uid))
return -EPERM;

/* Make sure caller can chgrp. */
if ((ia_valid & ATTR_GID) && !chgrp_ok(inode, attr->ia_gid))
if ((ia_valid & ATTR_GID) && !chgrp_ok(user_ns, inode, attr->ia_gid))
return -EPERM;

/* Make sure a caller can chmod. */
if (ia_valid & ATTR_MODE) {
if (!inode_owner_or_capable(&init_user_ns, inode))
if (!inode_owner_or_capable(user_ns, inode))
return -EPERM;
/* Also check the setgid bit! */
if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
inode->i_gid) &&
!capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID))
if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
i_gid_into_mnt(user_ns, inode)) &&
!capable_wrt_inode_uidgid(user_ns, inode, CAP_FSETID))
attr->ia_mode &= ~S_ISGID;
}

/* Check for setting the inode time. */
if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) {
if (!inode_owner_or_capable(&init_user_ns, inode))
if (!inode_owner_or_capable(user_ns, inode))
return -EPERM;
}

@@ -162,20 +168,27 @@ EXPORT_SYMBOL(inode_newsize_ok);

/**
* setattr_copy - copy simple metadata updates into the generic inode
* @user_ns: the user namespace the inode is accessed from
* @inode: the inode to be updated
* @attr: the new attributes
*
* setattr_copy must be called with i_mutex held.
*
* setattr_copy updates the inode's metadata with that specified
* in attr. Noticeably missing is inode size update, which is more complex
* in attr on idmapped mounts. If file ownership is changed setattr_copy
* doesn't map ia_uid and ia_gid. It will asssume the caller has already
* provided the intended values. Necessary permission checks to determine
* whether or not the S_ISGID property needs to be removed are performed with
* the correct idmapped mount permission helpers.
* Noticeably missing is inode size update, which is more complex
* as it requires pagecache updates.
*
* The inode is not marked as dirty after this operation. The rationale is
* that for "simple" filesystems, the struct inode is the inode storage.
* The caller is free to mark the inode dirty afterwards if needed.
*/
void setattr_copy(struct inode *inode, const struct iattr *attr)
void setattr_copy(struct user_namespace *user_ns, struct inode *inode,
const struct iattr *attr)
{
unsigned int ia_valid = attr->ia_valid;

@@ -191,9 +204,9 @@ void setattr_copy(struct inode *inode, const struct iattr *attr)
inode->i_ctime = attr->ia_ctime;
if (ia_valid & ATTR_MODE) {
umode_t mode = attr->ia_mode;

if (!in_group_p(inode->i_gid) &&
!capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID))
kgid_t kgid = i_gid_into_mnt(user_ns, inode);
if (!in_group_p(kgid) &&
!capable_wrt_inode_uidgid(user_ns, inode, CAP_FSETID))
mode &= ~S_ISGID;
inode->i_mode = mode;
}
@@ -202,6 +215,7 @@ EXPORT_SYMBOL(setattr_copy);

/**
* notify_change - modify attributes of a filesytem object
* @user_ns: the user namespace of the mount
* @dentry: object affected
* @attr: new attributes
* @delegated_inode: returns inode, if the inode is delegated
@@ -214,13 +228,17 @@ EXPORT_SYMBOL(setattr_copy);
* retry. Because breaking a delegation may take a long time, the
* caller should drop the i_mutex before doing so.
*
* If file ownership is changed notify_change() doesn't map ia_uid and
* ia_gid. It will asssume the caller has already provided the intended values.
*
* Alternatively, a caller may pass NULL for delegated_inode. This may
* be appropriate for callers that expect the underlying filesystem not
* to be NFS exported. Also, passing NULL is fine for callers holding
* the file open for write, as there can be no conflicting delegation in
* that case.
*/
int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **delegated_inode)
int notify_change(struct user_namespace *user_ns, struct dentry *dentry,
struct iattr *attr, struct inode **delegated_inode)
{
struct inode *inode = dentry->d_inode;
umode_t mode = inode->i_mode;
@@ -243,9 +261,8 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de
if (IS_IMMUTABLE(inode))
return -EPERM;

if (!inode_owner_or_capable(&init_user_ns, inode)) {
error = inode_permission(&init_user_ns, inode,
MAY_WRITE);
if (!inode_owner_or_capable(user_ns, inode)) {
error = inode_permission(user_ns, inode, MAY_WRITE);
if (error)
return error;
}
@@ -4875,7 +4875,7 @@ static int btrfs_setattr(struct dentry *dentry, struct iattr *attr)
if (btrfs_root_readonly(root))
return -EROFS;

err = setattr_prepare(dentry, attr);
err = setattr_prepare(&init_user_ns, dentry, attr);
if (err)
return err;

@@ -4886,7 +4886,7 @@ static int btrfs_setattr(struct dentry *dentry, struct iattr *attr)
}

if (attr->ia_valid) {
setattr_copy(inode, attr);
setattr_copy(&init_user_ns, inode, attr);
inode_inc_iversion(inode);
err = btrfs_dirty_inode(inode);

@@ -470,14 +470,14 @@ static int cachefiles_attr_changed(struct fscache_object *_object)
_debug("discard tail %llx", oi_size);
newattrs.ia_valid = ATTR_SIZE;
newattrs.ia_size = oi_size & PAGE_MASK;
ret = notify_change(object->backer, &newattrs, NULL);
ret = notify_change(&init_user_ns, object->backer, &newattrs, NULL);
if (ret < 0)
goto truncate_failed;
}

newattrs.ia_valid = ATTR_SIZE;
newattrs.ia_size = ni_size;
ret = notify_change(object->backer, &newattrs, NULL);
ret = notify_change(&init_user_ns, object->backer, &newattrs, NULL);

truncate_failed:
inode_unlock(d_inode(object->backer));
@@ -2251,7 +2251,7 @@ int ceph_setattr(struct dentry *dentry, struct iattr *attr)
if (ceph_snap(inode) != CEPH_NOSNAP)
return -EROFS;

err = setattr_prepare(dentry, attr);
err = setattr_prepare(&init_user_ns, dentry, attr);
if (err != 0)
return err;

@@ -2602,7 +2602,7 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs)
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_PERM)
attrs->ia_valid |= ATTR_FORCE;

rc = setattr_prepare(direntry, attrs);
rc = setattr_prepare(&init_user_ns, direntry, attrs);
if (rc < 0)
goto out;

@@ -2707,7 +2707,7 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs)
attrs->ia_size != i_size_read(inode))
truncate_setsize(inode, attrs->ia_size);

setattr_copy(inode, attrs);
setattr_copy(&init_user_ns, inode, attrs);
mark_inode_dirty(inode);

/* force revalidate when any of these times are set since some
@@ -2749,7 +2749,7 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs)
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_PERM)
attrs->ia_valid |= ATTR_FORCE;

rc = setattr_prepare(direntry, attrs);
rc = setattr_prepare(&init_user_ns, direntry, attrs);
if (rc < 0) {
free_xid(xid);
return rc;
@@ -2897,7 +2897,7 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs)
attrs->ia_size != i_size_read(inode))
truncate_setsize(inode, attrs->ia_size);

setattr_copy(inode, attrs);
setattr_copy(&init_user_ns, inode, attrs);
mark_inode_dirty(inode);

cifs_setattr_exit:
@@ -855,7 +855,7 @@ int ecryptfs_truncate(struct dentry *dentry, loff_t new_length)
struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry);

inode_lock(d_inode(lower_dentry));
rc = notify_change(lower_dentry, &lower_ia, NULL);
rc = notify_change(&init_user_ns, lower_dentry, &lower_ia, NULL);
inode_unlock(d_inode(lower_dentry));
}
return rc;
@@ -933,7 +933,7 @@ static int ecryptfs_setattr(struct dentry *dentry, struct iattr *ia)
}
mutex_unlock(&crypt_stat->cs_mutex);

rc = setattr_prepare(dentry, ia);
rc = setattr_prepare(&init_user_ns, dentry, ia);
if (rc)
goto out;
if (ia->ia_valid & ATTR_SIZE) {
@@ -959,7 +959,7 @@ static int ecryptfs_setattr(struct dentry *dentry, struct iattr *ia)
lower_ia.ia_valid &= ~ATTR_MODE;

inode_lock(d_inode(lower_dentry));
rc = notify_change(lower_dentry, &lower_ia, NULL);
rc = notify_change(&init_user_ns, lower_dentry, &lower_ia, NULL);
inode_unlock(d_inode(lower_dentry));
out:
fsstack_copy_attr_all(inode, lower_inode);
@@ -305,7 +305,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr)
ATTR_TIMES_SET);
}

error = setattr_prepare(dentry, attr);
error = setattr_prepare(&init_user_ns, dentry, attr);
attr->ia_valid = ia_valid;
if (error)
goto out;
@@ -340,7 +340,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr)
up_write(&EXFAT_I(inode)->truncate_lock);
}

setattr_copy(inode, attr);
setattr_copy(&init_user_ns, inode, attr);
exfat_truncate_atime(&inode->i_atime);
mark_inode_dirty(inode);

0 comments on commit d2e8305

Please sign in to comment.