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); }