Commit a76ab411 authored by J. R. Okajima's avatar J. R. Okajima
Browse files

aufs: xattr and acl



Support for XATTR and ACL including several branch attributes to ignore
the copy error around XATTR and ACL.

NFS always sets MS_POSIXACL regardless its mount option 'noacl.'
When MS_POSIXACL is set, generic_permission() calls check_acl() (via
acl_permission_check()) and gets -EOPNOTSUPP because the NFS branch is
mounted as 'noacl.'
In aufs, h_permission() should not call generic_permission() in this
case.
The similar thing happens in coping-up XATTR. vfs_getxattr_alloc()
returns -EOPNOTSUPP.

See also the document in this commit.
Signed-off-by: default avatarJ. R. Okajima <hooanon05g@gmail.com>
parent e4dbdb8c
# Copyright (C) 2014-2019 Junjiro R. Okajima
Listing XATTR/EA and getting the value
----------------------------------------------------------------------
For the inode standard attributes (owner, group, timestamps, etc.), aufs
shows the values from the topmost existing file. This behaviour is good
for the non-dir entries since the bahaviour exactly matches the shown
information. But for the directories, aufs considers all the same named
entries on the lower branches. Which means, if one of the lower entry
rejects readdir call, then aufs returns an error even if the topmost
entry allows it. This behaviour is necessary to respect the branch fs's
security, but can make users confused since the user-visible standard
attributes don't match the behaviour.
To address this issue, aufs has a mount option called dirperm1 which
checks the permission for the topmost entry only, and ignores the lower
entry's permission.
A similar issue can happen around XATTR.
getxattr(2) and listxattr(2) families behave as if dirperm1 option is
always set. Otherwise these very unpleasant situation would happen.
- listxattr(2) may return the duplicated entries.
- users may not be able to remove or reset the XATTR forever,
XATTR/EA support in the internal (copy,move)-(up,down)
----------------------------------------------------------------------
Generally the extended attributes of inode are categorized as these.
- "security" for LSM and capability.
- "system" for posix ACL, 'acl' mount option is required for the branch
fs generally.
- "trusted" for userspace, CAP_SYS_ADMIN is required.
- "user" for userspace, 'user_xattr' mount option is required for the
branch fs generally.
Moreover there are some other categories. Aufs handles these rather
unpopular categories as the ordinary ones, ie. there is no special
condition nor exception.
In copy-up, the support for XATTR on the dst branch may differ from the
src branch. In this case, the copy-up operation will get an error and
the original user operation which triggered the copy-up will fail. It
can happen that even all copy-up will fail.
When both of src and dst branches support XATTR and if an error occurs
during copying XATTR, then the copy-up should fail obviously. That is a
good reason and aufs should return an error to userspace. But when only
the src branch support that XATTR, aufs should not return an error.
For example, the src branch supports ACL but the dst branch doesn't
because the dst branch may natively un-support it or temporary
un-support it due to "noacl" mount option. Of course, the dst branch fs
may NOT return an error even if the XATTR is not supported. It is
totally up to the branch fs.
Anyway when the aufs internal copy-up gets an error from the dst branch
fs, then aufs tries removing the just copied entry and returns the error
to the userspace. The worst case of this situation will be all copy-up
will fail.
For the copy-up operation, there two basic approaches.
- copy the specified XATTR only (by category above), and return the
error unconditionally if it happens.
- copy all XATTR, and ignore the error on the specified category only.
In order to support XATTR and to implement the correct behaviour, aufs
chooses the latter approach and introduces some new branch attributes,
"icexsec", "icexsys", "icextr", "icexusr", and "icexoth".
They correspond to the XATTR namespaces (see above). Additionally, to be
convenient, "icex" is also provided which means all "icex*" attributes
are set (here the word "icex" stands for "ignore copy-error on XATTR").
The meaning of these attributes is to ignore the error from setting
XATTR on that branch.
Note that aufs tries copying all XATTR unconditionally, and ignores the
error from the dst branch according to the specified attributes.
Some XATTR may have its default value. The default value may come from
the parent dir or the environment. If the default value is set at the
file creating-time, it will be overwritten by copy-up.
Some contradiction may happen I am afraid.
Do we need another attribute to stop copying XATTR? I am unsure. For
now, aufs implements the branch attributes to ignore the error.
......@@ -88,6 +88,14 @@ config AUFS_INO_T_64
/* typedef unsigned long/int __kernel_ino_t */
/* alpha and s390x are int */
config AUFS_XATTR
bool "support for XATTR/EA (including Security Labels)"
help
If your branch fs supports XATTR/EA and you want to make them
available in aufs too, then enable this opsion and specify the
branch attributes for EA.
See detail in aufs.5.
config AUFS_DEBUG
bool "Debug aufs"
help
......
......@@ -25,4 +25,6 @@ aufs-$(CONFIG_SYSFS) += sysfs.o
aufs-$(CONFIG_AUFS_HNOTIFY) += hnotify.o
aufs-$(CONFIG_AUFS_HFSNOTIFY) += hfsnotify.o
aufs-$(CONFIG_AUFS_EXPORT) += export.o
aufs-$(CONFIG_AUFS_XATTR) += xattr.o
aufs-$(CONFIG_FS_POSIX_ACL) += posix_acl.o
aufs-$(CONFIG_AUFS_DEBUG) += debug.o
......@@ -10,7 +10,6 @@
#include <linux/fs_stack.h>
#include <linux/mm.h>
#include <linux/task_work.h>
#include <linux/uaccess.h>
#include "aufs.h"
void au_cpup_attr_flags(struct inode *dst, unsigned int iflags)
......@@ -155,15 +154,19 @@ static noinline_for_stack
int cpup_iattr(struct dentry *dst, aufs_bindex_t bindex, struct dentry *h_src,
struct au_cpup_reg_attr *h_src_attr)
{
int err, sbits;
int err, sbits, icex;
unsigned int mnt_flags;
unsigned char verbose;
struct iattr ia;
struct path h_path;
struct inode *h_isrc, *h_idst;
struct kstat *h_st;
struct au_branch *br;
h_path.dentry = au_h_dptr(dst, bindex);
h_idst = d_inode(h_path.dentry);
h_path.mnt = au_sbr_mnt(dst->d_sb, bindex);
br = au_sbr(dst->d_sb, bindex);
h_path.mnt = au_br_mnt(br);
h_isrc = d_inode(h_src);
ia.ia_valid = ATTR_FORCE | ATTR_UID | ATTR_GID
| ATTR_ATIME | ATTR_MTIME
......@@ -204,6 +207,15 @@ int cpup_iattr(struct dentry *dst, aufs_bindex_t bindex, struct dentry *h_src,
err = vfsub_notify_change(&h_path, &ia, /*delegated*/NULL);
}
icex = br->br_perm & AuBrAttr_ICEX;
if (!err) {
mnt_flags = au_mntflags(dst->d_sb);
/* re-commit later */
/* verbose = !!au_opt_test(mnt_flags, VERBOSE); */
verbose = 0;
err = au_cpup_xattr(h_path.dentry, h_src, icex, verbose);
}
return err;
}
......@@ -573,10 +585,43 @@ out:
return err;
}
static void au_do_cpup_dir(struct au_cp_generic *cpg, struct dentry *dst_parent)
/*
* regardless 'acl' option, reset all ACL.
* All ACL will be copied up later from the original entry on the lower branch.
*/
static int au_reset_acl(struct inode *h_dir, struct path *h_path, umode_t mode)
{
int err;
struct dentry *h_dentry;
struct inode *h_inode;
h_dentry = h_path->dentry;
h_inode = d_inode(h_dentry);
/* forget_all_cached_acls(h_inode)); */
err = vfsub_removexattr(h_dentry, XATTR_NAME_POSIX_ACL_ACCESS);
AuTraceErr(err);
if (err == -EOPNOTSUPP)
err = 0;
if (!err)
err = vfsub_acl_chmod(h_inode, mode);
AuTraceErr(err);
return err;
}
static int au_do_cpup_dir(struct au_cp_generic *cpg, struct dentry *dst_parent,
struct inode *h_dir, struct path *h_path)
{
int err;
struct inode *dir, *inode;
err = vfsub_removexattr(h_path->dentry, XATTR_NAME_POSIX_ACL_DEFAULT);
AuTraceErr(err);
if (err == -EOPNOTSUPP)
err = 0;
if (unlikely(err))
goto out;
/*
* strange behaviour from the users view,
* particularly setattr case
......@@ -586,6 +631,9 @@ static void au_do_cpup_dir(struct au_cp_generic *cpg, struct dentry *dst_parent)
au_cpup_attr_nlink(dir, /*force*/1);
inode = d_inode(cpg->dentry);
au_cpup_attr_nlink(inode, /*force*/1);
out:
return err;
}
static noinline_for_stack
......@@ -640,7 +688,7 @@ int cpup_entry(struct au_cp_generic *cpg, struct dentry *dst_parent,
isdir = 1;
err = vfsub_mkdir(h_dir, &h_path, mode);
if (!err)
au_do_cpup_dir(cpg, dst_parent);
err = au_do_cpup_dir(cpg, dst_parent, h_dir, &h_path);
break;
case S_IFLNK:
err = au_do_cpup_symlink(&h_path, h_src, h_dir);
......@@ -657,6 +705,8 @@ int cpup_entry(struct au_cp_generic *cpg, struct dentry *dst_parent,
AuIOErr("Unknown inode type 0%o\n", mode);
err = -EIO;
}
if (!err)
err = au_reset_acl(h_dir, &h_path, mode);
mnt_flags = au_mntflags(sb);
if (!au_opt_test(mnt_flags, UDBA_NONE)
......
......@@ -14,6 +14,7 @@
#include <linux/fs.h>
#include <linux/magic.h>
#include <linux/nfs_fs.h>
#include <linux/romfs_fs.h>
static inline int au_test_aufs(struct super_block *sb)
......@@ -329,5 +330,16 @@ static inline int au_test_fs_rr(struct super_block *sb)
|| au_test_romfs(sb);
}
/*
* test if the @inode is nfs with 'noacl' option
* NFS always sets SB_POSIXACL regardless its mount option 'noacl.'
*/
static inline int au_test_nfs_noacl(struct inode *inode)
{
return au_test_nfs(inode->i_sb)
/* && IS_POSIXACL(inode) */
&& !nfs_server_capable(inode, NFS_CAP_ACLS);
}
#endif /* __KERNEL__ */
#endif /* __AUFS_FSTYPE_H__ */
......@@ -35,13 +35,19 @@ static int h_permission(struct inode *h_inode, int mask,
* - skip the lower fs test in the case of write to ro branch.
* - nfs dir permission write check is optimized, but a policy for
* link/rename requires a real check.
* - nfs always sets SB_POSIXACL regardless its mount option 'noacl.'
* in this case, generic_permission() returns -EOPNOTSUPP.
*/
if ((write_mask && !au_br_writable(brperm))
|| (au_test_nfs(h_inode->i_sb) && S_ISDIR(h_inode->i_mode)
&& write_mask && !(mask & MAY_READ))
|| !h_inode->i_op->permission) {
/* AuLabel(generic_permission); */
/* AuDbg("get_acl %ps\n", h_inode->i_op->get_acl); */
err = generic_permission(h_inode, mask);
if (err == -EOPNOTSUPP && au_test_nfs_noacl(h_inode))
err = h_inode->i_op->permission(h_inode, mask);
AuTraceErr(err);
} else {
/* AuLabel(h_inode->permission); */
err = h_inode->i_op->permission(h_inode, mask);
......@@ -821,6 +827,12 @@ static int aufs_setattr(struct dentry *dentry, struct iattr *ia)
break;
}
}
/*
* regardless aufs 'acl' option setting.
* why don't all acl-aware fs call this func from their ->setattr()?
*/
if (!err && (ia->ia_valid & ATTR_MODE))
err = vfsub_acl_chmod(a->h_inode, ia->ia_mode);
if (!err)
au_cpup_attr_changeable(inode);
......@@ -845,6 +857,98 @@ out:
return err;
}
#if IS_ENABLED(CONFIG_AUFS_XATTR) || IS_ENABLED(CONFIG_FS_POSIX_ACL)
static int au_h_path_to_set_attr(struct dentry *dentry,
struct au_icpup_args *a, struct path *h_path)
{
int err;
struct super_block *sb;
sb = dentry->d_sb;
a->udba = au_opt_udba(sb);
/* no d_unlinked(), to set UDBA_NONE for root */
if (d_unhashed(dentry))
a->udba = AuOpt_UDBA_NONE;
if (a->udba != AuOpt_UDBA_NONE) {
AuDebugOn(IS_ROOT(dentry));
err = au_reval_for_attr(dentry, au_sigen(sb));
if (unlikely(err))
goto out;
}
err = au_pin_and_icpup(dentry, /*ia*/NULL, a);
if (unlikely(err < 0))
goto out;
h_path->dentry = a->h_path.dentry;
h_path->mnt = au_sbr_mnt(sb, a->btgt);
out:
return err;
}
ssize_t au_sxattr(struct dentry *dentry, struct inode *inode,
struct au_sxattr *arg)
{
int err;
struct path h_path;
struct super_block *sb;
struct au_icpup_args *a;
struct inode *h_inode;
IMustLock(inode);
err = -ENOMEM;
a = kzalloc(sizeof(*a), GFP_NOFS);
if (unlikely(!a))
goto out;
sb = dentry->d_sb;
err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
if (unlikely(err))
goto out_kfree;
h_path.dentry = NULL; /* silence gcc */
di_write_lock_child(dentry);
err = au_h_path_to_set_attr(dentry, a, &h_path);
if (unlikely(err))
goto out_di;
inode_unlock(a->h_inode);
switch (arg->type) {
case AU_XATTR_SET:
AuDebugOn(d_is_negative(h_path.dentry));
err = vfsub_setxattr(h_path.dentry,
arg->u.set.name, arg->u.set.value,
arg->u.set.size, arg->u.set.flags);
break;
case AU_ACL_SET:
err = -EOPNOTSUPP;
h_inode = d_inode(h_path.dentry);
if (h_inode->i_op->set_acl)
/* this will call posix_acl_update_mode */
err = h_inode->i_op->set_acl(h_inode,
arg->u.acl_set.acl,
arg->u.acl_set.type);
break;
}
if (!err)
au_cpup_attr_timesizes(inode);
au_unpin(&a->pin);
if (unlikely(err))
au_update_dbtop(dentry);
out_di:
di_write_unlock(dentry);
si_read_unlock(sb);
out_kfree:
au_kfree_rcu(a);
out:
AuTraceErr(err);
return err;
}
#endif
static void au_refresh_iattr(struct inode *inode, struct kstat *st,
unsigned int nlink)
{
......@@ -875,10 +979,12 @@ static void au_refresh_iattr(struct inode *inode, struct kstat *st,
}
/*
* common routine for aufs_getattr() and au_getxattr().
* returns zero or negative (an error).
* @dentry will be read-locked in success.
*/
int au_h_path_getattr(struct dentry *dentry, int force, struct path *h_path)
int au_h_path_getattr(struct dentry *dentry, int force, struct path *h_path,
int locked)
{
int err;
unsigned int mnt_flags, sigen;
......@@ -895,6 +1001,9 @@ int au_h_path_getattr(struct dentry *dentry, int force, struct path *h_path)
mnt_flags = au_mntflags(sb);
udba_none = !!au_opt_test(mnt_flags, UDBA_NONE);
if (unlikely(locked))
goto body; /* skip locking dinfo */
/* support fstat(2) */
if (!d_unlinked(dentry) && !udba_none) {
sigen = au_sigen(sb);
......@@ -922,6 +1031,7 @@ int au_h_path_getattr(struct dentry *dentry, int force, struct path *h_path)
} else
di_read_lock_child(dentry, AuLock_IR);
body:
inode = d_inode(dentry);
bindex = au_ibtop(inode);
h_path->mnt = au_sbr_mnt(sb, bindex);
......@@ -962,7 +1072,7 @@ static int aufs_getattr(const struct path *path, struct kstat *st,
err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
if (unlikely(err))
goto out;
err = au_h_path_getattr(dentry, /*force*/0, &h_path);
err = au_h_path_getattr(dentry, /*force*/0, &h_path, /*locked*/0);
if (unlikely(err))
goto out_si;
if (unlikely(!h_path.dentry))
......@@ -1128,9 +1238,18 @@ struct inode_operations aufs_iop_nogetattr[AuIop_Last],
aufs_iop[] = {
[AuIop_SYMLINK] = {
.permission = aufs_permission,
#ifdef CONFIG_FS_POSIX_ACL
.get_acl = aufs_get_acl,
.set_acl = aufs_set_acl, /* unsupport for symlink? */
#endif
.setattr = aufs_setattr,
.getattr = aufs_getattr,
#ifdef CONFIG_AUFS_XATTR
.listxattr = aufs_listxattr,
#endif
.get_link = aufs_get_link,
/* .update_time = aufs_update_time */
......@@ -1147,17 +1266,35 @@ struct inode_operations aufs_iop_nogetattr[AuIop_Last],
.rename = aufs_rename,
.permission = aufs_permission,
#ifdef CONFIG_FS_POSIX_ACL
.get_acl = aufs_get_acl,
.set_acl = aufs_set_acl,
#endif
.setattr = aufs_setattr,
.getattr = aufs_getattr,
#ifdef CONFIG_AUFS_XATTR
.listxattr = aufs_listxattr,
#endif
.update_time = aufs_update_time,
.tmpfile = aufs_tmpfile
},
[AuIop_OTHER] = {
.permission = aufs_permission,
#ifdef CONFIG_FS_POSIX_ACL
.get_acl = aufs_get_acl,
.set_acl = aufs_set_acl,
#endif
.setattr = aufs_setattr,
.getattr = aufs_getattr,
#ifdef CONFIG_AUFS_XATTR
.listxattr = aufs_listxattr,
#endif
.update_time = aufs_update_time
}
};
......@@ -257,6 +257,10 @@ static int set_inode(struct inode *inode, struct dentry *dentry)
au_igrab(d_inode(h_dentry)), flags);
}
au_cpup_attr_all(inode, /*force*/1);
/*
* to force calling aufs_get_acl() every time,
* do not call cache_no_acl() for aufs inode.
*/
out:
return err;
......
......@@ -184,7 +184,8 @@ struct au_icpup_args {
int au_pin_and_icpup(struct dentry *dentry, struct iattr *ia,
struct au_icpup_args *a);
int au_h_path_getattr(struct dentry *dentry, int force, struct path *h_path);
int au_h_path_getattr(struct dentry *dentry, int force, struct path *h_path,
int locked);
/* i_op_add.c */
int au_may_add(struct dentry *dentry, aufs_bindex_t bindex,
......@@ -276,6 +277,48 @@ AuStubVoid(au_plink_put, struct super_block *sb, int verbose);
AuStubVoid(au_plink_clean, struct super_block *sb, int verbose);
#endif /* CONFIG_PROC_FS */
#ifdef CONFIG_AUFS_XATTR
/* xattr.c */
int au_cpup_xattr(struct dentry *h_dst, struct dentry *h_src, int ignore_flags,
unsigned int verbose);
ssize_t aufs_listxattr(struct dentry *dentry, char *list, size_t size);
void au_xattr_init(struct super_block *sb);
#else
AuStubInt0(au_cpup_xattr, struct dentry *h_dst, struct dentry *h_src,
int ignore_flags, unsigned int verbose);
AuStubVoid(au_xattr_init, struct super_block *sb);
#endif
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *aufs_get_acl(struct inode *inode, int type);
int aufs_set_acl(struct inode *inode, struct posix_acl *acl, int type);
#endif
#if IS_ENABLED(CONFIG_AUFS_XATTR) || IS_ENABLED(CONFIG_FS_POSIX_ACL)
enum {
AU_XATTR_SET,
AU_ACL_SET
};
struct au_sxattr {
int type;
union {
struct {
const char *name;
const void *value;
size_t size;
int flags;
} set;
struct {
struct posix_acl *acl;
int type;
} acl_set;
} u;
};
ssize_t au_sxattr(struct dentry *dentry, struct inode *inode,
struct au_sxattr *arg);
#endif
/* ---------------------------------------------------------------------- */
/* lock subclass for iinfo */
......
......@@ -27,6 +27,7 @@ enum {
Opt_udba,
Opt_dio, Opt_nodio,
Opt_wbr_copyup, Opt_wbr_create,
Opt_acl, Opt_noacl,
Opt_tail, Opt_ignore, Opt_ignore_silent, Opt_err
};
......@@ -74,6 +75,15 @@ static match_table_t options = {
{Opt_wbr_copyup, "copyup=%s"},
{Opt_wbr_copyup, "copyup_policy=%s"},
/* generic VFS flag */
#ifdef CONFIG_FS_POSIX_ACL
{Opt_acl, "acl"},
{Opt_noacl, "noacl"},
#else
{Opt_ignore, "acl"},
{Opt_ignore_silent, "noacl"},
#endif
/* internal use for the scripts */
{Opt_ignore_silent, "si=%s"},
......@@ -131,6 +141,15 @@ static match_table_t brperm = {
};
static match_table_t brattr = {
#ifdef CONFIG_AUFS_XATTR
{AuBrAttr_ICEX, AUFS_BRATTR_ICEX},
{AuBrAttr_ICEX_SEC, AUFS_BRATTR_ICEX_SEC},
{AuBrAttr_ICEX_SYS, AUFS_BRATTR_ICEX_SYS},
{AuBrAttr_ICEX_TR, AUFS_BRATTR_ICEX_TR},
{AuBrAttr_ICEX_USR, AUFS_BRATTR_ICEX_USR},
{AuBrAttr_ICEX_OTH, AUFS_BRATTR_ICEX_OTH},
#endif
/* ro/rr branch */
{AuBrRAttr_WH, AUFS_BRRATTR_WH},
......@@ -529,6 +548,12 @@ static void dump_opts(struct au_opts *opts)
AuDbg("copyup %d, %s\n", opt->wbr_copyup,
au_optstr_wbr_copyup(opt->wbr_copyup));
break;
case Opt_acl:
AuLabel(acl);
break;
case Opt_noacl:
AuLabel(noacl);
break;
default:
BUG();
}
......@@ -801,6 +826,8 @@ int au_opts_parse(struct super_block *sb, char *str, struct au_opts *opts)
case Opt_nodio:
case Opt_rdblk_def:
case Opt_rdhash_def:
case Opt_acl:
case Opt_noacl:
err = 0;
opt->type = token;
break;
......@@ -998,6 +1025,13 @@ static int au_opt_simple(struct super_block *sb, struct au_opt *opt,
au_fclr_opts(opts->flags, TRUNC_XIB);
break;
case Opt_acl:
sb->s_flags |= SB_POSIXACL;
break;
case Opt_noacl:
sb->s_flags &= ~SB_POSIXACL;
break;
default:
err = 0;
break;
......@@ -1091,8 +1125,17 @@ int au_opts_verify(struct super_block *sb, unsigned long sb_flags,
skip = 0;
h_dir = au_h_iptr(dir, bindex);
br = au_sbr(sb, bindex);
do_free = 0;
if ((br->br_perm & AuBrAttr_ICEX)
&& !h_dir->i_op->listxattr)
br->br_perm &= ~AuBrAttr_ICEX;
#if 0
if ((br->br_perm & AuBrAttr_ICEX_SEC)
&& (au_br_sb(br)->s_flags & SB_NOSEC))
br->br_perm &= ~AuBrAttr_ICEX_SEC;
#endif
do_free = 0;
wbr = br->br_wbr;
if (wbr)
wbr_wh_read_lock(wbr);
......
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2014-2019 Junjiro R. Okajima
*/
/*
* posix acl operations
*/
#include <linux/fs.h>
#include "aufs.h"
struct posix_acl *aufs_get_acl(struct inode *inode, int type)
{
struct posix_acl *acl;
int err;
aufs_bindex_t bindex;
struct inode *h_inode;
struct super_block *sb;
acl = NULL;
sb = inode->i_sb;
si_read_lock(sb, AuLock_FLUSH);
ii_read_lock_child(inode);
if (!(sb->s_flags & SB_POSIXACL))
goto out;
bindex = au_ibtop(inode);
h_inode = au_h_iptr(inode, bindex);
if (unlikely(!h_inode
|| ((h_inode->i_mode & S_IFMT)
!= (inode->i_mode & S_IFMT)))) {
err = au_busy_or_stale();
acl = ERR_PTR(err);
goto out;
}
/* always topmost only */
acl = get_acl(h_inode, type);
if (!IS_ERR_OR_NULL(acl))
set_cached_acl(inode, type, acl);
out:
ii_read_unlock(inode);
si_read_unlock(sb);
AuTraceErrPtr(acl);
return acl;
}
int aufs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
{
int err;
ssize_t ssz;
struct dentry *dentry;
struct au_sxattr arg = {
.type = AU_ACL_SET,
.u.acl_set = {
.acl = acl,
.type = type
},
};
IMustLock(inode);
if (inode->i_ino == AUFS_ROOT_INO)
dentry = dget(inode->i_sb->s_root);
else {
dentry = d_find_alias(inode);
if (!dentry)
dentry = d_find_any_alias(inode);
if (!dentry) {
pr_warn("cannot handle this inode, "
"please report to aufs-users ML\n");
err = -ENOENT;
goto out;
}
}
ssz = au_sxattr(dentry, inode, &arg);
dput(dentry);
err = ssz;
if (ssz >= 0) {
err = 0;
set_cached_acl(inode, type, acl);
}
out:
return err;
}
......@@ -8,7 +8,6 @@
*/
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include "aufs.h"
static int au_procfs_plm_release(struct inode *inode, struct file *file)
......
......@@ -234,6 +234,8 @@ static int aufs_show_options(struct seq_file *m, struct dentry *dentry)
} while (0)
sb = dentry->d_sb;
if (sb->s_flags & SB_POSIXACL)
seq_puts(m, ",acl");
#if 0
if (sb->s_flags & SB_I_VERSION)
seq_puts(m, ",i_version");
......@@ -811,6 +813,8 @@ static int aufs_fill_super(struct super_block *sb, void *raw_data,
sb->s_magic = AUFS_SUPER_MAGIC;
sb->s_maxbytes = 0;
sb->s_stack_depth = 1;
au_export_init(sb);
au_xattr_init(sb);
err = alloc_root(sb);
if (unlikely(err)) {
......
......@@ -9,7 +9,6 @@
#include <linux/namei.h>
#include <linux/security.h>
#include <linux/uaccess.h>
#include "aufs.h"
int vfsub_sync_filesystem(struct super_block *h_sb, int wait)
......
......@@ -14,6 +14,8 @@
#include <linux/fs.h>
#include <linux/mount.h>
#include <linux/posix_acl.h>
#include <linux/xattr.h>
#include "debug.h"
/* ---------------------------------------------------------------------- */
......@@ -170,6 +172,20 @@ static inline int vfsub_update_time(struct inode *h_inode,
/* no vfsub_update_h_iattr() since we don't have struct path */
}
#ifdef CONFIG_FS_POSIX_ACL
static inline int vfsub_acl_chmod(struct inode *h_inode, umode_t h_mode)
{
int err;
err = posix_acl_chmod(h_inode, h_mode);
if (err == -EOPNOTSUPP)
err = 0;
return err;
}
#else
AuStubInt0(vfsub_acl_chmod, struct inode *h_inode, umode_t h_mode);
#endif
/*
* re-use branch fs's ioctl(FICLONE) while aufs itself doesn't support such
* ioctl.
......@@ -214,5 +230,30 @@ static inline int vfsub_getattr(const struct path *path, struct kstat *st)
return vfs_getattr(path, st, STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT);
}
/* ---------------------------------------------------------------------- */
static inline int vfsub_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
int err;
lockdep_off();
err = vfs_setxattr(dentry, name, value, size, flags);
lockdep_on();
return err;
}
static inline int vfsub_removexattr(struct dentry *dentry, const char *name)
{
int err;
lockdep_off();
err = vfs_removexattr(dentry, name);
lockdep_on();
return err;
}
#endif /* __KERNEL__ */
#endif /* __AUFS_VFSUB_H__ */
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2014-2019 Junjiro R. Okajima
*/
/*
* handling xattr functions
*/
#include <linux/fs.h>
#include <linux/posix_acl_xattr.h>
#include <linux/xattr.h>
#include "aufs.h"
static int au_xattr_ignore(int err, char *name, unsigned int ignore_flags)
{
if (!ignore_flags)
goto out;
switch (err) {
case -ENOMEM:
case -EDQUOT:
goto out;
}
if ((ignore_flags & AuBrAttr_ICEX) == AuBrAttr_ICEX) {
err = 0;
goto out;
}
#define cmp(brattr, prefix) do { \
if (!strncmp(name, XATTR_##prefix##_PREFIX, \
XATTR_##prefix##_PREFIX_LEN)) { \
if (ignore_flags & AuBrAttr_ICEX_##brattr) \
err = 0; \
goto out; \
} \
} while (0)
cmp(SEC, SECURITY);
cmp(SYS, SYSTEM);
cmp(TR, TRUSTED);
cmp(USR, USER);
#undef cmp
if (ignore_flags & AuBrAttr_ICEX_OTH)
err = 0;
out:
return err;
}
static const int au_xattr_out_of_list = AuBrAttr_ICEX_OTH << 1;
static int au_do_cpup_xattr(struct dentry *h_dst, struct dentry *h_src,
char *name, char **buf, unsigned int ignore_flags,
unsigned int verbose)
{
int err;
ssize_t ssz;
struct inode *h_idst;
ssz = vfs_getxattr_alloc(h_src, name, buf, 0, GFP_NOFS);
err = ssz;
if (unlikely(err <= 0)) {
if (err == -ENODATA
|| (err == -EOPNOTSUPP
&& ((ignore_flags & au_xattr_out_of_list)
|| (au_test_nfs_noacl(d_inode(h_src))
&& (!strcmp(name, XATTR_NAME_POSIX_ACL_ACCESS)
|| !strcmp(name,
XATTR_NAME_POSIX_ACL_DEFAULT))))
))
err = 0;
if (err && (verbose || au_debug_test()))
pr_err("%s, err %d\n", name, err);
goto out;
}
/* unlock it temporary */
h_idst = d_inode(h_dst);
inode_unlock(h_idst);
err = vfsub_setxattr(h_dst, name, *buf, ssz, /*flags*/0);
inode_lock_nested(h_idst, AuLsc_I_CHILD2);
if (unlikely(err)) {
if (verbose || au_debug_test())
pr_err("%s, err %d\n", name, err);
err = au_xattr_ignore(err, name, ignore_flags);
}
out:
return err;
}
int au_cpup_xattr(struct dentry *h_dst, struct dentry *h_src, int ignore_flags,
unsigned int verbose)
{
int err, unlocked, acl_access, acl_default;
ssize_t ssz;
struct inode *h_isrc, *h_idst;
char *value, *p, *o, *e;
/* try stopping to update the source inode while we are referencing */
/* there should not be the parent-child relationship between them */
h_isrc = d_inode(h_src);
h_idst = d_inode(h_dst);
inode_unlock(h_idst);
inode_lock_shared_nested(h_isrc, AuLsc_I_CHILD);
inode_lock_nested(h_idst, AuLsc_I_CHILD2);
unlocked = 0;
/* some filesystems don't list POSIX ACL, for example tmpfs */
ssz = vfs_listxattr(h_src, NULL, 0);
err = ssz;
if (unlikely(err < 0)) {
AuTraceErr(err);
if (err == -ENODATA
|| err == -EOPNOTSUPP)
err = 0; /* ignore */
goto out;
}
err = 0;
p = NULL;
o = NULL;
if (ssz) {
err = -ENOMEM;
p = kmalloc(ssz, GFP_NOFS);
o = p;
if (unlikely(!p))
goto out;
err = vfs_listxattr(h_src, p, ssz);
}
inode_unlock_shared(h_isrc);
unlocked = 1;
AuDbg("err %d, ssz %zd\n", err, ssz);
if (unlikely(err < 0))
goto out_free;
err = 0;
e = p + ssz;
value = NULL;
acl_access = 0;
acl_default = 0;
while (!err && p < e) {
acl_access |= !strncmp(p, XATTR_NAME_POSIX_ACL_ACCESS,
sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1);
acl_default |= !strncmp(p, XATTR_NAME_POSIX_ACL_DEFAULT,
sizeof(XATTR_NAME_POSIX_ACL_DEFAULT)
- 1);
err = au_do_cpup_xattr(h_dst, h_src, p, &value, ignore_flags,
verbose);
p += strlen(p) + 1;
}
AuTraceErr(err);
ignore_flags |= au_xattr_out_of_list;
if (!err && !acl_access) {
err = au_do_cpup_xattr(h_dst, h_src,
XATTR_NAME_POSIX_ACL_ACCESS, &value,
ignore_flags, verbose);
AuTraceErr(err);
}
if (!err && !acl_default) {
err = au_do_cpup_xattr(h_dst, h_src,
XATTR_NAME_POSIX_ACL_DEFAULT, &value,
ignore_flags, verbose);
AuTraceErr(err);
}
au_kfree_try_rcu(value);
out_free:
au_kfree_try_rcu(o);
out:
if (!unlocked)
inode_unlock_shared(h_isrc);
AuTraceErr(err);
return err;
}
/* ---------------------------------------------------------------------- */
static int au_smack_reentering(struct super_block *sb)
{
#if IS_ENABLED(CONFIG_SECURITY_SMACK)
/*
* as a part of lookup, smack_d_instantiate() is called, and it calls
* i_op->getxattr(). ouch.
*/
return si_pid_test(sb);
#else
return 0;
#endif
}
enum {
AU_XATTR_LIST,
AU_XATTR_GET
};
struct au_lgxattr {
int type;
union {
struct {
char *list;
size_t size;
} list;
struct {
const char *name;
void *value;
size_t size;
} get;
} u;
};
static ssize_t au_lgxattr(struct dentry *dentry, struct au_lgxattr *arg)
{
ssize_t err;
int reenter;
struct path h_path;
struct super_block *sb;
sb = dentry->d_sb;
reenter = au_smack_reentering(sb);
if (!reenter) {
err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
if (unlikely(err))
goto out;
}
err = au_h_path_getattr(dentry, /*force*/1, &h_path, reenter);
if (unlikely(err))
goto out_si;
if (unlikely(!h_path.dentry))
/* illegally overlapped or something */
goto out_di; /* pretending success */
/* always topmost entry only */
switch (arg->type) {
case AU_XATTR_LIST:
err = vfs_listxattr(h_path.dentry,
arg->u.list.list, arg->u.list.size);
break;
case AU_XATTR_GET:
AuDebugOn(d_is_negative(h_path.dentry));
err = vfs_getxattr(h_path.dentry,
arg->u.get.name, arg->u.get.value,
arg->u.get.size);
break;
}
out_di:
if (!reenter)
di_read_unlock(dentry, AuLock_IR);
out_si:
if (!reenter)
si_read_unlock(sb);
out:
AuTraceErr(err);
return err;
}
ssize_t aufs_listxattr(struct dentry *dentry, char *list, size_t size)
{
struct au_lgxattr arg = {
.type = AU_XATTR_LIST,
.u.list = {
.list = list,
.size = size
},
};
return au_lgxattr(dentry, &arg);
}
static ssize_t au_getxattr(struct dentry *dentry,
struct inode *inode __maybe_unused,
const char *name, void *value, size_t size)
{
struct au_lgxattr arg = {
.type = AU_XATTR_GET,
.u.get = {
.name = name,
.value = value,
.size = size
},
};
return au_lgxattr(dentry, &arg);
}
static int au_setxattr(struct dentry *dentry, struct inode *inode,
const char *name, const void *value, size_t size,
int flags)
{
struct au_sxattr arg = {
.type = AU_XATTR_SET,
.u.set = {
.name = name,
.value = value,
.size = size,
.flags = flags
},
};
return au_sxattr(dentry, inode, &arg);
}
/* ---------------------------------------------------------------------- */
static int au_xattr_get(const struct xattr_handler *handler,
struct dentry *dentry, struct inode *inode,
const char *name, void *buffer, size_t size)
{
return au_getxattr(dentry, inode, name, buffer, size);
}
static int au_xattr_set(const struct xattr_handler *handler,
struct dentry *dentry, struct inode *inode,
const char *name, const void *value, size_t size,
int flags)
{
return au_setxattr(dentry, inode, name, value, size, flags);
}
static const struct xattr_handler au_xattr_handler = {
.name = "",
.prefix = "",
.get = au_xattr_get,
.set = au_xattr_set
};
static const struct xattr_handler *au_xattr_handlers[] = {
#ifdef CONFIG_FS_POSIX_ACL
&posix_acl_access_xattr_handler,
&posix_acl_default_xattr_handler,
#endif
&au_xattr_handler, /* must be last */
NULL
};
void au_xattr_init(struct super_block *sb)
{
sb->s_xattr = au_xattr_handlers;
}
......@@ -24,7 +24,6 @@
#include <linux/seq_file.h>
#include <linux/statfs.h>
#include <linux/uaccess.h>
#include "aufs.h"
static aufs_bindex_t sbr_find_shared(struct super_block *sb, aufs_bindex_t btop,
......
......@@ -103,6 +103,12 @@ typedef int16_t aufs_bindex_t;
#define AUFS_BRPERM_RW "rw"
#define AUFS_BRPERM_RO "ro"
#define AUFS_BRPERM_RR "rr"
#define AUFS_BRATTR_ICEX "icex"
#define AUFS_BRATTR_ICEX_SEC "icexsec"
#define AUFS_BRATTR_ICEX_SYS "icexsys"
#define AUFS_BRATTR_ICEX_TR "icextr"
#define AUFS_BRATTR_ICEX_USR "icexusr"
#define AUFS_BRATTR_ICEX_OTH "icexoth"
#define AUFS_BRRATTR_WH "wh"
#define AUFS_BRWATTR_NLWH "nolwh"
......@@ -111,14 +117,49 @@ typedef int16_t aufs_bindex_t;
#define AuBrPerm_RR (1 << 2) /* natively readonly */
#define AuBrPerm_Mask (AuBrPerm_RW | AuBrPerm_RO | AuBrPerm_RR)
#define AuBrRAttr_WH (1 << 7) /* whiteout-able */
/* ignore error in copying XATTR */
#define AuBrAttr_ICEX_SEC (1 << 7)
#define AuBrAttr_ICEX_SYS (1 << 8)
#define AuBrAttr_ICEX_TR (1 << 9)
#define AuBrAttr_ICEX_USR (1 << 10)
#define AuBrAttr_ICEX_OTH (1 << 11)
#define AuBrAttr_ICEX (AuBrAttr_ICEX_SEC \
| AuBrAttr_ICEX_SYS \
| AuBrAttr_ICEX_TR \
| AuBrAttr_ICEX_USR \
| AuBrAttr_ICEX_OTH)
#define AuBrRAttr_WH (1 << 12) /* whiteout-able */
#define AuBrRAttr_Mask AuBrRAttr_WH
#define AuBrWAttr_NoLinkWH (1 << 8) /* un-hardlinkable whiteouts */
#define AuBrWAttr_NoLinkWH (1 << 13) /* un-hardlinkable whiteouts */
#define AuBrWAttr_Mask AuBrWAttr_NoLinkWH
/* #warning test userspace */
#ifdef __KERNEL__
#ifndef CONFIG_AUFS_XATTR
#undef AuBrAttr_ICEX
#define AuBrAttr_ICEX 0
#undef AuBrAttr_ICEX_SEC
#define AuBrAttr_ICEX_SEC 0
#undef AuBrAttr_ICEX_SYS
#define AuBrAttr_ICEX_SYS 0
#undef AuBrAttr_ICEX_TR
#define AuBrAttr_ICEX_TR 0
#undef AuBrAttr_ICEX_USR
#define AuBrAttr_ICEX_USR 0
#undef AuBrAttr_ICEX_OTH
#define AuBrAttr_ICEX_OTH 0
#endif
#endif
/* the longest combination */
#define AuBrPermStrSz sizeof(AUFS_BRPERM_RO \
/* AUFS_BRATTR_ICEX and AUFS_BRATTR_ICEX_TR don't affect here */
#define AuBrPermStrSz sizeof(AUFS_BRPERM_RW \
"+" AUFS_BRATTR_ICEX_SEC \
"+" AUFS_BRATTR_ICEX_SYS \
"+" AUFS_BRATTR_ICEX_USR \
"+" AUFS_BRATTR_ICEX_OTH \
"+" AUFS_BRWATTR_NLWH)
typedef struct {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment