ACL(拡張属性xattr)


Rev.1を表示中。最新版はこちら

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名称の一致するハンドラを取得する事で実装されています。
 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);
}

最終更新 2015/07/28 13:47:28 - north
(2015/07/28 13:47:28 作成)


検索

アクセス数
3694361
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。