ACL(拡張属性xattr)
ACLは拡張属性xattrで実装されます。ext3ファイルシステムスパーブロック取得のext3_fill_super()で、sb->s_xattrにUSER/TRUST/ACL ACCESS/ACL DEFAULT/SECURITYのコールバック関数が設定され、ACLはext3_xattr_handlers/ext3_xattr_acl_default_handlerとなります。
xattrの取得は、ファイルのinodeのスーパブロックのxattr_handlerからの.prefixとxattr名称の一致するハンドラを取得する事で実装されています。
取得後のxattr_resolve_name()の引数nameは、system.以降の文字列で、struct xattr.nameのposix_acl_access/posix_acl_defaultとなります。
POSIX_ACL_XATTR_DEFAULTはファイルシステムのデフォルト値で、更新時はPOSIX_ACL_XATTR_ACCESSとして使用されているようです。
ext3_acl_from_disk()は、ext3_xattr_get()の上記読み込んだデータをACLに設定し、set_cached_acl()でACL_TYPE_ACCESSならinode->i_acl、 ACL_TYPE_DEFAULTならnode->i_default_aclに設定します。(inodeとxattrのキャッシュは別実装で、inodeキャッシュでもxattrもキャッシュとしているとは限りません。)
xattr参照時、指定されるxattr名の正当性チェックのためxattr_permission()がコールされ、os2./security./system./trusted./user.から始まる名称でないと、xattrは参照できません。
xattrの取得は、ファイルのinodeのスーパブロックのxattr_handlerからの.prefixとxattr名称の一致するハンドラを取得する事で実装されています。
struct xattr {
char *name;
void *value;
size_t value_len;
};
const struct xattr_handler *ext3_xattr_handlers[] = {
&ext3_xattr_user_handler,
&ext3_xattr_trusted_handler,
#ifdef CONFIG_EXT3_FS_POSIX_ACL
&ext3_xattr_acl_access_handler,
&ext3_xattr_acl_default_handler,
#endif
#ifdef CONFIG_EXT3_FS_SECURITY
&ext3_xattr_security_handler,
#endif
NULL
};
static int ext3_fill_super (struct super_block *sb, void *data, int silent)
{
:
sb->s_op = &ext3_sops;
sb->s_export_op = &ext3_export_ops;
sb->s_xattr = ext3_xattr_handlers;
:
}
システムコールを介するコマンドによる取得、およびカーネルからの取得も含め、vfs_getxattr()からinode->i_op->getxattr()がコールされます。ext等ではgeneric_getxattr()がコールされます。引数のnameはxattrの名前で、ACLでは"system.posix_acl_access"ないし"system.posix_acl_default"となります。
ssize_t vfs_getxattr(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);
if (error)
return error;
error = security_inode_getxattr(dentry, name);
if (error)
return error;
if (!strncmp(name, XATTR_SECURITY_PREFIX,
XATTR_SECURITY_PREFIX_LEN)) {
const char *suffix = name + XATTR_SECURITY_PREFIX_LEN;
int ret = xattr_getsecurity(inode, suffix, value, size);
if (ret == -EOPNOTSUPP)
goto nolsm;
return ret;
}
nolsm:
if (inode->i_op->getxattr)
error = inode->i_op->getxattr(dentry, name, value, size);
else
error = -EOPNOTSUPP;
return error;
}
EXPORT_SYMBOL_GPL(vfs_getxattr);
xattr_resolve_name()でスーパブロックのxattrコールバックリスト(dentry->d_sb->s_xattr)から、引数name("system.posix_acl_access" "system.posix_acl_default")のxattr_handleを取得し、そのgetコールバックのext3_xattr_get_acl()がコールされます。取得後のxattr_resolve_name()の引数nameは、system.以降の文字列で、struct xattr.nameのposix_acl_access/posix_acl_defaultとなります。
POSIX_ACL_XATTR_DEFAULTはファイルシステムのデフォルト値で、更新時はPOSIX_ACL_XATTR_ACCESSとして使用されているようです。
ssize_t generic_getxattr(struct dentry *dentry, const char *name, void *buffer, size_t size)
{
const struct xattr_handler *handler;
handler = xattr_resolve_name(dentry->d_sb->s_xattr, &name);
if (!handler)
return -EOPNOTSUPP;
return handler->get(dentry, name, buffer, size, handler->flags);
}
#define for_each_xattr_handler(handlers, handler) \
for ((handler) = *(handlers)++; \
(handler) != NULL; \
(handler) = *(handlers)++)
static const struct xattr_handler *
xattr_resolve_name(const struct xattr_handler **handlers, const char **name)
{
const struct xattr_handler *handler;
if (!*name)
return NULL;
for_each_xattr_handler(handlers, handler) {
const char *n = strcmp_prefix(*name, handler->prefix);
if (n) {
*name = n;
break;
}
}
return handler;
}
#define POSIX_ACL_XATTR_ACCESS "system.posix_acl_access"
#define POSIX_ACL_XATTR_DEFAULT "system.posix_acl_default"
const struct xattr_handler ext3_xattr_acl_access_handler = {
.prefix = POSIX_ACL_XATTR_ACCESS,
.flags = ACL_TYPE_ACCESS,
.list = ext3_xattr_list_acl_access,
.get = ext3_xattr_get_acl,
.set = ext3_xattr_set_acl,
};
const struct xattr_handler ext3_xattr_acl_default_handler = {
.prefix = POSIX_ACL_XATTR_DEFAULT,
.flags = ACL_TYPE_DEFAULT,
.list = ext3_xattr_list_acl_default,
.get = ext3_xattr_get_acl,
.set = ext3_xattr_set_acl,
};
static int ext3_xattr_get_acl(struct dentry *dentry, const char *name, void *buffer,
size_t size, int type)
{
struct posix_acl *acl;
int error;
if (strcmp(name, "") != 0)
return -EINVAL;
if (!test_opt(dentry->d_sb, POSIX_ACL))
return -EOPNOTSUPP;
acl = ext3_get_acl(dentry->d_inode, type);
if (IS_ERR(acl))
return PTR_ERR(acl);
if (acl == NULL)
return -ENODATA;
error = posix_acl_to_xattr(acl, buffer, size);
posix_acl_release(acl);
return error;
}
name_indexがデバイスのセクタインデックスで、まずext3_xattr_get()で、第三引数をNULLとし、データサイズは不定のためサイズのみを取得し、そのサイズのバッファーをalloc()して、改めてname_indexのデータをそのバッファに読み込みます。ext3_acl_from_disk()は、ext3_xattr_get()の上記読み込んだデータをACLに設定し、set_cached_acl()でACL_TYPE_ACCESSならinode->i_acl、 ACL_TYPE_DEFAULTならnode->i_default_aclに設定します。(inodeとxattrのキャッシュは別実装で、inodeキャッシュでもxattrもキャッシュとしているとは限りません。)
struct posix_acl *ext3_get_acl(struct inode *inode, int type)
{
int name_index;
char *value = NULL;
struct posix_acl *acl;
int retval;
if (!test_opt(inode->i_sb, POSIX_ACL))
return NULL;
acl = get_cached_acl(inode, type);
if (acl != ACL_NOT_CACHED)
return acl;
switch (type) {
case ACL_TYPE_ACCESS:
name_index = EXT3_XATTR_INDEX_POSIX_ACL_ACCESS;
break;
case ACL_TYPE_DEFAULT:
name_index = EXT3_XATTR_INDEX_POSIX_ACL_DEFAULT;
break;
default:
BUG();
}
retval = ext3_xattr_get(inode, name_index, "", NULL, 0);
if (retval > 0) {
value = kmalloc(retval, GFP_NOFS);
if (!value)
return ERR_PTR(-ENOMEM);
retval = ext3_xattr_get(inode, name_index, "", value, retval);
}
if (retval > 0)
acl = ext3_acl_from_disk(value, retval);
else if (retval == -ENODATA || retval == -ENOSYS)
acl = NULL;
else
acl = ERR_PTR(retval);
kfree(value);
if (!IS_ERR(acl))
set_cached_acl(inode, type, acl);
return acl;
}
static inline struct posix_acl *get_cached_acl(struct inode *inode, int type)
{
struct posix_acl **p = acl_by_type(inode, type);
struct posix_acl *acl = ACCESS_ONCE(*p);
if (acl) {
spin_lock(&inode->i_lock);
acl = *p;
if (acl != ACL_NOT_CACHED)
acl = posix_acl_dup(acl);
spin_unlock(&inode->i_lock);
}
return acl;
}
static inline struct posix_acl **acl_by_type(struct inode *inode, int type)
{
switch (type) {
case ACL_TYPE_ACCESS:
return &inode->i_acl;
case ACL_TYPE_DEFAULT:
return &inode->i_default_acl;
default:
BUG();
}
}
cpu_to_le16/cpu_to_le32はエンディアン処理は、引数のaclはディスクイメージその物が設定されているからです。
int posix_acl_to_xattr(const struct posix_acl *acl, void *buffer, size_t size)
{
posix_acl_xattr_header *ext_acl = (posix_acl_xattr_header *)buffer;
posix_acl_xattr_entry *ext_entry = ext_acl->a_entries;
int real_size, n;
real_size = posix_acl_xattr_size(acl->a_count);
if (!buffer)
return real_size;
if (real_size > size)
return -ERANGE;
ext_acl->a_version = cpu_to_le32(POSIX_ACL_XATTR_VERSION);
for (n=0; n < acl->a_count; n++, ext_entry++) {
ext_entry->e_tag = cpu_to_le16(acl->a_entries[n].e_tag);
ext_entry->e_perm = cpu_to_le16(acl->a_entries[n].e_perm);
ext_entry->e_id = cpu_to_le32(acl->a_entries[n].e_id);
}
return real_size;
}
EXPORT_SYMBOL (posix_acl_to_xattr);
補足
xattrの属性名称は、os2./security./trusted./user.を先頭とする文字列です。system.はカーネルが、user.はユーザプロセスが参照する様ですが、この名称自身の実装上の制約はなく、単にカテゴリにしか過ぎません。xattrはカーネル/ユーザ区間から参照でき、実行ファイル自身が、それ自身のxattrで権限チェック等を行なったりすると言った実装も可能です。xattr参照時、指定されるxattr名の正当性チェックのためxattr_permission()がコールされ、os2./security./system./trusted./user.から始まる名称でないと、xattrは参照できません。
#define XATTR_OS2_PREFIX "os2."
#define XATTR_OS2_PREFIX_LEN (sizeof (XATTR_OS2_PREFIX) - 1)
#define XATTR_SECURITY_PREFIX "security."
#define XATTR_SECURITY_PREFIX_LEN (sizeof (XATTR_SECURITY_PREFIX) - 1)
#define XATTR_SYSTEM_PREFIX "system."
#define XATTR_SYSTEM_PREFIX_LEN (sizeof (XATTR_SYSTEM_PREFIX) - 1)
#define XATTR_TRUSTED_PREFIX "trusted."
#define XATTR_TRUSTED_PREFIX_LEN (sizeof (XATTR_TRUSTED_PREFIX) - 1)
#define XATTR_USER_PREFIX "user."
#define XATTR_USER_PREFIX_LEN (sizeof (XATTR_USER_PREFIX) - 1)
static int
xattr_permission(struct inode *inode, const char *name, int mask)
{
if (mask & MAY_WRITE) {
if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
return -EPERM;
}
if (!strncmp(name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN) ||
!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN))
return 0;
if (!strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN)) {
if (!capable(CAP_SYS_ADMIN))
return (mask & MAY_WRITE) ? -EPERM : -ENODATA;
return 0;
}
if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) {
if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode))
return (mask & MAY_WRITE) ? -EPERM : -ENODATA;
if (S_ISDIR(inode->i_mode) && (inode->i_mode & S_ISVTX) &&
(mask & MAY_WRITE) && !inode_owner_or_capable(inode))
return -EPERM;
}
return inode_permission(inode, mask);
}





