Overlayfs does not cache ACL's (to avoid double caching). Instead it just
calls the underlying filesystem's i_op->get_acl(), which will return the
cached value, if possible.
In rcu path walk, however, get_cached_acl_rcu() is employed to get the
value from the cache, which will fail on overlayfs resulting in dropping
out of rcu walk mode. This can result in a big performance hit in certain
situations.
Fix by calling ->get_acl() with rcu=true in case of ACL_DONT_CACHE (which
indicates pass-through)
Reported-by: garyhuang <zjh.20052005@163.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
#include <linux/fiemap.h>
#include <linux/fileattr.h>
#include <linux/security.h>
#include <linux/fiemap.h>
#include <linux/fileattr.h>
#include <linux/security.h>
+#include <linux/namei.h>
const struct cred *old_cred;
struct posix_acl *acl;
const struct cred *old_cred;
struct posix_acl *acl;
- if (rcu)
- return ERR_PTR(-ECHILD);
-
if (!IS_ENABLED(CONFIG_FS_POSIX_ACL) || !IS_POSIXACL(realinode))
return NULL;
if (!IS_ENABLED(CONFIG_FS_POSIX_ACL) || !IS_POSIXACL(realinode))
return NULL;
+ if (rcu)
+ return get_cached_acl_rcu(realinode, type);
+
old_cred = ovl_override_creds(inode->i_sb);
acl = get_acl(realinode, type);
revert_creds(old_cred);
old_cred = ovl_override_creds(inode->i_sb);
acl = get_acl(realinode, type);
revert_creds(old_cred);
#include <linux/xattr.h>
#include <linux/export.h>
#include <linux/user_namespace.h>
#include <linux/xattr.h>
#include <linux/export.h>
#include <linux/user_namespace.h>
+#include <linux/namei.h>
static struct posix_acl **acl_by_type(struct inode *inode, int type)
{
static struct posix_acl **acl_by_type(struct inode *inode, int type)
{
struct posix_acl *get_cached_acl_rcu(struct inode *inode, int type)
{
struct posix_acl *get_cached_acl_rcu(struct inode *inode, int type)
{
- return rcu_dereference(*acl_by_type(inode, type));
+ struct posix_acl *acl = rcu_dereference(*acl_by_type(inode, type));
+
+ if (acl == ACL_DONT_CACHE) {
+ struct posix_acl *ret;
+
+ ret = inode->i_op->get_acl(inode, type, LOOKUP_RCU);
+ if (!IS_ERR(ret))
+ acl = ret;
+ }
+
+ return acl;
}
EXPORT_SYMBOL(get_cached_acl_rcu);
}
EXPORT_SYMBOL(get_cached_acl_rcu);
struct posix_acl;
#define ACL_NOT_CACHED ((void *)(-1))
struct posix_acl;
#define ACL_NOT_CACHED ((void *)(-1))
+/*
+ * ACL_DONT_CACHE is for stacked filesystems, that rely on underlying fs to
+ * cache the ACL. This also means that ->get_acl() can be called in RCU mode
+ * with the LOOKUP_RCU flag.
+ */
#define ACL_DONT_CACHE ((void *)(-3))
static inline struct posix_acl *
#define ACL_DONT_CACHE ((void *)(-3))
static inline struct posix_acl *
extern int set_posix_acl(struct user_namespace *, struct inode *, int,
struct posix_acl *);
extern int set_posix_acl(struct user_namespace *, struct inode *, int,
struct posix_acl *);
+struct posix_acl *get_cached_acl_rcu(struct inode *inode, int type);
+
#ifdef CONFIG_FS_POSIX_ACL
int posix_acl_chmod(struct user_namespace *, struct inode *, umode_t);
extern int posix_acl_create(struct inode *, umode_t *, struct posix_acl **,
#ifdef CONFIG_FS_POSIX_ACL
int posix_acl_chmod(struct user_namespace *, struct inode *, umode_t);
extern int posix_acl_create(struct inode *, umode_t *, struct posix_acl **,
extern int simple_acl_create(struct inode *, struct inode *);
struct posix_acl *get_cached_acl(struct inode *inode, int type);
extern int simple_acl_create(struct inode *, struct inode *);
struct posix_acl *get_cached_acl(struct inode *inode, int type);
-struct posix_acl *get_cached_acl_rcu(struct inode *inode, int type);
void set_cached_acl(struct inode *inode, int type, struct posix_acl *acl);
void forget_cached_acl(struct inode *inode, int type);
void forget_all_cached_acls(struct inode *inode);
void set_cached_acl(struct inode *inode, int type, struct posix_acl *acl);
void forget_cached_acl(struct inode *inode, int type);
void forget_all_cached_acls(struct inode *inode);