forked from Minki/linux
549c729771
Extend some inode methods with an additional user namespace argument. A filesystem that is aware of idmapped mounts will receive the user namespace the mount has been marked with. This can be used for additional permission checking and also to enable filesystems to translate between uids and gids if they need to. We have implemented all relevant helpers in earlier patches. As requested we simply extend the exisiting inode method instead of introducing new ones. This is a little more code churn but it's mostly mechanical and doesnt't leave us with additional inode methods. Link: https://lore.kernel.org/r/20210121131959.646623-25-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> Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
273 lines
5.8 KiB
C
273 lines
5.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/fs/ext2/acl.c
|
|
*
|
|
* Copyright (C) 2001-2003 Andreas Gruenbacher, <agruen@suse.de>
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/fs.h>
|
|
#include "ext2.h"
|
|
#include "xattr.h"
|
|
#include "acl.h"
|
|
|
|
/*
|
|
* Convert from filesystem to in-memory representation.
|
|
*/
|
|
static struct posix_acl *
|
|
ext2_acl_from_disk(const void *value, size_t size)
|
|
{
|
|
const char *end = (char *)value + size;
|
|
int n, count;
|
|
struct posix_acl *acl;
|
|
|
|
if (!value)
|
|
return NULL;
|
|
if (size < sizeof(ext2_acl_header))
|
|
return ERR_PTR(-EINVAL);
|
|
if (((ext2_acl_header *)value)->a_version !=
|
|
cpu_to_le32(EXT2_ACL_VERSION))
|
|
return ERR_PTR(-EINVAL);
|
|
value = (char *)value + sizeof(ext2_acl_header);
|
|
count = ext2_acl_count(size);
|
|
if (count < 0)
|
|
return ERR_PTR(-EINVAL);
|
|
if (count == 0)
|
|
return NULL;
|
|
acl = posix_acl_alloc(count, GFP_KERNEL);
|
|
if (!acl)
|
|
return ERR_PTR(-ENOMEM);
|
|
for (n=0; n < count; n++) {
|
|
ext2_acl_entry *entry =
|
|
(ext2_acl_entry *)value;
|
|
if ((char *)value + sizeof(ext2_acl_entry_short) > end)
|
|
goto fail;
|
|
acl->a_entries[n].e_tag = le16_to_cpu(entry->e_tag);
|
|
acl->a_entries[n].e_perm = le16_to_cpu(entry->e_perm);
|
|
switch(acl->a_entries[n].e_tag) {
|
|
case ACL_USER_OBJ:
|
|
case ACL_GROUP_OBJ:
|
|
case ACL_MASK:
|
|
case ACL_OTHER:
|
|
value = (char *)value +
|
|
sizeof(ext2_acl_entry_short);
|
|
break;
|
|
|
|
case ACL_USER:
|
|
value = (char *)value + sizeof(ext2_acl_entry);
|
|
if ((char *)value > end)
|
|
goto fail;
|
|
acl->a_entries[n].e_uid =
|
|
make_kuid(&init_user_ns,
|
|
le32_to_cpu(entry->e_id));
|
|
break;
|
|
case ACL_GROUP:
|
|
value = (char *)value + sizeof(ext2_acl_entry);
|
|
if ((char *)value > end)
|
|
goto fail;
|
|
acl->a_entries[n].e_gid =
|
|
make_kgid(&init_user_ns,
|
|
le32_to_cpu(entry->e_id));
|
|
break;
|
|
|
|
default:
|
|
goto fail;
|
|
}
|
|
}
|
|
if (value != end)
|
|
goto fail;
|
|
return acl;
|
|
|
|
fail:
|
|
posix_acl_release(acl);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
/*
|
|
* Convert from in-memory to filesystem representation.
|
|
*/
|
|
static void *
|
|
ext2_acl_to_disk(const struct posix_acl *acl, size_t *size)
|
|
{
|
|
ext2_acl_header *ext_acl;
|
|
char *e;
|
|
size_t n;
|
|
|
|
*size = ext2_acl_size(acl->a_count);
|
|
ext_acl = kmalloc(sizeof(ext2_acl_header) + acl->a_count *
|
|
sizeof(ext2_acl_entry), GFP_KERNEL);
|
|
if (!ext_acl)
|
|
return ERR_PTR(-ENOMEM);
|
|
ext_acl->a_version = cpu_to_le32(EXT2_ACL_VERSION);
|
|
e = (char *)ext_acl + sizeof(ext2_acl_header);
|
|
for (n=0; n < acl->a_count; n++) {
|
|
const struct posix_acl_entry *acl_e = &acl->a_entries[n];
|
|
ext2_acl_entry *entry = (ext2_acl_entry *)e;
|
|
entry->e_tag = cpu_to_le16(acl_e->e_tag);
|
|
entry->e_perm = cpu_to_le16(acl_e->e_perm);
|
|
switch(acl_e->e_tag) {
|
|
case ACL_USER:
|
|
entry->e_id = cpu_to_le32(
|
|
from_kuid(&init_user_ns, acl_e->e_uid));
|
|
e += sizeof(ext2_acl_entry);
|
|
break;
|
|
case ACL_GROUP:
|
|
entry->e_id = cpu_to_le32(
|
|
from_kgid(&init_user_ns, acl_e->e_gid));
|
|
e += sizeof(ext2_acl_entry);
|
|
break;
|
|
|
|
case ACL_USER_OBJ:
|
|
case ACL_GROUP_OBJ:
|
|
case ACL_MASK:
|
|
case ACL_OTHER:
|
|
e += sizeof(ext2_acl_entry_short);
|
|
break;
|
|
|
|
default:
|
|
goto fail;
|
|
}
|
|
}
|
|
return (char *)ext_acl;
|
|
|
|
fail:
|
|
kfree(ext_acl);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
/*
|
|
* inode->i_mutex: don't care
|
|
*/
|
|
struct posix_acl *
|
|
ext2_get_acl(struct inode *inode, int type)
|
|
{
|
|
int name_index;
|
|
char *value = NULL;
|
|
struct posix_acl *acl;
|
|
int retval;
|
|
|
|
switch (type) {
|
|
case ACL_TYPE_ACCESS:
|
|
name_index = EXT2_XATTR_INDEX_POSIX_ACL_ACCESS;
|
|
break;
|
|
case ACL_TYPE_DEFAULT:
|
|
name_index = EXT2_XATTR_INDEX_POSIX_ACL_DEFAULT;
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
retval = ext2_xattr_get(inode, name_index, "", NULL, 0);
|
|
if (retval > 0) {
|
|
value = kmalloc(retval, GFP_KERNEL);
|
|
if (!value)
|
|
return ERR_PTR(-ENOMEM);
|
|
retval = ext2_xattr_get(inode, name_index, "", value, retval);
|
|
}
|
|
if (retval > 0)
|
|
acl = ext2_acl_from_disk(value, retval);
|
|
else if (retval == -ENODATA || retval == -ENOSYS)
|
|
acl = NULL;
|
|
else
|
|
acl = ERR_PTR(retval);
|
|
kfree(value);
|
|
|
|
return acl;
|
|
}
|
|
|
|
static int
|
|
__ext2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
|
|
{
|
|
int name_index;
|
|
void *value = NULL;
|
|
size_t size = 0;
|
|
int error;
|
|
|
|
switch(type) {
|
|
case ACL_TYPE_ACCESS:
|
|
name_index = EXT2_XATTR_INDEX_POSIX_ACL_ACCESS;
|
|
break;
|
|
|
|
case ACL_TYPE_DEFAULT:
|
|
name_index = EXT2_XATTR_INDEX_POSIX_ACL_DEFAULT;
|
|
if (!S_ISDIR(inode->i_mode))
|
|
return acl ? -EACCES : 0;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
if (acl) {
|
|
value = ext2_acl_to_disk(acl, &size);
|
|
if (IS_ERR(value))
|
|
return (int)PTR_ERR(value);
|
|
}
|
|
|
|
error = ext2_xattr_set(inode, name_index, "", value, size, 0);
|
|
|
|
kfree(value);
|
|
if (!error)
|
|
set_cached_acl(inode, type, acl);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* inode->i_mutex: down
|
|
*/
|
|
int
|
|
ext2_set_acl(struct user_namespace *mnt_userns, struct inode *inode,
|
|
struct posix_acl *acl, int type)
|
|
{
|
|
int error;
|
|
int update_mode = 0;
|
|
umode_t mode = inode->i_mode;
|
|
|
|
if (type == ACL_TYPE_ACCESS && acl) {
|
|
error = posix_acl_update_mode(&init_user_ns, inode, &mode,
|
|
&acl);
|
|
if (error)
|
|
return error;
|
|
update_mode = 1;
|
|
}
|
|
error = __ext2_set_acl(inode, acl, type);
|
|
if (!error && update_mode) {
|
|
inode->i_mode = mode;
|
|
inode->i_ctime = current_time(inode);
|
|
mark_inode_dirty(inode);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Initialize the ACLs of a new inode. Called from ext2_new_inode.
|
|
*
|
|
* dir->i_mutex: down
|
|
* inode->i_mutex: up (access to inode is still exclusive)
|
|
*/
|
|
int
|
|
ext2_init_acl(struct inode *inode, struct inode *dir)
|
|
{
|
|
struct posix_acl *default_acl, *acl;
|
|
int error;
|
|
|
|
error = posix_acl_create(dir, &inode->i_mode, &default_acl, &acl);
|
|
if (error)
|
|
return error;
|
|
|
|
if (default_acl) {
|
|
error = __ext2_set_acl(inode, default_acl, ACL_TYPE_DEFAULT);
|
|
posix_acl_release(default_acl);
|
|
} else {
|
|
inode->i_default_acl = NULL;
|
|
}
|
|
if (acl) {
|
|
if (!error)
|
|
error = __ext2_set_acl(inode, acl, ACL_TYPE_ACCESS);
|
|
posix_acl_release(acl);
|
|
} else {
|
|
inode->i_acl = NULL;
|
|
}
|
|
return error;
|
|
}
|