ovl: handle idmappings in ovl_get_acl()

During permission checking overlayfs will call

ovl_permission()
-> generic_permission()
   -> acl_permission_check()
      -> check_acl()
         -> get_acl()
            -> inode->i_op->get_acl() == ovl_get_acl()
               -> get_acl() /* on the underlying filesystem */
                  -> inode->i_op->get_acl() == /*lower filesystem callback */
         -> posix_acl_permission()

passing through the get_acl() request to the underlying filesystem.

Before returning these values to the VFS we need to take the idmapping of the
relevant layer into account and translate any ACL_{GROUP,USER} values according
to the idmapped mount.

We cannot alter the ACLs returned from the relevant layer directly as that
would alter the cached values filesystem wide for the lower filesystem. Instead
we can clone the ACLs and then apply the relevant idmapping of the layer.

This is obviously only relevant when idmapped layers are used.

Link: https://lore.kernel.org/r/20220708090134.385160-4-brauner@kernel.org
Cc: Seth Forshee <sforshee@digitalocean.com>
Cc: Amir Goldstein <amir73il@gmail.com>
Cc: Vivek Goyal <vgoyal@redhat.com>
Cc: Christoph Hellwig <hch@lst.de>
Cc: Aleksa Sarai <cyphar@cyphar.com>
Cc: Miklos Szeredi <mszeredi@redhat.com>
Cc: linux-unionfs@vger.kernel.org
Reviewed-by: Seth Forshee <sforshee@digitalocean.com>
Signed-off-by: Christian Brauner (Microsoft) <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2022-07-06 18:09:12 +02:00 committed by Christian Brauner (Microsoft)
parent 8043bffd01
commit 1aa5fef575
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2

View File

@ -454,23 +454,94 @@ ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size)
return res;
}
/*
* Apply the idmapping of the layer to POSIX ACLs. The caller must pass a clone
* of the POSIX ACLs retrieved from the lower layer to this function to not
* alter the POSIX ACLs for the underlying filesystem.
*/
static void ovl_idmap_posix_acl(struct user_namespace *mnt_userns,
struct posix_acl *acl)
{
for (unsigned int i = 0; i < acl->a_count; i++) {
vfsuid_t vfsuid;
vfsgid_t vfsgid;
struct posix_acl_entry *e = &acl->a_entries[i];
switch (e->e_tag) {
case ACL_USER:
vfsuid = make_vfsuid(mnt_userns, &init_user_ns, e->e_uid);
e->e_uid = vfsuid_into_kuid(vfsuid);
break;
case ACL_GROUP:
vfsgid = make_vfsgid(mnt_userns, &init_user_ns, e->e_gid);
e->e_gid = vfsgid_into_kgid(vfsgid);
break;
}
}
}
/*
* When the relevant layer is an idmapped mount we need to take the idmapping
* of the layer into account and translate any ACL_{GROUP,USER} values
* according to the idmapped mount.
*
* We cannot alter the ACLs returned from the relevant layer as that would
* alter the cached values filesystem wide for the lower filesystem. Instead we
* can clone the ACLs and then apply the relevant idmapping of the layer.
*
* This is obviously only relevant when idmapped layers are used.
*/
struct posix_acl *ovl_get_acl(struct inode *inode, int type, bool rcu)
{
struct inode *realinode = ovl_inode_real(inode);
const struct cred *old_cred;
struct posix_acl *acl;
struct posix_acl *acl, *clone;
struct path realpath;
if (!IS_ENABLED(CONFIG_FS_POSIX_ACL) || !IS_POSIXACL(realinode))
return NULL;
/* Careful in RCU walk mode */
ovl_i_path_real(inode, &realpath);
if (!realpath.dentry) {
WARN_ON(!rcu);
return ERR_PTR(-ECHILD);
}
if (rcu) {
acl = get_cached_acl_rcu(realinode, type);
} else {
const struct cred *old_cred;
old_cred = ovl_override_creds(inode->i_sb);
acl = get_acl(realinode, type);
revert_creds(old_cred);
}
/*
* If there are no POSIX ACLs, or we encountered an error,
* or the layer isn't idmapped we don't need to do anything.
*/
if (!is_idmapped_mnt(realpath.mnt) || IS_ERR_OR_NULL(acl))
return acl;
/*
* We only get here if the layer is idmapped. So drop out of RCU path
* walk so we can clone the ACLs. There's no need to release the ACLs
* since get_cached_acl_rcu() doesn't take a reference on the ACLs.
*/
if (rcu)
return get_cached_acl_rcu(realinode, type);
return ERR_PTR(-ECHILD);
old_cred = ovl_override_creds(inode->i_sb);
acl = get_acl(realinode, type);
revert_creds(old_cred);
return acl;
clone = posix_acl_clone(acl, GFP_KERNEL);
if (!clone)
clone = ERR_PTR(-ENOMEM);
else
ovl_idmap_posix_acl(mnt_user_ns(realpath.mnt), clone);
/*
* Since we're not in RCU path walk we always need to release the
* original ACLs.
*/
posix_acl_release(acl);
return clone;
}
int ovl_update_time(struct inode *inode, struct timespec64 *ts, int flags)