xattr: handle idmapped mounts

When interacting with extended attributes the vfs verifies that the
caller is privileged over the inode with which the extended attribute is
associated. For posix access and posix default extended attributes a uid
or gid can be stored on-disk. Let the functions handle posix extended
attributes on 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.
This has no effect for e.g. security xattrs since they don't store uids
or gids and don't perform permission checks on them like posix acls do.

Link: https://lore.kernel.org/r/20210121131959.646623-10-christian.brauner@ubuntu.com
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
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: James Morris <jamorris@linux.microsoft.com>
Signed-off-by: Tycho Andersen <tycho@tycho.pizza>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
This commit is contained in:
Tycho Andersen
2021-01-21 14:19:28 +01:00
committed by Christian Brauner
parent e65ce2a50c
commit c7c7a1a18a
19 changed files with 160 additions and 124 deletions

View File

@@ -83,7 +83,8 @@ xattr_resolve_name(struct inode *inode, const char **name)
* because different namespaces have very different rules.
*/
static int
xattr_permission(struct inode *inode, const char *name, int mask)
xattr_permission(struct user_namespace *mnt_userns, struct inode *inode,
const char *name, int mask)
{
/*
* We can never set or remove an extended attribute on a read-only
@@ -128,11 +129,11 @@ xattr_permission(struct inode *inode, const char *name, int mask)
return (mask & MAY_WRITE) ? -EPERM : -ENODATA;
if (S_ISDIR(inode->i_mode) && (inode->i_mode & S_ISVTX) &&
(mask & MAY_WRITE) &&
!inode_owner_or_capable(&init_user_ns, inode))
!inode_owner_or_capable(mnt_userns, inode))
return -EPERM;
}
return inode_permission(&init_user_ns, inode, mask);
return inode_permission(mnt_userns, inode, mask);
}
/*
@@ -163,8 +164,9 @@ xattr_supported_namespace(struct inode *inode, const char *prefix)
EXPORT_SYMBOL(xattr_supported_namespace);
int
__vfs_setxattr(struct dentry *dentry, struct inode *inode, const char *name,
const void *value, size_t size, int flags)
__vfs_setxattr(struct user_namespace *mnt_userns, struct dentry *dentry,
struct inode *inode, const char *name, const void *value,
size_t size, int flags)
{
const struct xattr_handler *handler;
@@ -175,7 +177,7 @@ __vfs_setxattr(struct dentry *dentry, struct inode *inode, const char *name,
return -EOPNOTSUPP;
if (size == 0)
value = ""; /* empty EA, do not remove */
return handler->set(handler, &init_user_ns, dentry, inode, name, value,
return handler->set(handler, mnt_userns, dentry, inode, name, value,
size, flags);
}
EXPORT_SYMBOL(__vfs_setxattr);
@@ -184,6 +186,7 @@ EXPORT_SYMBOL(__vfs_setxattr);
* __vfs_setxattr_noperm - perform setxattr operation without performing
* permission checks.
*
* @mnt_userns - user namespace of the mount the inode was found from
* @dentry - object to perform setxattr on
* @name - xattr name to set
* @value - value to set @name to
@@ -196,8 +199,9 @@ EXPORT_SYMBOL(__vfs_setxattr);
* is executed. It also assumes that the caller will make the appropriate
* permission checks.
*/
int __vfs_setxattr_noperm(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
int __vfs_setxattr_noperm(struct user_namespace *mnt_userns,
struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
struct inode *inode = dentry->d_inode;
int error = -EAGAIN;
@@ -207,7 +211,8 @@ int __vfs_setxattr_noperm(struct dentry *dentry, const char *name,
if (issec)
inode->i_flags &= ~S_NOSEC;
if (inode->i_opflags & IOP_XATTR) {
error = __vfs_setxattr(dentry, inode, name, value, size, flags);
error = __vfs_setxattr(mnt_userns, dentry, inode, name, value,
size, flags);
if (!error) {
fsnotify_xattr(dentry);
security_inode_post_setxattr(dentry, name, value,
@@ -246,14 +251,14 @@ int __vfs_setxattr_noperm(struct dentry *dentry, const char *name,
* a delegation was broken on, NULL if none.
*/
int
__vfs_setxattr_locked(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags,
struct inode **delegated_inode)
__vfs_setxattr_locked(struct user_namespace *mnt_userns, struct dentry *dentry,
const char *name, const void *value, size_t size,
int flags, struct inode **delegated_inode)
{
struct inode *inode = dentry->d_inode;
int error;
error = xattr_permission(inode, name, MAY_WRITE);
error = xattr_permission(mnt_userns, inode, name, MAY_WRITE);
if (error)
return error;
@@ -265,7 +270,8 @@ __vfs_setxattr_locked(struct dentry *dentry, const char *name,
if (error)
goto out;
error = __vfs_setxattr_noperm(dentry, name, value, size, flags);
error = __vfs_setxattr_noperm(mnt_userns, dentry, name, value,
size, flags);
out:
return error;
@@ -273,8 +279,8 @@ out:
EXPORT_SYMBOL_GPL(__vfs_setxattr_locked);
int
vfs_setxattr(struct dentry *dentry, const char *name, const void *value,
size_t size, int flags)
vfs_setxattr(struct user_namespace *mnt_userns, struct dentry *dentry,
const char *name, const void *value, size_t size, int flags)
{
struct inode *inode = dentry->d_inode;
struct inode *delegated_inode = NULL;
@@ -282,7 +288,7 @@ vfs_setxattr(struct dentry *dentry, const char *name, const void *value,
int error;
if (size && strcmp(name, XATTR_NAME_CAPS) == 0) {
error = cap_convert_nscap(&init_user_ns, dentry, &value, size);
error = cap_convert_nscap(mnt_userns, dentry, &value, size);
if (error < 0)
return error;
size = error;
@@ -290,8 +296,8 @@ vfs_setxattr(struct dentry *dentry, const char *name, const void *value,
retry_deleg:
inode_lock(inode);
error = __vfs_setxattr_locked(dentry, name, value, size, flags,
&delegated_inode);
error = __vfs_setxattr_locked(mnt_userns, dentry, name, value, size,
flags, &delegated_inode);
inode_unlock(inode);
if (delegated_inode) {
@@ -341,15 +347,16 @@ out_noalloc:
* Returns the result of alloc, if failed, or the getxattr operation.
*/
ssize_t
vfs_getxattr_alloc(struct dentry *dentry, const char *name, char **xattr_value,
size_t xattr_size, gfp_t flags)
vfs_getxattr_alloc(struct user_namespace *mnt_userns, struct dentry *dentry,
const char *name, char **xattr_value, size_t xattr_size,
gfp_t flags)
{
const struct xattr_handler *handler;
struct inode *inode = dentry->d_inode;
char *value = *xattr_value;
int error;
error = xattr_permission(inode, name, MAY_READ);
error = xattr_permission(mnt_userns, inode, name, MAY_READ);
if (error)
return error;
@@ -390,12 +397,13 @@ __vfs_getxattr(struct dentry *dentry, struct inode *inode, const char *name,
EXPORT_SYMBOL(__vfs_getxattr);
ssize_t
vfs_getxattr(struct dentry *dentry, const char *name, void *value, size_t size)
vfs_getxattr(struct user_namespace *mnt_userns, struct dentry *dentry,
const char *name, void *value, size_t size)
{
struct inode *inode = dentry->d_inode;
int error;
error = xattr_permission(inode, name, MAY_READ);
error = xattr_permission(mnt_userns, inode, name, MAY_READ);
if (error)
return error;
@@ -441,7 +449,8 @@ vfs_listxattr(struct dentry *dentry, char *list, size_t size)
EXPORT_SYMBOL_GPL(vfs_listxattr);
int
__vfs_removexattr(struct dentry *dentry, const char *name)
__vfs_removexattr(struct user_namespace *mnt_userns, struct dentry *dentry,
const char *name)
{
struct inode *inode = d_inode(dentry);
const struct xattr_handler *handler;
@@ -451,8 +460,8 @@ __vfs_removexattr(struct dentry *dentry, const char *name)
return PTR_ERR(handler);
if (!handler->set)
return -EOPNOTSUPP;
return handler->set(handler, &init_user_ns, dentry, inode, name, NULL,
0, XATTR_REPLACE);
return handler->set(handler, mnt_userns, dentry, inode, name, NULL, 0,
XATTR_REPLACE);
}
EXPORT_SYMBOL(__vfs_removexattr);
@@ -466,13 +475,14 @@ EXPORT_SYMBOL(__vfs_removexattr);
* a delegation was broken on, NULL if none.
*/
int
__vfs_removexattr_locked(struct dentry *dentry, const char *name,
struct inode **delegated_inode)
__vfs_removexattr_locked(struct user_namespace *mnt_userns,
struct dentry *dentry, const char *name,
struct inode **delegated_inode)
{
struct inode *inode = dentry->d_inode;
int error;
error = xattr_permission(inode, name, MAY_WRITE);
error = xattr_permission(mnt_userns, inode, name, MAY_WRITE);
if (error)
return error;
@@ -484,7 +494,7 @@ __vfs_removexattr_locked(struct dentry *dentry, const char *name,
if (error)
goto out;
error = __vfs_removexattr(dentry, name);
error = __vfs_removexattr(mnt_userns, dentry, name);
if (!error) {
fsnotify_xattr(dentry);
@@ -497,7 +507,8 @@ out:
EXPORT_SYMBOL_GPL(__vfs_removexattr_locked);
int
vfs_removexattr(struct dentry *dentry, const char *name)
vfs_removexattr(struct user_namespace *mnt_userns, struct dentry *dentry,
const char *name)
{
struct inode *inode = dentry->d_inode;
struct inode *delegated_inode = NULL;
@@ -505,7 +516,8 @@ vfs_removexattr(struct dentry *dentry, const char *name)
retry_deleg:
inode_lock(inode);
error = __vfs_removexattr_locked(dentry, name, &delegated_inode);
error = __vfs_removexattr_locked(mnt_userns, dentry,
name, &delegated_inode);
inode_unlock(inode);
if (delegated_inode) {
@@ -522,8 +534,9 @@ EXPORT_SYMBOL_GPL(vfs_removexattr);
* Extended attribute SET operations
*/
static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
size_t size, int flags)
setxattr(struct user_namespace *mnt_userns, struct dentry *d,
const char __user *name, const void __user *value, size_t size,
int flags)
{
int error;
void *kvalue = NULL;
@@ -550,11 +563,10 @@ setxattr(struct dentry *d, const char __user *name, const void __user *value,
}
if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
(strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
posix_acl_fix_xattr_from_user(&init_user_ns, kvalue,
size);
posix_acl_fix_xattr_from_user(mnt_userns, kvalue, size);
}
error = vfs_setxattr(d, kname, kvalue, size, flags);
error = vfs_setxattr(mnt_userns, d, kname, kvalue, size, flags);
out:
kvfree(kvalue);
@@ -567,13 +579,15 @@ static int path_setxattr(const char __user *pathname,
{
struct path path;
int error;
retry:
error = user_path_at(AT_FDCWD, pathname, lookup_flags, &path);
if (error)
return error;
error = mnt_want_write(path.mnt);
if (!error) {
error = setxattr(path.dentry, name, value, size, flags);
error = setxattr(mnt_user_ns(path.mnt), path.dentry, name,
value, size, flags);
mnt_drop_write(path.mnt);
}
path_put(&path);
@@ -609,7 +623,9 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name,
audit_file(f.file);
error = mnt_want_write_file(f.file);
if (!error) {
error = setxattr(f.file->f_path.dentry, name, value, size, flags);
error = setxattr(file_mnt_user_ns(f.file),
f.file->f_path.dentry, name,
value, size, flags);
mnt_drop_write_file(f.file);
}
fdput(f);
@@ -620,8 +636,8 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name,
* Extended attribute GET operations
*/
static ssize_t
getxattr(struct dentry *d, const char __user *name, void __user *value,
size_t size)
getxattr(struct user_namespace *mnt_userns, struct dentry *d,
const char __user *name, void __user *value, size_t size)
{
ssize_t error;
void *kvalue = NULL;
@@ -641,12 +657,11 @@ getxattr(struct dentry *d, const char __user *name, void __user *value,
return -ENOMEM;
}
error = vfs_getxattr(d, kname, kvalue, size);
error = vfs_getxattr(mnt_userns, d, kname, kvalue, size);
if (error > 0) {
if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
(strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
posix_acl_fix_xattr_to_user(&init_user_ns, kvalue,
error);
posix_acl_fix_xattr_to_user(mnt_userns, kvalue, error);
if (size && copy_to_user(value, kvalue, error))
error = -EFAULT;
} else if (error == -ERANGE && size >= XATTR_SIZE_MAX) {
@@ -670,7 +685,7 @@ retry:
error = user_path_at(AT_FDCWD, pathname, lookup_flags, &path);
if (error)
return error;
error = getxattr(path.dentry, name, value, size);
error = getxattr(mnt_user_ns(path.mnt), path.dentry, name, value, size);
path_put(&path);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
@@ -700,7 +715,8 @@ SYSCALL_DEFINE4(fgetxattr, int, fd, const char __user *, name,
if (!f.file)
return error;
audit_file(f.file);
error = getxattr(f.file->f_path.dentry, name, value, size);
error = getxattr(file_mnt_user_ns(f.file), f.file->f_path.dentry,
name, value, size);
fdput(f);
return error;
}
@@ -784,7 +800,8 @@ SYSCALL_DEFINE3(flistxattr, int, fd, char __user *, list, size_t, size)
* Extended attribute REMOVE operations
*/
static long
removexattr(struct dentry *d, const char __user *name)
removexattr(struct user_namespace *mnt_userns, struct dentry *d,
const char __user *name)
{
int error;
char kname[XATTR_NAME_MAX + 1];
@@ -795,7 +812,7 @@ removexattr(struct dentry *d, const char __user *name)
if (error < 0)
return error;
return vfs_removexattr(d, kname);
return vfs_removexattr(mnt_userns, d, kname);
}
static int path_removexattr(const char __user *pathname,
@@ -809,7 +826,7 @@ retry:
return error;
error = mnt_want_write(path.mnt);
if (!error) {
error = removexattr(path.dentry, name);
error = removexattr(mnt_user_ns(path.mnt), path.dentry, name);
mnt_drop_write(path.mnt);
}
path_put(&path);
@@ -842,7 +859,8 @@ SYSCALL_DEFINE2(fremovexattr, int, fd, const char __user *, name)
audit_file(f.file);
error = mnt_want_write_file(f.file);
if (!error) {
error = removexattr(f.file->f_path.dentry, name);
error = removexattr(file_mnt_user_ns(f.file),
f.file->f_path.dentry, name);
mnt_drop_write_file(f.file);
}
fdput(f);