めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

tmpfsでxattrを取り扱うカーネルソースに関するメモ

何の話かというと

RHEL6では、tmpfsに対して、xattr(拡張ファイル属性)が利用できません。正確に言うと、security.selinuxなど特定の属性だけが使えて、user.*やtrusted.*などの一般的な属性が使えません。アップストリームでは、3.0カーネルで、一般的なxattrに対応する公式パッチ(下記)が取り込まれているのですが、RHEL6にはバックポートされていないためです。

tmpfs: implement generic xattr support

最初、このパッチに気づかないで、(別ブランチからバックポートしていた)Fedoraのソースを見ながらやっつけで、俺々パッチを書いたんですが、公式パッチの下記コメントにもあるように、xattrの取り扱いがちょっと特殊で面白かったので、そのあたりをメモしておこうかなと。

The xattr interface is a bit odd. If a filesystem does not implement any {get,set,list}xattr functions the VFS will call into some random LSM hooks and the running LSM can then implement some method for handling xattrs. SELinux for example provides a method to support security.selinux but no other security.* xattrs.

RHEL6のカーネルソース

まず、現行のRHEL6カーネル(linux-2.6.32-358.23.2.el6)のソースを見ます。

mm/shmem.c

static const struct inode_operations shmem_inode_operations = {
        .truncate       = shmem_truncate,
        .setattr        = shmem_setattr,
        .truncate_range = shmem_truncate_range,
#ifdef CONFIG_TMPFS_POSIX_ACL
        .setxattr       = generic_setxattr,
        .getxattr       = generic_getxattr,
        .listxattr      = generic_listxattr,
        .removexattr    = generic_removexattr,
        .check_acl      = shmem_check_acl,
#endif 
};

inode_operationテーブルのxattr操作関数として、generic_*xattrが使用されています。たとえば、generic_getxattrを見るとこんな感じです。

fs/xattr.c

ssize_t
generic_getxattr(struct dentry *dentry, const char *name, void *buffer, size_t size)
{
        struct xattr_handler *handler;
        struct inode *inode = dentry->d_inode;

        handler = xattr_resolve_name(inode->i_sb->s_xattr, &name);
        if (!handler)
                return -EOPNOTSUPP;
        return handler->get(inode, name, buffer, size);
}

super blockのテーブル「inode->i_sb->s_xattr」から、属性名に対応するハンドラをさがして、見つかったハンドラに処理を移譲しています。このテーブル「inode->i_sb->s_xattr」は、次のように用意されています。

mm/shmem.c

int shmem_fill_super(struct super_block *sb, void *data, int silent)
{
...
#ifdef CONFIG_TMPFS_POSIX_ACL
        sb->s_xattr = shmem_xattr_handlers;
        sb->s_flags |= MS_POSIXACL;
#endif
...

static struct xattr_handler *shmem_xattr_handlers[] = {
        &shmem_xattr_acl_access_handler,
        &shmem_xattr_acl_default_handler,
        &shmem_xattr_security_handler,
        NULL
};

テーブルに登録しているハンドラの中身はこんな感じ。

mm/shmem.c

static struct xattr_handler shmem_xattr_security_handler = {
        .prefix = XATTR_SECURITY_PREFIX,
        .list   = shmem_xattr_security_list,
        .get    = shmem_xattr_security_get,
        .set    = shmem_xattr_security_set,
};

mm/shmem_acl.c

struct xattr_handler shmem_xattr_acl_access_handler = {
        .prefix = POSIX_ACL_XATTR_ACCESS,
        .list   = shmem_list_acl_access,
        .get    = shmem_get_acl_access,
        .set    = shmem_set_acl_access,
};
...

struct xattr_handler shmem_xattr_acl_default_handler = {
        .prefix = POSIX_ACL_XATTR_DEFAULT,
        .list   = shmem_list_acl_default,
        .get    = shmem_get_acl_default,
        .set    = shmem_set_acl_default,
};

これらのハンドラで、拡張属性の種類ごとに特化した取り扱い処理を実装しています。上記のテーブルにある.prefixの内容は、次の通り。

include/linux/posix_acl_xattr.h:

#define POSIX_ACL_XATTR_ACCESS  "system.posix_acl_access"
#define POSIX_ACL_XATTR_DEFAULT "system.posix_acl_default"
...
#define XATTR_SECURITY_PREFIX   "security."

属性名からハンドラを検索する部分を確認すると、属性名のprefixでマッチングしていることが分かります。したがって、現在の実装では、上記の3種類のprefixだけに対応していることになります。

fs/xattr.c

static struct xattr_handler *
xattr_resolve_name(struct xattr_handler **handlers, const char **name)
{
        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;
}

俺々パッチ

そこで、俺々パッチでは、generic_*xattrのラッパーを用意して、prefixが「system」と「security」の場合は、generic_*xattrに投げて、それ以外の場合は、独自実装で拡張属性を保存するようにしています。

まず、こいつで、inodeに拡張属性のフィールドを追加しています。

include/linux/shmem_fs.h

struct simple_xattrs {
        struct list_head head;
        spinlock_t lock;
};

struct simple_xattr {
        struct list_head list;
        char *name;
        size_t size;
        char value[0];
};

static inline void simple_xattrs_init(struct simple_xattrs *xattrs)
{
        INIT_LIST_HEAD(&xattrs->head);
        spin_lock_init(&xattrs->lock);
}

struct shmem_inode_info {
...
        struct simple_xattrs    xattrs;         /* list of xattrs */
...
};

続いて、こちらで、ラッパーを追加しています。

inode_operationテーブルに、下記のラッパー「shmem_*xattr」を登録します。

mm/shmem.c

static const struct inode_operations shmem_inode_operations = {
        .truncate       = shmem_truncate,
        .setattr        = shmem_setattr,
        .truncate_range = shmem_truncate_range,
#ifdef CONFIG_TMPFS_POSIX_ACL
        .check_acl      = shmem_check_acl,
#endif 
        .setxattr = shmem_setxattr,
        .getxattr = shmem_getxattr,
        .listxattr = shmem_listxattr,
        .removexattr = shmem_removexattr,
};

ラッパーの中身は、たとえばこんな感じ。prefixを見て処理を分けています。

mm/shmem.c

static ssize_t shmem_getxattr(struct dentry *dentry, const char *name,
                              void *buffer, size_t size)
{
        struct shmem_inode_info *info = SHMEM_I(dentry->d_inode);
        if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN) ||
            !strncmp(name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN) )
                return generic_getxattr(dentry, name, buffer, size);
        return simple_xattr_get(&info->xattrs, name, buffer, size);
}

linus treeのパッチ

最後にlinus treeの公式パッチです。これもgeneric_*xattrのラッパーを作っている点では同じですが、security prefixについては、super blockのハンドラに移譲することをやめて、独自実装でまかなうようになっています。

 static const struct xattr_handler *shmem_xattr_handlers[] = {
+#ifdef CONFIG_TMPFS_POSIX_ACL
 	&generic_acl_access_handler,
 	&generic_acl_default_handler,
-	&shmem_xattr_security_handler,
+#endif
 	NULL
 };

というのは、「shmem_xattr_security_handler」では、SELinuxなどのセキュリティモジュールに特化した属性だけを扱っており、security prefixを持つ、一般的な属性を扱えないためです。この点が、下記のコメントに対応しているものと思われます。

This new patch does not use the LSM fallback functions and instead just implements a native get/set/list xattr feature for the full security.* and trusted.* namespace like a normal filesystem. This means that tmpfs can now support both security.selinux and security.capability, which was not previously possible.

すっきり!