summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-11-13 13:03:45 +0000
committerMark Brown <broonie@kernel.org>2026-11-13 13:03:45 +0000
commit48d7d1c219806ad354744647c33ce35a7cc15bf4 (patch)
tree86d53cac4a54c2f9f0bc6c643f0e608826f5069c
parente896801285ee3cc166792c6e79a3af1ef5be522c (diff)
parent748739f3171ed1c44a30a98b0e0341b1a8d62d31 (diff)
downloadlinux-next-fs-current.tar.gz
linux-next-fs-current.zip
Merge branch 'ovl-fixes' of https://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.gitfs-current
-rw-r--r--Documentation/filesystems/overlayfs.rst24
-rw-r--r--fs/overlayfs/dir.c20
-rw-r--r--fs/overlayfs/namei.c61
-rw-r--r--fs/overlayfs/overlayfs.h22
-rw-r--r--fs/overlayfs/ovl_entry.h2
-rw-r--r--fs/overlayfs/params.c91
-rw-r--r--fs/overlayfs/params.h2
-rw-r--r--fs/overlayfs/super.c52
-rw-r--r--fs/overlayfs/util.c12
-rw-r--r--tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c45
10 files changed, 270 insertions, 61 deletions
diff --git a/Documentation/filesystems/overlayfs.rst b/Documentation/filesystems/overlayfs.rst
index 4c241d220bc2..e01f7810c027 100644
--- a/Documentation/filesystems/overlayfs.rst
+++ b/Documentation/filesystems/overlayfs.rst
@@ -236,14 +236,19 @@ Module options (can also be changed through /sys/module/overlay/parameters/):
Mount options:
- "redirect_dir=on":
- Redirects are enabled.
+ Redirects are created and followed.
- "redirect_dir=follow":
Redirects are not created, but followed.
+- "redirect_dir=followdata":
+ Redirects are not created.
+ Only redirects from lower to lowerdata are followed.
- "redirect_dir=nofollow":
Redirects are not created and not followed.
- "redirect_dir=off":
- If "redirect_always_follow" is enabled in the kernel/module config,
- this "off" translates to "follow", otherwise it translates to "nofollow".
+ Redirects are not created.
+ Which redirects are followed depends on "redirect_always_follow" in the
+ kernel/module config, on data-only lower layers and on the "userxattr"
+ mount option.
When the NFS export feature is enabled, every copied up directory is
indexed by the file handle of the lower inode and a file handle of the
@@ -418,8 +423,8 @@ pointed by REDIRECT. This should not be possible on local system as setting
"trusted." xattrs will require CAP_SYS_ADMIN. But it should be possible
for untrusted layers like from a pen drive.
-Note: redirect_dir={off|nofollow|follow[*]} and nfs_export=on mount options
-conflict with metacopy=on, and will result in an error.
+Note: redirect_dir={off|nofollow|followdata|follow[*]} and nfs_export=on
+mount options conflict with metacopy=on, and will result in an error.
[*] redirect_dir=follow only conflicts with metacopy=on if upperdir=... is
given.
@@ -460,10 +465,11 @@ to the absolute path of the "lower data" file in the "data-only" lower layer.
Instead of explicitly enabling "metacopy=on" it is sufficient to specify at
least one data-only layer to enable redirection of data to a data-only layer.
-In this case other forms of metacopy are rejected. Note: this way, data-only
-layers may be used together with "userxattr", in which case careful attention
-must be given to privileges needed to change the "user.overlay.redirect" xattr
-to prevent misuse.
+In this case other forms of metacopy are rejected. A data-only layer selects
+"redirect_dir=followdata", unless "redirect_dir=follow" or "redirect_dir=on" is
+in effect. Note: this way, data-only layers may be used together with
+"userxattr", in which case careful attention must be given to privileges needed
+to change the "user.overlay.redirect" xattr to prevent misuse.
Since kernel version v6.8, "data-only" lower layers can also be added using
the "datadir+" mount options and the fsconfig syscall from new mount api.
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index e0a164e22d67..8fec18dc5fc7 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -368,7 +368,7 @@ static bool ovl_type_origin(struct dentry *dentry)
}
static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
- struct ovl_cattr *attr)
+ struct ovl_cattr *attr, bool origin)
{
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
@@ -393,7 +393,7 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
ovl_set_opaque(dentry, newdentry);
}
- ovl_dir_modified(dentry->d_parent, false);
+ ovl_dir_modified(dentry->d_parent, origin);
err = ovl_instantiate(dentry, inode, newdentry, !!attr->hardlink, NULL);
if (err)
goto out_cleanup;
@@ -491,7 +491,7 @@ static int ovl_set_upper_acl(struct ovl_fs *ofs, struct dentry *upperdentry,
}
static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
- struct ovl_cattr *cattr)
+ struct ovl_cattr *cattr, bool origin)
{
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct dentry *workdir = ovl_workdir(dentry);
@@ -582,7 +582,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
if (err)
goto out_cleanup_unlocked;
}
- ovl_dir_modified(dentry->d_parent, false);
+ ovl_dir_modified(dentry->d_parent, origin);
err = ovl_instantiate(dentry, inode, newdentry, hardlink, NULL);
if (err) {
ovl_cleanup(ofs, upperdir, newdentry);
@@ -647,12 +647,12 @@ DEFINE_CLASS(ovl_override_creator_creds,
static int ovl_create_handle_whiteouts(struct dentry *dentry,
struct inode *inode,
- struct ovl_cattr *attr)
+ struct ovl_cattr *attr, bool origin)
{
if (!ovl_dentry_is_whiteout(dentry))
- return ovl_create_upper(dentry, inode, attr);
+ return ovl_create_upper(dentry, inode, attr, origin);
- return ovl_create_over_whiteout(dentry, inode, attr);
+ return ovl_create_over_whiteout(dentry, inode, attr, origin);
}
static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
@@ -687,12 +687,14 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
*/
if (attr->hardlink)
- return ovl_create_handle_whiteouts(dentry, inode, attr);
+ return ovl_create_handle_whiteouts(dentry, inode, attr,
+ origin);
scoped_class(ovl_override_creator_creds, cred, original_creds, dentry, inode, attr->mode) {
if (IS_ERR(cred))
return PTR_ERR(cred);
- return ovl_create_handle_whiteouts(dentry, inode, attr);
+ return ovl_create_handle_whiteouts(dentry, inode, attr,
+ origin);
}
}
return err;
diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c
index 64d63b260709..ddf60254888a 100644
--- a/fs/overlayfs/namei.c
+++ b/fs/overlayfs/namei.c
@@ -27,6 +27,7 @@ struct ovl_lookup_data {
bool last;
char *redirect;
char *upperredirect;
+ char *lowerdata_redirect;
int metacopy;
/* Referring to last redirect xattr */
bool absolute_redirect;
@@ -39,13 +40,12 @@ static int ovl_check_redirect(const struct path *path, struct ovl_lookup_data *d
char *buf;
struct ovl_fs *ofs = OVL_FS(d->sb);
- d->absolute_redirect = false;
buf = ovl_get_redirect_xattr(ofs, path, prelen + strlen(post));
if (IS_ERR_OR_NULL(buf))
return PTR_ERR(buf);
- if (buf[0] == '/') {
- d->absolute_redirect = true;
+ d->absolute_redirect = buf[0] == '/';
+ if (d->absolute_redirect) {
/*
* One of the ancestor path elements in an absolute path
* lookup in ovl_lookup_layer() could have been opaque and
@@ -1047,6 +1047,14 @@ out:
out_err:
pr_warn_ratelimited("lazy lowerdata lookup failed (%pd2, err=%i)\n",
dentry, err);
+ /*
+ * The data of a metacopy file is missing or is not a regular file in
+ * the data-only layers: the layers are corrupted. Do not return the
+ * error of the lookup (e.g. ENOENT) to the caller, it would make the
+ * file look like it does not exist.
+ */
+ if (err != -ENOMEM)
+ err = -EIO;
goto out;
}
@@ -1079,7 +1087,8 @@ static bool ovl_check_follow_redirect(struct ovl_lookup_data *d)
pr_warn_ratelimited("refusing to follow metacopy origin for (%pd2)\n", d->dentry);
return false;
}
- if ((d->redirect || d->upperredirect) && !ovl_redirect_follow(ofs)) {
+ /* Check follow to lower layer, redirects to lowerdata are not checked here */
+ if ((d->redirect || d->upperredirect) && !ovl_redirect_follow_lower(ofs)) {
pr_warn_ratelimited("refusing to follow redirect for (%pd2)\n", d->dentry);
return false;
}
@@ -1097,13 +1106,26 @@ struct ovl_lookup_ctx {
unsigned int ctr;
};
+/*
+ * Without metacopy=on, the absolute redirect of a lower metacopy file is
+ * followed only into the data-only layers.
+ */
+static bool ovl_is_lowerdata_redirect(struct ovl_lookup_ctx *ctx,
+ struct ovl_lookup_data *d)
+{
+ struct ovl_fs *ofs = OVL_FS(d->sb);
+
+ return ofs->numdatalayer && !ofs->config.metacopy && ctx->ctr &&
+ d->metacopy && d->absolute_redirect;
+}
+
static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data *d)
{
struct dentry *dentry = ctx->dentry;
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct ovl_entry *poe = OVL_E(dentry->d_parent);
struct ovl_entry *roe = OVL_E(dentry->d_sb->s_root);
- bool check_redirect = (ovl_redirect_follow(ofs) || ofs->numdatalayer);
+ bool check_redirect = ovl_redirect_follow(ofs);
struct dentry *upperdir;
struct dentry *this;
struct dentry *origin = NULL;
@@ -1234,6 +1256,10 @@ static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data
ctx->ctr++;
}
+ /* Lookup of lowerdata in the data-only layers only */
+ if (ovl_is_lowerdata_redirect(ctx, d))
+ d->stop = true;
+
if (d->stop)
break;
@@ -1251,6 +1277,8 @@ static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data
if (d->metacopy && ctx->ctr && ofs->numdatalayer && d->absolute_redirect) {
d->metacopy = 0;
ctx->ctr++;
+ d->lowerdata_redirect = d->redirect;
+ d->redirect = NULL;
} else if (!ovl_check_follow_redirect(d)) {
err = -EPERM;
return err;
@@ -1367,24 +1395,32 @@ static int ovl_lookup_layers(struct ovl_lookup_ctx *ctx, struct ovl_lookup_data
};
/* Store lowerdata redirect for lazy lookup */
- if (ctx->ctr > 1 && !d->is_dir && !ctx->stack[ctx->ctr - 1].dentry) {
- oip.lowerdata_redirect = d->redirect;
- d->redirect = NULL;
- }
+ if (d->lowerdata_redirect)
+ oip.lowerdata_redirect = d->lowerdata_redirect;
inode = ovl_get_inode(dentry->d_sb, &oip);
if (IS_ERR(inode))
return PTR_ERR(inode);
+ d->lowerdata_redirect = NULL;
+
ctx->inode = inode;
if (ctx->upperdentry && !uppermetacopy)
ovl_set_flag(OVL_UPPERDATA, ctx->inode);
if (metacopy_size > OVL_METACOPY_MIN_SIZE)
ovl_set_flag(OVL_HAS_DIGEST, ctx->inode);
- }
- ovl_dentry_init_reval(dentry, ctx->upperdentry, OVL_I_E(ctx->inode));
+ /*
+ * ovl_get_inode() puts ctx->upperdentry if it finds a cached
+ * inode, e.g. for another hard link, so use the upper dentry
+ * of the inode.
+ */
+ ovl_dentry_init_reval(dentry, ovl_i_dentry_upper(inode),
+ OVL_I_E(inode));
+ } else {
+ ovl_dentry_init_reval(dentry, NULL, NULL);
+ }
return 0;
}
@@ -1394,7 +1430,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
{
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct ovl_entry *poe = OVL_E(dentry->d_parent);
- bool check_redirect = (ovl_redirect_follow(ofs) || ofs->numdatalayer);
+ bool check_redirect = ovl_redirect_follow(ofs);
int err;
struct ovl_lookup_ctx ctx = {
.dentry = dentry,
@@ -1419,6 +1455,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
dput(ctx.index);
ovl_stack_free(ctx.stack, ctx.ctr);
kfree(d.redirect);
+ kfree(d.lowerdata_redirect);
if (err) {
ovl_free_entry(ctx.oe);
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index e42543d4c108..646f8ffc40ae 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -76,9 +76,15 @@ enum ovl_entry_flag {
};
enum {
- OVL_REDIRECT_OFF, /* "off" mode is never used. In effect */
- OVL_REDIRECT_FOLLOW, /* ...it translates to either "follow" */
- OVL_REDIRECT_NOFOLLOW, /* ...or "nofollow". */
+ /*
+ * "off" mode is never used after mount options are verified.
+ * In effect it translates to either "follow", "nofollow" or
+ * "followdata" (with data-only layers).
+ */
+ OVL_REDIRECT_OFF,
+ OVL_REDIRECT_FOLLOW,
+ OVL_REDIRECT_FOLLOWDATA,
+ OVL_REDIRECT_NOFOLLOW,
OVL_REDIRECT_ON,
};
@@ -582,6 +588,7 @@ static inline int ovl_set_impure_dir(struct dentry *dentry)
bool ovl_inuse_trylock(struct dentry *dentry);
void ovl_inuse_unlock(struct dentry *dentry);
bool ovl_is_inuse(struct dentry *dentry);
+void ovl_inuse_unlock_all(struct ovl_fs *ofs);
bool ovl_need_index(struct dentry *dentry);
int ovl_nlink_start(struct dentry *dentry);
void ovl_nlink_end(struct dentry *dentry);
@@ -632,11 +639,20 @@ static inline char ovl_get_opaquedir_val(struct ovl_fs *ofs,
return ovl_get_dir_xattr_val(ofs, path, OVL_XATTR_OPAQUE);
}
+/* Are redirects followed into lower layers? */
+static inline bool ovl_redirect_follow_lower(struct ovl_fs *ofs)
+{
+ return ofs->config.redirect_mode == OVL_REDIRECT_FOLLOW ||
+ ofs->config.redirect_mode == OVL_REDIRECT_ON;
+}
+
+/* Are redirects followed into lower or lowerdata layers? */
static inline bool ovl_redirect_follow(struct ovl_fs *ofs)
{
return ofs->config.redirect_mode != OVL_REDIRECT_NOFOLLOW;
}
+/* Are redirects created in upper dir? */
static inline bool ovl_redirect_dir(struct ovl_fs *ofs)
{
return ofs->config.redirect_mode == OVL_REDIRECT_ON;
diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
index ac07e8769f9b..5b2f1b930daa 100644
--- a/fs/overlayfs/ovl_entry.h
+++ b/fs/overlayfs/ovl_entry.h
@@ -24,6 +24,8 @@ struct ovl_config {
struct ovl_sb {
struct super_block *sb;
dev_t pseudo_dev;
+ /* Idmap of the first lower layer on this fs */
+ const struct mnt_idmap *lower_idmap;
/* Unusable (conflicting) uuid */
bool bad_uuid;
/* Used as a lower layer (but maybe also as upper) */
diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
index 5a15e5478a6b..d1ca4a9094f2 100644
--- a/fs/overlayfs/params.c
+++ b/fs/overlayfs/params.c
@@ -107,21 +107,40 @@ static int ovl_xino_def(void)
const struct constant_table ovl_parameter_redirect_dir[] = {
{ "off", OVL_REDIRECT_OFF },
{ "follow", OVL_REDIRECT_FOLLOW },
+ { "followdata", OVL_REDIRECT_FOLLOWDATA },
{ "nofollow", OVL_REDIRECT_NOFOLLOW },
{ "on", OVL_REDIRECT_ON },
{}
};
-static const char *ovl_redirect_mode(struct ovl_config *config)
+static const char *ovl_redirect_mode_name(int redirect_mode)
{
- return ovl_parameter_redirect_dir[config->redirect_mode].name;
+ return ovl_parameter_redirect_dir[redirect_mode].name;
}
+const char *ovl_redirect_mode(struct ovl_config *config)
+{
+ return ovl_redirect_mode_name(config->redirect_mode);
+}
+
+int ovl_redirect_follow_mode(bool follow_lower, size_t nr_data)
+{
+ if (ovl_redirect_always_follow && follow_lower)
+ return OVL_REDIRECT_FOLLOW;
+
+ /* Data-only layers cannot be used without following redirects to them */
+ return nr_data ? OVL_REDIRECT_FOLLOWDATA : OVL_REDIRECT_NOFOLLOW;
+}
+
+/*
+ * The default for show_options() is resolved as if there are no data-only layers,
+ * so that redirect_dir=followdata, which is auto selected with data-only layers,
+ * is displayed in mount options.
+ */
static int ovl_redirect_mode_def(void)
{
- return ovl_redirect_dir_def ? OVL_REDIRECT_ON :
- ovl_redirect_always_follow ? OVL_REDIRECT_FOLLOW :
- OVL_REDIRECT_NOFOLLOW;
+ return ovl_redirect_dir_def ? OVL_REDIRECT_ON :
+ ovl_redirect_follow_mode(true, 0);
}
static const struct constant_table ovl_parameter_verity[] = {
@@ -659,11 +678,6 @@ static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param)
break;
case Opt_redirect_dir:
config->redirect_mode = result.uint_32;
- if (config->redirect_mode == OVL_REDIRECT_OFF) {
- config->redirect_mode = ovl_redirect_always_follow ?
- OVL_REDIRECT_FOLLOW :
- OVL_REDIRECT_NOFOLLOW;
- }
ctx->set.redirect = true;
break;
case Opt_index:
@@ -819,7 +833,13 @@ int ovl_init_fs_context(struct fs_context *fc)
if (!ofs)
goto out_err;
- ofs->config.redirect_mode = ovl_redirect_mode_def();
+ /*
+ * The module default determines only if new redirects are created in
+ * upper dir. If they are not, the redirect follow mode will be
+ * determined after parsing mount options.
+ */
+ ofs->config.redirect_mode = ovl_redirect_dir_def ?
+ OVL_REDIRECT_ON : OVL_REDIRECT_OFF;
ofs->config.index = ovl_index_def;
ofs->config.uuid = ovl_uuid_def();
ofs->config.nfs_export = ovl_nfs_export_def;
@@ -845,15 +865,12 @@ void ovl_free_fs(struct ovl_fs *ofs)
struct vfsmount **mounts;
unsigned i;
+ ovl_inuse_unlock_all(ofs);
iput(ofs->workbasedir_trap);
iput(ofs->workdir_trap);
dput(ofs->whiteout);
dput(ofs->workdir);
- if (ofs->workdir_locked)
- ovl_inuse_unlock(ofs->workbasedir);
dput(ofs->workbasedir);
- if (ofs->upperdir_locked)
- ovl_inuse_unlock(ovl_upper_mnt(ofs)->mnt_root);
/* Reuse ofs->config.lowerdirs as a vfsmount array before freeing it */
mounts = (struct vfsmount **) ofs->config.lowerdirs;
@@ -880,6 +897,11 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
struct ovl_config *config)
{
struct ovl_opt_set set = ctx->set;
+ /* The requested redirect_dir=, for reporting conflicts with it */
+ int redirect_opt = config->redirect_mode;
+ /* The redirect follow mode for redirect_dir=off and for the default */
+ int follow_mode = ovl_redirect_follow_mode(!config->userxattr,
+ ctx->nr_data);
/* Workdir/index are useless in non-upper mount */
if (!config->upperdir) {
@@ -907,6 +929,13 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
}
/*
+ * Resolve redirect_dir=off to the default redirect follow mode.
+ * With userxattr, redirects are not followed to lower by default.
+ */
+ if (config->redirect_mode == OVL_REDIRECT_OFF)
+ config->redirect_mode = follow_mode;
+
+ /*
* This is to make the logic below simpler. It doesn't make any other
* difference, since redirect_dir=on is only used for upper.
*/
@@ -917,7 +946,7 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
if (config->metacopy && config->redirect_mode != OVL_REDIRECT_ON) {
if (set.metacopy && set.redirect) {
pr_err("conflicting options: metacopy=on,redirect_dir=%s\n",
- ovl_redirect_mode(config));
+ ovl_redirect_mode_name(redirect_opt));
return -EINVAL;
}
if (set.redirect) {
@@ -926,7 +955,7 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
* in this conflict.
*/
pr_info("disabling metacopy due to redirect_dir=%s\n",
- ovl_redirect_mode(config));
+ ovl_redirect_mode_name(redirect_opt));
config->metacopy = false;
} else {
/* Automatically enable redirect otherwise. */
@@ -934,6 +963,16 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
}
}
+ /* Resolve nfs_export -> !data-only layers dependency */
+ if (config->nfs_export && ctx->nr_data) {
+ if (set.nfs_export) {
+ pr_err("NFS export is not supported with data-only layers\n");
+ return -EINVAL;
+ }
+ pr_info("disabling nfs_export due to data-only layers\n");
+ config->nfs_export = false;
+ }
+
/* Resolve nfs_export -> index dependency */
if (config->nfs_export && !config->index) {
if (!config->upperdir &&
@@ -991,9 +1030,10 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
/* Resolve userxattr -> !redirect && !metacopy dependency */
if (config->userxattr) {
if (set.redirect &&
- config->redirect_mode != OVL_REDIRECT_NOFOLLOW) {
+ config->redirect_mode != OVL_REDIRECT_NOFOLLOW &&
+ config->redirect_mode != OVL_REDIRECT_FOLLOWDATA) {
pr_err("conflicting options: userxattr,redirect_dir=%s\n",
- ovl_redirect_mode(config));
+ ovl_redirect_mode_name(redirect_opt));
return -EINVAL;
}
if (config->metacopy && set.metacopy) {
@@ -1006,10 +1046,21 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
* options must be explicitly enabled if used together with
* userxattr.
*/
- config->redirect_mode = OVL_REDIRECT_NOFOLLOW;
+ if (!set.redirect)
+ config->redirect_mode = follow_mode;
config->metacopy = false;
}
+ /* Data-only layers are reached through redirects */
+ if (ctx->nr_data && config->redirect_mode == OVL_REDIRECT_NOFOLLOW) {
+ pr_err("redirect_dir=nofollow is not supported with data-only layers\n");
+ return -EINVAL;
+ }
+ if (!ctx->nr_data && config->redirect_mode == OVL_REDIRECT_FOLLOWDATA) {
+ pr_err("redirect_dir=followdata requires data-only layers\n");
+ return -EINVAL;
+ }
+
/*
* Fail if we don't have trusted xattr capability and a feature was
* explicitly requested that requires them.
diff --git a/fs/overlayfs/params.h b/fs/overlayfs/params.h
index ffd53cdd8482..3a514a21b14c 100644
--- a/fs/overlayfs/params.h
+++ b/fs/overlayfs/params.h
@@ -42,3 +42,5 @@ int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
struct ovl_config *config);
int ovl_show_options(struct seq_file *m, struct dentry *dentry);
const char *ovl_xino_mode(struct ovl_config *config);
+const char *ovl_redirect_mode(struct ovl_config *config);
+int ovl_redirect_follow_mode(bool follow_lower, size_t nr_data);
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 8bc970748b96..e61aab1999d3 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -701,7 +701,7 @@ static int ovl_create_volatile_dirty(struct ovl_fs *ofs)
}
static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs,
- const struct path *workpath)
+ const struct path *workpath, size_t nr_data)
{
struct vfsmount *mnt = ovl_upper_mnt(ofs);
struct dentry *workdir;
@@ -765,9 +765,11 @@ static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs,
if (err) {
pr_warn("failed to set xattr on upper\n");
ofs->noxattr = true;
- if (ovl_redirect_follow(ofs)) {
- ofs->config.redirect_mode = OVL_REDIRECT_NOFOLLOW;
- pr_warn("...falling back to redirect_dir=nofollow.\n");
+ if (ovl_redirect_follow_lower(ofs)) {
+ ofs->config.redirect_mode =
+ ovl_redirect_follow_mode(false, nr_data);
+ pr_warn("...falling back to redirect_dir=%s.\n",
+ ovl_redirect_mode(&ofs->config));
}
if (ofs->config.metacopy) {
ofs->config.metacopy = false;
@@ -844,7 +846,7 @@ out:
static int ovl_get_workdir(struct super_block *sb, struct ovl_fs *ofs,
const struct path *upperpath,
- const struct path *workpath)
+ const struct path *workpath, size_t nr_data)
{
int err;
@@ -873,7 +875,7 @@ static int ovl_get_workdir(struct super_block *sb, struct ovl_fs *ofs,
if (err)
return err;
- return ovl_make_workdir(sb, ofs, workpath);
+ return ovl_make_workdir(sb, ofs, workpath, nr_data);
}
static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs,
@@ -1142,6 +1144,29 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
}
/*
+ * Lower file handles identify the lower fs, but not the layer,
+ * so with different idmaps on the same lower fs, index and
+ * nfs_export may decode a file handle through a layer with the
+ * wrong idmap. Following copy up origin without index is only
+ * used for st_ino, which does not depend on the idmap.
+ */
+ if (i < nr_merged_lower &&
+ (ofs->config.index || ofs->config.nfs_export)) {
+ const struct mnt_idmap *idmap = mnt_idmap(mnt);
+ struct ovl_sb *fs = &ofs->fs[fsid];
+
+ if (!fs->lower_idmap) {
+ fs->lower_idmap = idmap;
+ } else if (fs->lower_idmap != idmap) {
+ pr_err("lower layer '%pd2' conflicts with idmap of another lower layer on the same fs, mount with '-o index=off,nfs_export=off' to override.\n",
+ l->path.dentry);
+ iput(trap);
+ kern_unmount(mnt);
+ return -EINVAL;
+ }
+ }
+
+ /*
* Make lower layers R/O. That way fchmod/fchown on lower file
* will fail instead of modifying lower fs.
*/
@@ -1485,7 +1510,8 @@ static int ovl_fill_super_creds(struct fs_context *fc, struct super_block *sb)
}
}
- err = ovl_get_workdir(sb, ofs, &ctx->upper, &ctx->work);
+ err = ovl_get_workdir(sb, ofs, &ctx->upper, &ctx->work,
+ ctx->nr_data);
if (err)
return err;
@@ -1619,13 +1645,23 @@ out_err:
return err;
}
+static void ovl_kill_sb(struct super_block *sb)
+{
+ struct ovl_fs *ofs = OVL_FS(sb);
+
+ /* Nothing can modify them now, do not hold them across the sync */
+ if (ofs)
+ ovl_inuse_unlock_all(ofs);
+ kill_anon_super(sb);
+}
+
struct file_system_type ovl_fs_type = {
.owner = THIS_MODULE,
.name = "overlay",
.init_fs_context = ovl_init_fs_context,
.parameters = ovl_parameter_spec,
.fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP,
- .kill_sb = kill_anon_super,
+ .kill_sb = ovl_kill_sb,
};
MODULE_ALIAS_FS("overlay");
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 788c6870d67d..fd4b1fcf57ad 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -1059,6 +1059,18 @@ bool ovl_is_inuse(struct dentry *dentry)
return inuse;
}
+void ovl_inuse_unlock_all(struct ovl_fs *ofs)
+{
+ if (ofs->workdir_locked) {
+ ovl_inuse_unlock(ofs->workbasedir);
+ ofs->workdir_locked = false;
+ }
+ if (ofs->upperdir_locked) {
+ ovl_inuse_unlock(ovl_upper_mnt(ofs)->mnt_root);
+ ofs->upperdir_locked = false;
+ }
+}
+
/*
* Does this overlay dentry need to be indexed on copy up?
*/
diff --git a/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c
index 44a75839f4ed..5df42cf3e688 100644
--- a/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c
+++ b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c
@@ -498,4 +498,49 @@ TEST_F(idmapped_overlay, nfs_export_handles)
EXPECT_EQ(close(ovl), 0);
}
+/* With index=on, same-superblock lower layers must not use different idmaps. */
+TEST_F(idmapped_overlay, reject_same_sb_mixed_idmaps)
+{
+ char lower1[PATH_MAX], lower2[PATH_MAX];
+ char upper[PATH_MAX], work[PATH_MAX];
+ int fd_lower1, fd_lower2, fsfd;
+
+ if (!ovl_supported())
+ SKIP(return, "overlayfs not supported");
+
+ snprintf(lower1, sizeof(lower1), "%s/l", self->base);
+ snprintf(lower2, sizeof(lower2), "%s/l2", self->base);
+ snprintf(upper, sizeof(upper), "%s/u", self->base);
+ snprintf(work, sizeof(work), "%s/w", self->base);
+ ASSERT_EQ(mkdir(lower2, 0755), 0);
+ ASSERT_EQ(chown(lower2, ID_HOST, ID_HOST), 0);
+
+ fd_lower1 = idmapped_layer_fd(lower1, ID_HOST, ID_NS, ID_RANGE);
+ ASSERT_GE(fd_lower1, 0);
+ fd_lower2 = sys_open_tree(AT_FDCWD, lower2,
+ OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ ASSERT_GE(fd_lower2, 0);
+ fsfd = sys_fsopen("overlay", 0);
+ ASSERT_GE(fsfd, 0);
+ ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_SET_STRING,
+ "source", "test", 0), 0);
+ ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_SET_STRING,
+ "upperdir", upper, 0), 0);
+ ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_SET_STRING,
+ "workdir", work, 0), 0);
+ ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_SET_STRING,
+ "index", "on", 0), 0);
+ ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_SET_FD,
+ "lowerdir+", NULL, fd_lower1), 0);
+ ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_SET_FD,
+ "lowerdir+", NULL, fd_lower2), 0);
+
+ errno = 0;
+ EXPECT_EQ(sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0), -1);
+ EXPECT_EQ(errno, EINVAL);
+ EXPECT_EQ(close(fd_lower1), 0);
+ EXPECT_EQ(close(fd_lower2), 0);
+ EXPECT_EQ(close(fsfd), 0);
+}
+
TEST_HARNESS_MAIN