From b2dd05f107b11966e26fe52a313b418364cf497b Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Tue, 16 May 2023 22:16:17 +0800 Subject: ovl: let helper ovl_i_path_real() return the realinode Let helper ovl_i_path_real() return the realinode to prepare for checking non-null realinode in RCU walking path. [msz] Use d_inode_rcu() since we are depending on the consitency between dentry and inode being non-NULL in an RCU setting. Signed-off-by: Zhihao Cheng Signed-off-by: Amir Goldstein Fixes: ffa5723c6d25 ("ovl: store lower path in ovl_inode") Cc: # v5.19 Signed-off-by: Miklos Szeredi --- fs/overlayfs/overlayfs.h | 2 +- fs/overlayfs/util.c | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 4d0b278f5630..7398de332527 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -382,7 +382,7 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry); void ovl_path_upper(struct dentry *dentry, struct path *path); void ovl_path_lower(struct dentry *dentry, struct path *path); void ovl_path_lowerdata(struct dentry *dentry, struct path *path); -void ovl_i_path_real(struct inode *inode, struct path *path); +struct inode *ovl_i_path_real(struct inode *inode, struct path *path); enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path); enum ovl_path_type ovl_path_realdata(struct dentry *dentry, struct path *path); struct dentry *ovl_dentry_upper(struct dentry *dentry); diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 923d66d131c1..5a6f34c7ed03 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -250,7 +250,7 @@ struct dentry *ovl_i_dentry_upper(struct inode *inode) return ovl_upperdentry_dereference(OVL_I(inode)); } -void ovl_i_path_real(struct inode *inode, struct path *path) +struct inode *ovl_i_path_real(struct inode *inode, struct path *path) { path->dentry = ovl_i_dentry_upper(inode); if (!path->dentry) { @@ -259,6 +259,8 @@ void ovl_i_path_real(struct inode *inode, struct path *path) } else { path->mnt = ovl_upper_mnt(OVL_FS(inode->i_sb)); } + + return path->dentry ? d_inode_rcu(path->dentry) : NULL; } struct inode *ovl_inode_upper(struct inode *inode) @@ -1105,8 +1107,7 @@ void ovl_copyattr(struct inode *inode) vfsuid_t vfsuid; vfsgid_t vfsgid; - ovl_i_path_real(inode, &realpath); - realinode = d_inode(realpath.dentry); + realinode = ovl_i_path_real(inode, &realpath); real_idmap = mnt_idmap(realpath.mnt); vfsuid = i_uid_into_vfsuid(real_idmap, realinode); -- cgit v1.2.3-58-ga151 From 1a73f5b8f079fd42a544c1600beface50c63af7c Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Tue, 16 May 2023 22:16:18 +0800 Subject: ovl: fix null pointer dereference in ovl_permission() Following process: P1 P2 path_lookupat link_path_walk inode_permission ovl_permission ovl_i_path_real(inode, &realpath) path->dentry = ovl_i_dentry_upper(inode) drop_cache __dentry_kill(ovl_dentry) iput(ovl_inode) ovl_destroy_inode(ovl_inode) dput(oi->__upperdentry) dentry_kill(upperdentry) dentry_unlink_inode upperdentry->d_inode = NULL realinode = d_inode(realpath.dentry) // return NULL inode_permission(realinode) inode->i_sb // NULL pointer dereference , will trigger an null pointer dereference at realinode: [ 335.664979] BUG: kernel NULL pointer dereference, address: 0000000000000002 [ 335.668032] CPU: 0 PID: 2592 Comm: ls Not tainted 6.3.0 [ 335.669956] RIP: 0010:inode_permission+0x33/0x2c0 [ 335.678939] Call Trace: [ 335.679165] [ 335.679371] ovl_permission+0xde/0x320 [ 335.679723] inode_permission+0x15e/0x2c0 [ 335.680090] link_path_walk+0x115/0x550 [ 335.680771] path_lookupat.isra.0+0xb2/0x200 [ 335.681170] filename_lookup+0xda/0x240 [ 335.681922] vfs_statx+0xa6/0x1f0 [ 335.682233] vfs_fstatat+0x7b/0xb0 Fetch a reproducer in [Link]. Use the helper ovl_i_path_realinode() to get realinode and then do non-nullptr checking. Link: https://bugzilla.kernel.org/show_bug.cgi?id=217405 Fixes: 4b7791b2e958 ("ovl: handle idmappings in ovl_permission()") Cc: # v5.19 Signed-off-by: Zhihao Cheng Suggested-by: Christian Brauner Suggested-by: Amir Goldstein Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 541cf3717fc2..ca56b1328a2c 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -288,8 +288,8 @@ int ovl_permission(struct mnt_idmap *idmap, int err; /* Careful in RCU walk mode */ - ovl_i_path_real(inode, &realpath); - if (!realpath.dentry) { + realinode = ovl_i_path_real(inode, &realpath); + if (!realinode) { WARN_ON(!(mask & MAY_NOT_BLOCK)); return -ECHILD; } @@ -302,7 +302,6 @@ int ovl_permission(struct mnt_idmap *idmap, if (err) return err; - realinode = d_inode(realpath.dentry); old_cred = ovl_override_creds(inode->i_sb); if (!upperinode && !special_file(realinode->i_mode) && mask & MAY_WRITE) { -- cgit v1.2.3-58-ga151 From f4e19e595cc2e76a8a58413eb19d3d9c51328b53 Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Tue, 16 May 2023 22:16:19 +0800 Subject: ovl: fix null pointer dereference in ovl_get_acl_rcu() Following process: P1 P2 path_openat link_path_walk may_lookup inode_permission(rcu) ovl_permission acl_permission_check check_acl get_cached_acl_rcu ovl_get_inode_acl realinode = ovl_inode_real(ovl_inode) drop_cache __dentry_kill(ovl_dentry) iput(ovl_inode) ovl_destroy_inode(ovl_inode) dput(oi->__upperdentry) dentry_kill(upperdentry) dentry_unlink_inode upperdentry->d_inode = NULL ovl_inode_upper upperdentry = ovl_i_dentry_upper(ovl_inode) d_inode(upperdentry) // returns NULL IS_POSIXACL(realinode) // NULL pointer dereference , will trigger an null pointer dereference at realinode: [ 205.472797] BUG: kernel NULL pointer dereference, address: 0000000000000028 [ 205.476701] CPU: 2 PID: 2713 Comm: ls Not tainted 6.3.0-12064-g2edfa098e750-dirty #1216 [ 205.478754] RIP: 0010:do_ovl_get_acl+0x5d/0x300 [ 205.489584] Call Trace: [ 205.489812] [ 205.490014] ovl_get_inode_acl+0x26/0x30 [ 205.490466] get_cached_acl_rcu+0x61/0xa0 [ 205.490908] generic_permission+0x1bf/0x4e0 [ 205.491447] ovl_permission+0x79/0x1b0 [ 205.491917] inode_permission+0x15e/0x2c0 [ 205.492425] link_path_walk+0x115/0x550 [ 205.493311] path_lookupat.isra.0+0xb2/0x200 [ 205.493803] filename_lookup+0xda/0x240 [ 205.495747] vfs_fstatat+0x7b/0xb0 Fetch a reproducer in [Link]. Use the helper ovl_i_path_realinode() to get realinode and then do non-nullptr checking. Link: https://bugzilla.kernel.org/show_bug.cgi?id=217404 Fixes: 332f606b32b6 ("ovl: enable RCU'd ->get_acl()") Cc: # v5.15 Signed-off-by: Zhihao Cheng Suggested-by: Christian Brauner Suggested-by: Amir Goldstein Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index ca56b1328a2c..e7e888dea634 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -558,20 +558,20 @@ struct posix_acl *do_ovl_get_acl(struct mnt_idmap *idmap, struct inode *inode, int type, bool rcu, bool noperm) { - struct inode *realinode = ovl_inode_real(inode); + struct inode *realinode; struct posix_acl *acl; struct path realpath; - if (!IS_POSIXACL(realinode)) - return NULL; - /* Careful in RCU walk mode */ - ovl_i_path_real(inode, &realpath); - if (!realpath.dentry) { + realinode = ovl_i_path_real(inode, &realpath); + if (!realinode) { WARN_ON(!rcu); return ERR_PTR(-ECHILD); } + if (!IS_POSIXACL(realinode)) + return NULL; + if (rcu) { /* * If the layer is idmapped drop out of RCU path walk -- cgit v1.2.3-58-ga151 From b07d5cc93e1b28df47a72c519d09d0a836043613 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Mon, 3 Apr 2023 11:29:59 +0300 Subject: ovl: update of dentry revalidate flags after copy up After copy up, we may need to update d_flags if upper dentry is on a remote fs and lower dentries are not. Add helpers to allow incremental update of the revalidate flags. Fixes: bccece1ead36 ("ovl: allow remote upper") Reviewed-by: Gao Xiang Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 2 ++ fs/overlayfs/dir.c | 3 +-- fs/overlayfs/export.c | 3 +-- fs/overlayfs/namei.c | 3 +-- fs/overlayfs/overlayfs.h | 6 ++++-- fs/overlayfs/super.c | 2 +- fs/overlayfs/util.c | 24 ++++++++++++++++++++---- 7 files changed, 30 insertions(+), 13 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index f658cc8ea492..95dce240ba17 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -575,6 +575,7 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c) /* Restore timestamps on parent (best effort) */ ovl_set_timestamps(ofs, upperdir, &c->pstat); ovl_dentry_set_upper_alias(c->dentry); + ovl_dentry_update_reval(c->dentry, upper); } } inode_unlock(udir); @@ -894,6 +895,7 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c) inode_unlock(udir); ovl_dentry_set_upper_alias(c->dentry); + ovl_dentry_update_reval(c->dentry, ovl_dentry_upper(c->dentry)); } out: diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index fc25fb95d5fc..9be52d8013c8 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -269,8 +269,7 @@ static int ovl_instantiate(struct dentry *dentry, struct inode *inode, ovl_dir_modified(dentry->d_parent, false); ovl_dentry_set_upper_alias(dentry); - ovl_dentry_update_reval(dentry, newdentry, - DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE); + ovl_dentry_init_reval(dentry, newdentry); if (!hardlink) { /* diff --git a/fs/overlayfs/export.c b/fs/overlayfs/export.c index defd4e231ad2..5c36fb3a7bab 100644 --- a/fs/overlayfs/export.c +++ b/fs/overlayfs/export.c @@ -326,8 +326,7 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb, if (upper_alias) ovl_dentry_set_upper_alias(dentry); - ovl_dentry_update_reval(dentry, upper, - DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE); + ovl_dentry_init_reval(dentry, upper); return d_instantiate_anon(dentry, inode); diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index cfb3420b7df0..100a492d2b2a 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -1122,8 +1122,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, ovl_set_flag(OVL_UPPERDATA, inode); } - ovl_dentry_update_reval(dentry, upperdentry, - DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE); + ovl_dentry_init_reval(dentry, upperdentry); revert_creds(old_cred); if (origin_path) { diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 7398de332527..2b79a9398c13 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -375,8 +375,10 @@ bool ovl_index_all(struct super_block *sb); bool ovl_verify_lower(struct super_block *sb); struct ovl_entry *ovl_alloc_entry(unsigned int numlower); bool ovl_dentry_remote(struct dentry *dentry); -void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *upperdentry, - unsigned int mask); +void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *realdentry); +void ovl_dentry_init_reval(struct dentry *dentry, struct dentry *upperdentry); +void ovl_dentry_init_flags(struct dentry *dentry, struct dentry *upperdentry, + unsigned int mask); bool ovl_dentry_weird(struct dentry *dentry); enum ovl_path_type ovl_path_type(struct dentry *dentry); void ovl_path_upper(struct dentry *dentry, struct path *path); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index f97ad8b40dbb..ae1058fbfb5b 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1877,7 +1877,7 @@ static struct dentry *ovl_get_root(struct super_block *sb, ovl_dentry_set_flag(OVL_E_CONNECTED, root); ovl_set_upperdata(d_inode(root)); ovl_inode_init(d_inode(root), &oip, ino, fsid); - ovl_dentry_update_reval(root, upperdentry, DCACHE_OP_WEAK_REVALIDATE); + ovl_dentry_init_flags(root, upperdentry, DCACHE_OP_WEAK_REVALIDATE); return root; } diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 5a6f34c7ed03..fb12e7fa8548 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -94,14 +94,30 @@ struct ovl_entry *ovl_alloc_entry(unsigned int numlower) return oe; } +#define OVL_D_REVALIDATE (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE) + bool ovl_dentry_remote(struct dentry *dentry) { - return dentry->d_flags & - (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE); + return dentry->d_flags & OVL_D_REVALIDATE; +} + +void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *realdentry) +{ + if (!ovl_dentry_remote(realdentry)) + return; + + spin_lock(&dentry->d_lock); + dentry->d_flags |= realdentry->d_flags & OVL_D_REVALIDATE; + spin_unlock(&dentry->d_lock); +} + +void ovl_dentry_init_reval(struct dentry *dentry, struct dentry *upperdentry) +{ + return ovl_dentry_init_flags(dentry, upperdentry, OVL_D_REVALIDATE); } -void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *upperdentry, - unsigned int mask) +void ovl_dentry_init_flags(struct dentry *dentry, struct dentry *upperdentry, + unsigned int mask) { struct ovl_entry *oe = OVL_E(dentry); unsigned int i, flags = 0; -- cgit v1.2.3-58-ga151 From a6ff2bc0be179eea72832b5396481a08ccd22d5a Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 15 Mar 2023 04:31:37 +0200 Subject: ovl: use OVL_E() and OVL_E_FLAGS() accessors Instead of open coded instances, because we are about to split the two apart. Reviewed-by: Alexander Larsson Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/export.c | 2 +- fs/overlayfs/namei.c | 8 ++++---- fs/overlayfs/ovl_entry.h | 5 +++++ fs/overlayfs/super.c | 2 +- fs/overlayfs/util.c | 20 ++++++++++---------- 5 files changed, 21 insertions(+), 16 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/export.c b/fs/overlayfs/export.c index 5c36fb3a7bab..2cfdfcca5659 100644 --- a/fs/overlayfs/export.c +++ b/fs/overlayfs/export.c @@ -341,7 +341,7 @@ out_iput: /* Get the upper or lower dentry in stack whose on layer @idx */ static struct dentry *ovl_dentry_real_at(struct dentry *dentry, int idx) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); int i; if (!idx) diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 100a492d2b2a..e66352f19755 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -790,7 +790,7 @@ fail: */ int ovl_path_next(int idx, struct dentry *dentry, struct path *path) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); BUG_ON(idx < 0); if (idx == 0) { @@ -833,8 +833,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, struct ovl_entry *oe; const struct cred *old_cred; struct ovl_fs *ofs = dentry->d_sb->s_fs_info; - struct ovl_entry *poe = dentry->d_parent->d_fsdata; - struct ovl_entry *roe = dentry->d_sb->s_root->d_fsdata; + struct ovl_entry *poe = OVL_E(dentry->d_parent); + struct ovl_entry *roe = OVL_E(dentry->d_sb->s_root); struct ovl_path *stack = NULL, *origin_path = NULL; struct dentry *upperdir, *upperdentry = NULL; struct dentry *origin = NULL; @@ -1157,7 +1157,7 @@ out: bool ovl_lower_positive(struct dentry *dentry) { - struct ovl_entry *poe = dentry->d_parent->d_fsdata; + struct ovl_entry *poe = OVL_E(dentry->d_parent); const struct qstr *name = &dentry->d_name; const struct cred *old_cred; unsigned int i; diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index fd11fe6d6d45..4c7312126b3b 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -124,6 +124,11 @@ static inline struct ovl_entry *OVL_E(struct dentry *dentry) return (struct ovl_entry *) dentry->d_fsdata; } +static inline unsigned long *OVL_E_FLAGS(struct dentry *dentry) +{ + return &OVL_E(dentry)->flags; +} + struct ovl_inode { union { struct ovl_dir_cache *cache; /* directory */ diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index ae1058fbfb5b..ea34edd9d624 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -138,7 +138,7 @@ static int ovl_revalidate_real(struct dentry *d, unsigned int flags, bool weak) static int ovl_dentry_revalidate_common(struct dentry *dentry, unsigned int flags, bool weak) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); struct inode *inode = d_inode_rcu(dentry); struct dentry *upper; unsigned int i; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index fb12e7fa8548..73a5316d7ed5 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -143,7 +143,7 @@ bool ovl_dentry_weird(struct dentry *dentry) enum ovl_path_type ovl_path_type(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); enum ovl_path_type type = 0; if (ovl_dentry_upper(dentry)) { @@ -176,7 +176,7 @@ void ovl_path_upper(struct dentry *dentry, struct path *path) void ovl_path_lower(struct dentry *dentry, struct path *path) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); if (oe->numlower) { path->mnt = oe->lowerstack[0].layer->mnt; @@ -188,7 +188,7 @@ void ovl_path_lower(struct dentry *dentry, struct path *path) void ovl_path_lowerdata(struct dentry *dentry, struct path *path) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); if (oe->numlower) { path->mnt = oe->lowerstack[oe->numlower - 1].layer->mnt; @@ -231,14 +231,14 @@ struct dentry *ovl_dentry_upper(struct dentry *dentry) struct dentry *ovl_dentry_lower(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); return oe->numlower ? oe->lowerstack[0].dentry : NULL; } const struct ovl_layer *ovl_layer_lower(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); return oe->numlower ? oe->lowerstack[0].layer : NULL; } @@ -251,7 +251,7 @@ const struct ovl_layer *ovl_layer_lower(struct dentry *dentry) */ struct dentry *ovl_dentry_lowerdata(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); return oe->numlower ? oe->lowerstack[oe->numlower - 1].dentry : NULL; } @@ -331,17 +331,17 @@ void ovl_set_dir_cache(struct inode *inode, struct ovl_dir_cache *cache) void ovl_dentry_set_flag(unsigned long flag, struct dentry *dentry) { - set_bit(flag, &OVL_E(dentry)->flags); + set_bit(flag, OVL_E_FLAGS(dentry)); } void ovl_dentry_clear_flag(unsigned long flag, struct dentry *dentry) { - clear_bit(flag, &OVL_E(dentry)->flags); + clear_bit(flag, OVL_E_FLAGS(dentry)); } bool ovl_dentry_test_flag(unsigned long flag, struct dentry *dentry) { - return test_bit(flag, &OVL_E(dentry)->flags); + return test_bit(flag, OVL_E_FLAGS(dentry)); } bool ovl_dentry_is_opaque(struct dentry *dentry) @@ -1017,7 +1017,7 @@ out: bool ovl_is_metacopy_dentry(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_entry *oe = OVL_E(dentry); if (!d_is_reg(dentry)) return false; -- cgit v1.2.3-58-ga151 From 5522c9c7cbd2ab4e886fa1c70896f0bdd483ce0b Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Mon, 3 Apr 2023 11:51:47 +0300 Subject: ovl: use ovl_numlower() and ovl_lowerstack() accessors This helps fortify against dereferencing a NULL ovl_entry, before we move the ovl_entry reference into ovl_inode. Reviewed-by: Alexander Larsson Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/export.c | 18 ++++++++++-------- fs/overlayfs/namei.c | 34 ++++++++++++++++++---------------- fs/overlayfs/ovl_entry.h | 14 ++++++++++++-- fs/overlayfs/super.c | 23 +++++++++++++---------- fs/overlayfs/util.c | 36 ++++++++++++++++++++---------------- 5 files changed, 73 insertions(+), 52 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/export.c b/fs/overlayfs/export.c index 2cfdfcca5659..ddb546627749 100644 --- a/fs/overlayfs/export.c +++ b/fs/overlayfs/export.c @@ -80,7 +80,7 @@ static int ovl_connectable_layer(struct dentry *dentry) /* We can get overlay root from root of any layer */ if (dentry == dentry->d_sb->s_root) - return oe->numlower; + return ovl_numlower(oe); /* * If it's an unindexed merge dir, then it's not connectable with any @@ -91,7 +91,7 @@ static int ovl_connectable_layer(struct dentry *dentry) return 0; /* We can get upper/overlay path from indexed/lower dentry */ - return oe->lowerstack[0].layer->idx; + return ovl_lowerstack(oe)->layer->idx; } /* @@ -105,6 +105,7 @@ static int ovl_connectable_layer(struct dentry *dentry) static int ovl_connect_layer(struct dentry *dentry) { struct dentry *next, *parent = NULL; + struct ovl_entry *oe = OVL_E(dentry); int origin_layer; int err = 0; @@ -112,7 +113,7 @@ static int ovl_connect_layer(struct dentry *dentry) WARN_ON(!ovl_dentry_lower(dentry))) return -EIO; - origin_layer = OVL_E(dentry)->lowerstack[0].layer->idx; + origin_layer = ovl_lowerstack(oe)->layer->idx; if (ovl_dentry_test_flag(OVL_E_CONNECTED, dentry)) return origin_layer; @@ -319,8 +320,8 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb, goto nomem; if (lower) { - oe->lowerstack->dentry = dget(lower); - oe->lowerstack->layer = lowerpath->layer; + ovl_lowerstack(oe)->dentry = dget(lower); + ovl_lowerstack(oe)->layer = lowerpath->layer; } dentry->d_fsdata = oe; if (upper_alias) @@ -342,14 +343,15 @@ out_iput: static struct dentry *ovl_dentry_real_at(struct dentry *dentry, int idx) { struct ovl_entry *oe = OVL_E(dentry); + struct ovl_path *lowerstack = ovl_lowerstack(oe); int i; if (!idx) return ovl_dentry_upper(dentry); - for (i = 0; i < oe->numlower; i++) { - if (oe->lowerstack[i].layer->idx == idx) - return oe->lowerstack[i].dentry; + for (i = 0; i < ovl_numlower(oe); i++) { + if (lowerstack[i].layer->idx == idx) + return lowerstack[i].dentry; } return NULL; diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index e66352f19755..31f889d27083 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -791,19 +791,20 @@ fail: int ovl_path_next(int idx, struct dentry *dentry, struct path *path) { struct ovl_entry *oe = OVL_E(dentry); + struct ovl_path *lowerstack = ovl_lowerstack(oe); BUG_ON(idx < 0); if (idx == 0) { ovl_path_upper(dentry, path); if (path->dentry) - return oe->numlower ? 1 : -1; + return ovl_numlower(oe) ? 1 : -1; idx++; } - BUG_ON(idx > oe->numlower); - path->dentry = oe->lowerstack[idx - 1].dentry; - path->mnt = oe->lowerstack[idx - 1].layer->mnt; + BUG_ON(idx > ovl_numlower(oe)); + path->dentry = lowerstack[idx - 1].dentry; + path->mnt = lowerstack[idx - 1].layer->mnt; - return (idx < oe->numlower) ? idx + 1 : -1; + return (idx < ovl_numlower(oe)) ? idx + 1 : -1; } /* Fix missing 'origin' xattr */ @@ -853,7 +854,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, .is_dir = false, .opaque = false, .stop = false, - .last = ofs->config.redirect_follow ? false : !poe->numlower, + .last = ofs->config.redirect_follow ? false : !ovl_numlower(poe), .redirect = NULL, .metacopy = false, }; @@ -904,7 +905,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, upperopaque = d.opaque; } - if (!d.stop && poe->numlower) { + if (!d.stop && ovl_numlower(poe)) { err = -ENOMEM; stack = kcalloc(ofs->numlayer - 1, sizeof(struct ovl_path), GFP_KERNEL); @@ -912,13 +913,13 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, goto out_put_upper; } - for (i = 0; !d.stop && i < poe->numlower; i++) { - struct ovl_path lower = poe->lowerstack[i]; + for (i = 0; !d.stop && i < ovl_numlower(poe); i++) { + struct ovl_path lower = ovl_lowerstack(poe)[i]; if (!ofs->config.redirect_follow) - d.last = i == poe->numlower - 1; + d.last = i == ovl_numlower(poe) - 1; else - d.last = lower.layer->idx == roe->numlower; + d.last = lower.layer->idx == ovl_numlower(roe); d.mnt = lower.layer->mnt; err = ovl_lookup_layer(lower.dentry, &d, &this, false); @@ -1072,7 +1073,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (!oe) goto out_put; - memcpy(oe->lowerstack, stack, sizeof(struct ovl_path) * ctr); + memcpy(ovl_lowerstack(oe), stack, sizeof(struct ovl_path) * ctr); dentry->d_fsdata = oe; if (upperopaque) @@ -1177,12 +1178,13 @@ bool ovl_lower_positive(struct dentry *dentry) old_cred = ovl_override_creds(dentry->d_sb); /* Positive upper -> have to look up lower to see whether it exists */ - for (i = 0; !done && !positive && i < poe->numlower; i++) { + for (i = 0; !done && !positive && i < ovl_numlower(poe); i++) { struct dentry *this; - struct dentry *lowerdir = poe->lowerstack[i].dentry; + struct ovl_path *parentpath = &ovl_lowerstack(poe)[i]; - this = lookup_one_positive_unlocked(mnt_idmap(poe->lowerstack[i].layer->mnt), - name->name, lowerdir, name->len); + this = lookup_one_positive_unlocked( + mnt_idmap(parentpath->layer->mnt), + name->name, parentpath->dentry, name->len); if (IS_ERR(this)) { switch (PTR_ERR(this)) { case -ENOENT: diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 4c7312126b3b..0e009fa39d54 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -113,10 +113,20 @@ struct ovl_entry { }; struct rcu_head rcu; }; - unsigned numlower; - struct ovl_path lowerstack[]; + unsigned int __numlower; + struct ovl_path __lowerstack[]; }; +static inline unsigned int ovl_numlower(struct ovl_entry *oe) +{ + return oe ? oe->__numlower : 0; +} + +static inline struct ovl_path *ovl_lowerstack(struct ovl_entry *oe) +{ + return ovl_numlower(oe) ? oe->__lowerstack : NULL; +} + struct ovl_entry *ovl_alloc_entry(unsigned int numlower); static inline struct ovl_entry *OVL_E(struct dentry *dentry) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index ea34edd9d624..baa970a2b398 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -56,10 +56,11 @@ MODULE_PARM_DESC(xino_auto, static void ovl_entry_stack_free(struct ovl_entry *oe) { + struct ovl_path *lowerstack = ovl_lowerstack(oe); unsigned int i; - for (i = 0; i < oe->numlower; i++) - dput(oe->lowerstack[i].dentry); + for (i = 0; i < ovl_numlower(oe); i++) + dput(lowerstack[i].dentry); } static bool ovl_metacopy_def = IS_ENABLED(CONFIG_OVERLAY_FS_METACOPY); @@ -139,6 +140,7 @@ static int ovl_dentry_revalidate_common(struct dentry *dentry, unsigned int flags, bool weak) { struct ovl_entry *oe = OVL_E(dentry); + struct ovl_path *lowerstack = ovl_lowerstack(oe); struct inode *inode = d_inode_rcu(dentry); struct dentry *upper; unsigned int i; @@ -152,10 +154,9 @@ static int ovl_dentry_revalidate_common(struct dentry *dentry, if (upper) ret = ovl_revalidate_real(upper, flags, weak); - for (i = 0; ret > 0 && i < oe->numlower; i++) { - ret = ovl_revalidate_real(oe->lowerstack[i].dentry, flags, - weak); - } + for (i = 0; ret > 0 && i < ovl_numlower(oe); i++) + ret = ovl_revalidate_real(lowerstack[i].dentry, flags, weak); + return ret; } @@ -1454,7 +1455,7 @@ static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs, /* Verify lower root is upper root origin */ err = ovl_verify_origin(ofs, upperpath->dentry, - oe->lowerstack[0].dentry, true); + ovl_lowerstack(oe)->dentry, true); if (err) { pr_err("failed to verify upper root origin\n"); goto out; @@ -1717,6 +1718,7 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb, { int err; struct path *stack = NULL; + struct ovl_path *lowerstack; unsigned int i; struct ovl_entry *oe; @@ -1754,9 +1756,10 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb, if (!oe) goto out_err; + lowerstack = ovl_lowerstack(oe); for (i = 0; i < numlower; i++) { - oe->lowerstack[i].dentry = dget(stack[i].dentry); - oe->lowerstack[i].layer = &ofs->layers[i+1]; + lowerstack[i].dentry = dget(stack[i].dentry); + lowerstack[i].layer = &ofs->layers[i+1]; } out: @@ -1849,7 +1852,7 @@ static struct dentry *ovl_get_root(struct super_block *sb, struct ovl_entry *oe) { struct dentry *root; - struct ovl_path *lowerpath = &oe->lowerstack[0]; + struct ovl_path *lowerpath = ovl_lowerstack(oe); unsigned long ino = d_inode(lowerpath->dentry)->i_ino; int fsid = lowerpath->layer->fsid; struct ovl_inode_params oip = { diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 73a5316d7ed5..a146c7ab8d8e 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -85,11 +85,11 @@ bool ovl_verify_lower(struct super_block *sb) struct ovl_entry *ovl_alloc_entry(unsigned int numlower) { - size_t size = offsetof(struct ovl_entry, lowerstack[numlower]); + size_t size = offsetof(struct ovl_entry, __lowerstack[numlower]); struct ovl_entry *oe = kzalloc(size, GFP_KERNEL); if (oe) - oe->numlower = numlower; + oe->__numlower = numlower; return oe; } @@ -120,12 +120,13 @@ void ovl_dentry_init_flags(struct dentry *dentry, struct dentry *upperdentry, unsigned int mask) { struct ovl_entry *oe = OVL_E(dentry); + struct ovl_path *lowerstack = ovl_lowerstack(oe); unsigned int i, flags = 0; if (upperdentry) flags |= upperdentry->d_flags; - for (i = 0; i < oe->numlower; i++) - flags |= oe->lowerstack[i].dentry->d_flags; + for (i = 0; i < ovl_numlower(oe); i++) + flags |= lowerstack[i].dentry->d_flags; spin_lock(&dentry->d_lock); dentry->d_flags &= ~mask; @@ -152,7 +153,7 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry) /* * Non-dir dentry can hold lower dentry of its copy up origin. */ - if (oe->numlower) { + if (ovl_numlower(oe)) { if (ovl_test_flag(OVL_CONST_INO, d_inode(dentry))) type |= __OVL_PATH_ORIGIN; if (d_is_dir(dentry) || @@ -160,7 +161,7 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry) type |= __OVL_PATH_MERGE; } } else { - if (oe->numlower > 1) + if (ovl_numlower(oe) > 1) type |= __OVL_PATH_MERGE; } return type; @@ -177,10 +178,11 @@ void ovl_path_upper(struct dentry *dentry, struct path *path) void ovl_path_lower(struct dentry *dentry, struct path *path) { struct ovl_entry *oe = OVL_E(dentry); + struct ovl_path *lowerpath = ovl_lowerstack(oe); - if (oe->numlower) { - path->mnt = oe->lowerstack[0].layer->mnt; - path->dentry = oe->lowerstack[0].dentry; + if (ovl_numlower(oe)) { + path->mnt = lowerpath->layer->mnt; + path->dentry = lowerpath->dentry; } else { *path = (struct path) { }; } @@ -189,10 +191,11 @@ void ovl_path_lower(struct dentry *dentry, struct path *path) void ovl_path_lowerdata(struct dentry *dentry, struct path *path) { struct ovl_entry *oe = OVL_E(dentry); + struct ovl_path *lowerstack = ovl_lowerstack(oe); - if (oe->numlower) { - path->mnt = oe->lowerstack[oe->numlower - 1].layer->mnt; - path->dentry = oe->lowerstack[oe->numlower - 1].dentry; + if (ovl_numlower(oe)) { + path->mnt = lowerstack[ovl_numlower(oe) - 1].layer->mnt; + path->dentry = lowerstack[ovl_numlower(oe) - 1].dentry; } else { *path = (struct path) { }; } @@ -233,14 +236,14 @@ struct dentry *ovl_dentry_lower(struct dentry *dentry) { struct ovl_entry *oe = OVL_E(dentry); - return oe->numlower ? oe->lowerstack[0].dentry : NULL; + return ovl_numlower(oe) ? ovl_lowerstack(oe)->dentry : NULL; } const struct ovl_layer *ovl_layer_lower(struct dentry *dentry) { struct ovl_entry *oe = OVL_E(dentry); - return oe->numlower ? oe->lowerstack[0].layer : NULL; + return ovl_numlower(oe) ? ovl_lowerstack(oe)->layer : NULL; } /* @@ -253,7 +256,8 @@ struct dentry *ovl_dentry_lowerdata(struct dentry *dentry) { struct ovl_entry *oe = OVL_E(dentry); - return oe->numlower ? oe->lowerstack[oe->numlower - 1].dentry : NULL; + return ovl_numlower(oe) ? + ovl_lowerstack(oe)[ovl_numlower(oe) - 1].dentry : NULL; } struct dentry *ovl_dentry_real(struct dentry *dentry) @@ -1028,7 +1032,7 @@ bool ovl_is_metacopy_dentry(struct dentry *dentry) return false; } - return (oe->numlower > 1); + return (ovl_numlower(oe) > 1); } char *ovl_get_redirect_xattr(struct ovl_fs *ofs, const struct path *path, int padding) -- cgit v1.2.3-58-ga151 From 163db0da3515d1d32343906a2fb7854492191d19 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Mon, 3 Apr 2023 20:36:16 +0300 Subject: ovl: factor out ovl_free_entry() and ovl_stack_*() helpers In preparation for moving lowerstack into ovl_inode. Note that in ovl_lookup() the temp stack dentry refs are now cloned into the final ovl_lowerstack instead of being transferred, so cleanup always needs to call ovl_stack_free(stack). Reviewed-by: Alexander Larsson Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/namei.c | 13 +++++-------- fs/overlayfs/overlayfs.h | 5 +++++ fs/overlayfs/ovl_entry.h | 2 -- fs/overlayfs/super.c | 14 ++------------ fs/overlayfs/util.c | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 22 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 31f889d27083..c237c8dbff09 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -907,8 +907,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (!d.stop && ovl_numlower(poe)) { err = -ENOMEM; - stack = kcalloc(ofs->numlayer - 1, sizeof(struct ovl_path), - GFP_KERNEL); + stack = ovl_stack_alloc(ofs->numlayer - 1); if (!stack) goto out_put_upper; } @@ -1073,7 +1072,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (!oe) goto out_put; - memcpy(ovl_lowerstack(oe), stack, sizeof(struct ovl_path) * ctr); + ovl_stack_cpy(ovl_lowerstack(oe), stack, ctr); dentry->d_fsdata = oe; if (upperopaque) @@ -1131,18 +1130,16 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, kfree(origin_path); } dput(index); - kfree(stack); + ovl_stack_free(stack, ctr); kfree(d.redirect); return d_splice_alias(inode, dentry); out_free_oe: dentry->d_fsdata = NULL; - kfree(oe); + ovl_free_entry(oe); out_put: dput(index); - for (i = 0; i < ctr; i++) - dput(stack[i].dentry); - kfree(stack); + ovl_stack_free(stack, ctr); out_put_upper: if (origin_path) { dput(origin_path->dentry); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 2b79a9398c13..4f83509a0294 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -373,7 +373,12 @@ int ovl_can_decode_fh(struct super_block *sb); struct dentry *ovl_indexdir(struct super_block *sb); bool ovl_index_all(struct super_block *sb); bool ovl_verify_lower(struct super_block *sb); +struct ovl_path *ovl_stack_alloc(unsigned int n); +void ovl_stack_cpy(struct ovl_path *dst, struct ovl_path *src, unsigned int n); +void ovl_stack_put(struct ovl_path *stack, unsigned int n); +void ovl_stack_free(struct ovl_path *stack, unsigned int n); struct ovl_entry *ovl_alloc_entry(unsigned int numlower); +void ovl_free_entry(struct ovl_entry *oe); bool ovl_dentry_remote(struct dentry *dentry); void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *realdentry); void ovl_dentry_init_reval(struct dentry *dentry, struct dentry *upperdentry); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 0e009fa39d54..608742f60037 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -127,8 +127,6 @@ static inline struct ovl_path *ovl_lowerstack(struct ovl_entry *oe) return ovl_numlower(oe) ? oe->__lowerstack : NULL; } -struct ovl_entry *ovl_alloc_entry(unsigned int numlower); - static inline struct ovl_entry *OVL_E(struct dentry *dentry) { return (struct ovl_entry *) dentry->d_fsdata; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index baa970a2b398..35ecea4b60b0 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -54,15 +54,6 @@ module_param_named(xino_auto, ovl_xino_auto_def, bool, 0644); MODULE_PARM_DESC(xino_auto, "Auto enable xino feature"); -static void ovl_entry_stack_free(struct ovl_entry *oe) -{ - struct ovl_path *lowerstack = ovl_lowerstack(oe); - unsigned int i; - - for (i = 0; i < ovl_numlower(oe); i++) - dput(lowerstack[i].dentry); -} - static bool ovl_metacopy_def = IS_ENABLED(CONFIG_OVERLAY_FS_METACOPY); module_param_named(metacopy, ovl_metacopy_def, bool, 0644); MODULE_PARM_DESC(metacopy, @@ -73,7 +64,7 @@ static void ovl_dentry_release(struct dentry *dentry) struct ovl_entry *oe = dentry->d_fsdata; if (oe) { - ovl_entry_stack_free(oe); + ovl_stack_put(ovl_lowerstack(oe), ovl_numlower(oe)); kfree_rcu(oe, rcu); } } @@ -2070,8 +2061,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) return 0; out_free_oe: - ovl_entry_stack_free(oe); - kfree(oe); + ovl_free_entry(oe); out_err: kfree(splitlower); path_put(&upperpath); diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index a146c7ab8d8e..4ff58f92100d 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -83,6 +83,34 @@ bool ovl_verify_lower(struct super_block *sb) return ofs->config.nfs_export && ofs->config.index; } +struct ovl_path *ovl_stack_alloc(unsigned int n) +{ + return kcalloc(n, sizeof(struct ovl_path), GFP_KERNEL); +} + +void ovl_stack_cpy(struct ovl_path *dst, struct ovl_path *src, unsigned int n) +{ + unsigned int i; + + memcpy(dst, src, sizeof(struct ovl_path) * n); + for (i = 0; i < n; i++) + dget(src[i].dentry); +} + +void ovl_stack_put(struct ovl_path *stack, unsigned int n) +{ + unsigned int i; + + for (i = 0; stack && i < n; i++) + dput(stack[i].dentry); +} + +void ovl_stack_free(struct ovl_path *stack, unsigned int n) +{ + ovl_stack_put(stack, n); + kfree(stack); +} + struct ovl_entry *ovl_alloc_entry(unsigned int numlower) { size_t size = offsetof(struct ovl_entry, __lowerstack[numlower]); @@ -94,6 +122,12 @@ struct ovl_entry *ovl_alloc_entry(unsigned int numlower) return oe; } +void ovl_free_entry(struct ovl_entry *oe) +{ + ovl_stack_put(ovl_lowerstack(oe), ovl_numlower(oe)); + kfree(oe); +} + #define OVL_D_REVALIDATE (DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE) bool ovl_dentry_remote(struct dentry *dentry) -- cgit v1.2.3-58-ga151 From 0af950f57fefabab628f1963af881e6b9bfe7f38 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sat, 8 Apr 2023 12:31:13 +0300 Subject: ovl: move ovl_entry into ovl_inode The lower stacks of all the ovl inode aliases should be identical and there is redundant information in ovl_entry and ovl_inode. Move lowerstack into ovl_inode and keep only the OVL_E_FLAGS per overlay dentry. Following patches will deduplicate redundant ovl_inode fields. Note that for pure upper and negative dentries, OVL_E(dentry) may be NULL now, so it is imporatnt to use the ovl_numlower() accessor. Reviewed-by: Alexander Larsson Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 2 +- fs/overlayfs/export.c | 22 ++++++++++++---------- fs/overlayfs/inode.c | 8 ++++---- fs/overlayfs/namei.c | 19 ++++++++++--------- fs/overlayfs/overlayfs.h | 6 ++++-- fs/overlayfs/ovl_entry.h | 36 ++++++++++++++++++------------------ fs/overlayfs/super.c | 18 ++++-------------- fs/overlayfs/util.c | 8 ++++---- 8 files changed, 57 insertions(+), 62 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 9be52d8013c8..92bdcedfaaec 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -269,7 +269,7 @@ static int ovl_instantiate(struct dentry *dentry, struct inode *inode, ovl_dir_modified(dentry->d_parent, false); ovl_dentry_set_upper_alias(dentry); - ovl_dentry_init_reval(dentry, newdentry); + ovl_dentry_init_reval(dentry, newdentry, NULL); if (!hardlink) { /* diff --git a/fs/overlayfs/export.c b/fs/overlayfs/export.c index ddb546627749..be142ea73fad 100644 --- a/fs/overlayfs/export.c +++ b/fs/overlayfs/export.c @@ -286,7 +286,7 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb, struct dentry *lower = lowerpath ? lowerpath->dentry : NULL; struct dentry *upper = upper_alias ?: index; struct dentry *dentry; - struct inode *inode; + struct inode *inode = NULL; struct ovl_entry *oe; struct ovl_inode_params oip = { .lowerpath = lowerpath, @@ -298,9 +298,19 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb, if (d_is_dir(upper ?: lower)) return ERR_PTR(-EIO); + oe = ovl_alloc_entry(!!lower); + if (!oe) + return ERR_PTR(-ENOMEM); + oip.upperdentry = dget(upper); + if (lower) { + ovl_lowerstack(oe)->dentry = dget(lower); + ovl_lowerstack(oe)->layer = lowerpath->layer; + } + oip.oe = oe; inode = ovl_get_inode(sb, &oip); if (IS_ERR(inode)) { + ovl_free_entry(oe); dput(upper); return ERR_CAST(inode); } @@ -315,19 +325,11 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb, dentry = d_alloc_anon(inode->i_sb); if (unlikely(!dentry)) goto nomem; - oe = ovl_alloc_entry(lower ? 1 : 0); - if (!oe) - goto nomem; - if (lower) { - ovl_lowerstack(oe)->dentry = dget(lower); - ovl_lowerstack(oe)->layer = lowerpath->layer; - } - dentry->d_fsdata = oe; if (upper_alias) ovl_dentry_set_upper_alias(dentry); - ovl_dentry_init_reval(dentry, upper); + ovl_dentry_init_reval(dentry, upper, OVL_I_E(inode)); return d_instantiate_anon(dentry, inode); diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index e7e888dea634..995135ffec98 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -1002,8 +1002,9 @@ void ovl_inode_init(struct inode *inode, struct ovl_inode_params *oip, struct inode *realinode; struct ovl_inode *oi = OVL_I(inode); - if (oip->upperdentry) - oi->__upperdentry = oip->upperdentry; + oi->__upperdentry = oip->upperdentry; + oi->oe = oip->oe; + oi->redirect = oip->redirect; if (oip->lowerpath && oip->lowerpath->dentry) { oi->lowerpath.dentry = dget(oip->lowerpath->dentry); oi->lowerpath.layer = oip->lowerpath->layer; @@ -1368,6 +1369,7 @@ struct inode *ovl_get_inode(struct super_block *sb, } dput(upperdentry); + ovl_free_entry(oip->oe); kfree(oip->redirect); goto out; } @@ -1397,8 +1399,6 @@ struct inode *ovl_get_inode(struct super_block *sb, if (oip->index) ovl_set_flag(OVL_INDEX, inode); - OVL_I(inode)->redirect = oip->redirect; - if (bylower) ovl_set_flag(OVL_CONST_INO, inode); diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index c237c8dbff09..a7d975bf9e48 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -831,7 +831,7 @@ static int ovl_fix_origin(struct ovl_fs *ofs, struct dentry *dentry, struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { - struct ovl_entry *oe; + struct ovl_entry *oe = NULL; const struct cred *old_cred; struct ovl_fs *ofs = dentry->d_sb->s_fs_info; struct ovl_entry *poe = OVL_E(dentry->d_parent); @@ -1067,13 +1067,14 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } } - oe = ovl_alloc_entry(ctr); - err = -ENOMEM; - if (!oe) - goto out_put; + if (ctr) { + oe = ovl_alloc_entry(ctr); + err = -ENOMEM; + if (!oe) + goto out_put; - ovl_stack_cpy(ovl_lowerstack(oe), stack, ctr); - dentry->d_fsdata = oe; + ovl_stack_cpy(ovl_lowerstack(oe), stack, ctr); + } if (upperopaque) ovl_dentry_set_opaque(dentry); @@ -1107,6 +1108,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, struct ovl_inode_params oip = { .upperdentry = upperdentry, .lowerpath = stack, + .oe = oe, .index = index, .numlower = ctr, .redirect = upperredirect, @@ -1122,7 +1124,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, ovl_set_flag(OVL_UPPERDATA, inode); } - ovl_dentry_init_reval(dentry, upperdentry); + ovl_dentry_init_reval(dentry, upperdentry, OVL_I_E(inode)); revert_creds(old_cred); if (origin_path) { @@ -1135,7 +1137,6 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, return d_splice_alias(inode, dentry); out_free_oe: - dentry->d_fsdata = NULL; ovl_free_entry(oe); out_put: dput(index); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 4f83509a0294..cd74d7466a58 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -381,9 +381,10 @@ struct ovl_entry *ovl_alloc_entry(unsigned int numlower); void ovl_free_entry(struct ovl_entry *oe); bool ovl_dentry_remote(struct dentry *dentry); void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *realdentry); -void ovl_dentry_init_reval(struct dentry *dentry, struct dentry *upperdentry); +void ovl_dentry_init_reval(struct dentry *dentry, struct dentry *upperdentry, + struct ovl_entry *oe); void ovl_dentry_init_flags(struct dentry *dentry, struct dentry *upperdentry, - unsigned int mask); + struct ovl_entry *oe, unsigned int mask); bool ovl_dentry_weird(struct dentry *dentry); enum ovl_path_type ovl_path_type(struct dentry *dentry); void ovl_path_upper(struct dentry *dentry, struct path *path); @@ -653,6 +654,7 @@ struct ovl_inode_params { struct inode *newinode; struct dentry *upperdentry; struct ovl_path *lowerpath; + struct ovl_entry *oe; bool index; unsigned int numlower; char *redirect; diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 608742f60037..f511ac78c5bd 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -47,6 +47,11 @@ struct ovl_path { struct dentry *dentry; }; +struct ovl_entry { + unsigned int __numlower; + struct ovl_path __lowerstack[]; +}; + /* private information held for overlayfs's superblock */ struct ovl_fs { unsigned int numlayer; @@ -105,18 +110,6 @@ static inline bool ovl_should_sync(struct ovl_fs *ofs) return !ofs->config.ovl_volatile; } -/* private information held for every overlayfs dentry */ -struct ovl_entry { - union { - struct { - unsigned long flags; - }; - struct rcu_head rcu; - }; - unsigned int __numlower; - struct ovl_path __lowerstack[]; -}; - static inline unsigned int ovl_numlower(struct ovl_entry *oe) { return oe ? oe->__numlower : 0; @@ -127,14 +120,10 @@ static inline struct ovl_path *ovl_lowerstack(struct ovl_entry *oe) return ovl_numlower(oe) ? oe->__lowerstack : NULL; } -static inline struct ovl_entry *OVL_E(struct dentry *dentry) -{ - return (struct ovl_entry *) dentry->d_fsdata; -} - +/* private information held for every overlayfs dentry */ static inline unsigned long *OVL_E_FLAGS(struct dentry *dentry) { - return &OVL_E(dentry)->flags; + return (unsigned long *) &dentry->d_fsdata; } struct ovl_inode { @@ -148,6 +137,7 @@ struct ovl_inode { struct inode vfs_inode; struct dentry *__upperdentry; struct ovl_path lowerpath; + struct ovl_entry *oe; /* synchronize copy up and more */ struct mutex lock; @@ -158,6 +148,16 @@ static inline struct ovl_inode *OVL_I(struct inode *inode) return container_of(inode, struct ovl_inode, vfs_inode); } +static inline struct ovl_entry *OVL_I_E(struct inode *inode) +{ + return inode ? OVL_I(inode)->oe : NULL; +} + +static inline struct ovl_entry *OVL_E(struct dentry *dentry) +{ + return OVL_I_E(d_inode(dentry)); +} + static inline struct dentry *ovl_upperdentry_dereference(struct ovl_inode *oi) { return READ_ONCE(oi->__upperdentry); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 35ecea4b60b0..bb4a062ab0d5 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -59,16 +59,6 @@ module_param_named(metacopy, ovl_metacopy_def, bool, 0644); MODULE_PARM_DESC(metacopy, "Default to on or off for the metadata only copy up feature"); -static void ovl_dentry_release(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - if (oe) { - ovl_stack_put(ovl_lowerstack(oe), ovl_numlower(oe)); - kfree_rcu(oe, rcu); - } -} - static struct dentry *ovl_d_real(struct dentry *dentry, const struct inode *inode) { @@ -162,7 +152,6 @@ static int ovl_dentry_weak_revalidate(struct dentry *dentry, unsigned int flags) } static const struct dentry_operations ovl_dentry_operations = { - .d_release = ovl_dentry_release, .d_real = ovl_d_real, .d_revalidate = ovl_dentry_revalidate, .d_weak_revalidate = ovl_dentry_weak_revalidate, @@ -182,6 +171,7 @@ static struct inode *ovl_alloc_inode(struct super_block *sb) oi->version = 0; oi->flags = 0; oi->__upperdentry = NULL; + oi->oe = NULL; oi->lowerpath.dentry = NULL; oi->lowerpath.layer = NULL; oi->lowerdata = NULL; @@ -205,6 +195,7 @@ static void ovl_destroy_inode(struct inode *inode) dput(oi->__upperdentry); dput(oi->lowerpath.dentry); + ovl_free_entry(oi->oe); if (S_ISDIR(inode->i_mode)) ovl_dir_cache_free(inode); else @@ -1849,14 +1840,13 @@ static struct dentry *ovl_get_root(struct super_block *sb, struct ovl_inode_params oip = { .upperdentry = upperdentry, .lowerpath = lowerpath, + .oe = oe, }; root = d_make_root(ovl_new_inode(sb, S_IFDIR, 0)); if (!root) return NULL; - root->d_fsdata = oe; - if (upperdentry) { /* Root inode uses upper st_ino/i_ino */ ino = d_inode(upperdentry)->i_ino; @@ -1871,7 +1861,7 @@ static struct dentry *ovl_get_root(struct super_block *sb, ovl_dentry_set_flag(OVL_E_CONNECTED, root); ovl_set_upperdata(d_inode(root)); ovl_inode_init(d_inode(root), &oip, ino, fsid); - ovl_dentry_init_flags(root, upperdentry, DCACHE_OP_WEAK_REVALIDATE); + ovl_dentry_init_flags(root, upperdentry, oe, DCACHE_OP_WEAK_REVALIDATE); return root; } diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 4ff58f92100d..c4bd901f3bda 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -145,15 +145,15 @@ void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *realdentry) spin_unlock(&dentry->d_lock); } -void ovl_dentry_init_reval(struct dentry *dentry, struct dentry *upperdentry) +void ovl_dentry_init_reval(struct dentry *dentry, struct dentry *upperdentry, + struct ovl_entry *oe) { - return ovl_dentry_init_flags(dentry, upperdentry, OVL_D_REVALIDATE); + return ovl_dentry_init_flags(dentry, upperdentry, oe, OVL_D_REVALIDATE); } void ovl_dentry_init_flags(struct dentry *dentry, struct dentry *upperdentry, - unsigned int mask) + struct ovl_entry *oe, unsigned int mask) { - struct ovl_entry *oe = OVL_E(dentry); struct ovl_path *lowerstack = ovl_lowerstack(oe); unsigned int i, flags = 0; -- cgit v1.2.3-58-ga151 From ac900ed4f253434e099a025fc1da3d04bc741bd3 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sat, 1 Apr 2023 10:29:19 +0300 Subject: ovl: deduplicate lowerpath and lowerstack[] The ovl_inode contains a copy of lowerpath in lowerstack[0], so the lowerpath member can be removed. Use accessor ovl_lowerpath() to get the lowerpath whereever the member was accessed directly. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/export.c | 2 -- fs/overlayfs/inode.c | 8 ++------ fs/overlayfs/namei.c | 2 -- fs/overlayfs/overlayfs.h | 2 -- fs/overlayfs/ovl_entry.h | 6 +++++- fs/overlayfs/super.c | 4 ---- fs/overlayfs/util.c | 10 ++++++---- 7 files changed, 13 insertions(+), 21 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/export.c b/fs/overlayfs/export.c index be142ea73fad..35680b6e175b 100644 --- a/fs/overlayfs/export.c +++ b/fs/overlayfs/export.c @@ -289,9 +289,7 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb, struct inode *inode = NULL; struct ovl_entry *oe; struct ovl_inode_params oip = { - .lowerpath = lowerpath, .index = index, - .numlower = !!lower }; /* We get overlay directory dentries with ovl_lookup_real() */ diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 995135ffec98..4dbf8d880c0f 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -1005,10 +1005,6 @@ void ovl_inode_init(struct inode *inode, struct ovl_inode_params *oip, oi->__upperdentry = oip->upperdentry; oi->oe = oip->oe; oi->redirect = oip->redirect; - if (oip->lowerpath && oip->lowerpath->dentry) { - oi->lowerpath.dentry = dget(oip->lowerpath->dentry); - oi->lowerpath.layer = oip->lowerpath->layer; - } if (oip->lowerdata) oi->lowerdata = igrab(d_inode(oip->lowerdata)); @@ -1325,7 +1321,7 @@ struct inode *ovl_get_inode(struct super_block *sb, { struct ovl_fs *ofs = OVL_FS(sb); struct dentry *upperdentry = oip->upperdentry; - struct ovl_path *lowerpath = oip->lowerpath; + struct ovl_path *lowerpath = ovl_lowerpath(oip->oe); struct inode *realinode = upperdentry ? d_inode(upperdentry) : NULL; struct inode *inode; struct dentry *lowerdentry = lowerpath ? lowerpath->dentry : NULL; @@ -1404,7 +1400,7 @@ struct inode *ovl_get_inode(struct super_block *sb, /* Check for non-merge dir that may have whiteouts */ if (is_dir) { - if (((upperdentry && lowerdentry) || oip->numlower > 1) || + if (((upperdentry && lowerdentry) || ovl_numlower(oip->oe) > 1) || ovl_path_check_origin_xattr(ofs, &realpath)) { ovl_set_flag(OVL_WHITEOUTS, inode); } diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index a7d975bf9e48..317ab1efc236 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -1107,10 +1107,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (upperdentry || ctr) { struct ovl_inode_params oip = { .upperdentry = upperdentry, - .lowerpath = stack, .oe = oe, .index = index, - .numlower = ctr, .redirect = upperredirect, .lowerdata = (ctr > 1 && !d.is_dir) ? stack[ctr - 1].dentry : NULL, diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index cd74d7466a58..8b38844ae341 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -653,10 +653,8 @@ bool ovl_is_private_xattr(struct super_block *sb, const char *name); struct ovl_inode_params { struct inode *newinode; struct dentry *upperdentry; - struct ovl_path *lowerpath; struct ovl_entry *oe; bool index; - unsigned int numlower; char *redirect; struct dentry *lowerdata; }; diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index f511ac78c5bd..0bb8ab3aa8a7 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -120,6 +120,11 @@ static inline struct ovl_path *ovl_lowerstack(struct ovl_entry *oe) return ovl_numlower(oe) ? oe->__lowerstack : NULL; } +static inline struct ovl_path *ovl_lowerpath(struct ovl_entry *oe) +{ + return ovl_lowerstack(oe); +} + /* private information held for every overlayfs dentry */ static inline unsigned long *OVL_E_FLAGS(struct dentry *dentry) { @@ -136,7 +141,6 @@ struct ovl_inode { unsigned long flags; struct inode vfs_inode; struct dentry *__upperdentry; - struct ovl_path lowerpath; struct ovl_entry *oe; /* synchronize copy up and more */ diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index bb4a062ab0d5..1efc4f252561 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -172,8 +172,6 @@ static struct inode *ovl_alloc_inode(struct super_block *sb) oi->flags = 0; oi->__upperdentry = NULL; oi->oe = NULL; - oi->lowerpath.dentry = NULL; - oi->lowerpath.layer = NULL; oi->lowerdata = NULL; mutex_init(&oi->lock); @@ -194,7 +192,6 @@ static void ovl_destroy_inode(struct inode *inode) struct ovl_inode *oi = OVL_I(inode); dput(oi->__upperdentry); - dput(oi->lowerpath.dentry); ovl_free_entry(oi->oe); if (S_ISDIR(inode->i_mode)) ovl_dir_cache_free(inode); @@ -1839,7 +1836,6 @@ static struct dentry *ovl_get_root(struct super_block *sb, int fsid = lowerpath->layer->fsid; struct ovl_inode_params oip = { .upperdentry = upperdentry, - .lowerpath = lowerpath, .oe = oe, }; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index c4bd901f3bda..143e6898f50d 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -306,10 +306,12 @@ struct dentry *ovl_i_dentry_upper(struct inode *inode) struct inode *ovl_i_path_real(struct inode *inode, struct path *path) { + struct ovl_path *lowerpath = ovl_lowerpath(OVL_I_E(inode)); + path->dentry = ovl_i_dentry_upper(inode); if (!path->dentry) { - path->dentry = OVL_I(inode)->lowerpath.dentry; - path->mnt = OVL_I(inode)->lowerpath.layer->mnt; + path->dentry = lowerpath->dentry; + path->mnt = lowerpath->layer->mnt; } else { path->mnt = ovl_upper_mnt(OVL_FS(inode->i_sb)); } @@ -326,9 +328,9 @@ struct inode *ovl_inode_upper(struct inode *inode) struct inode *ovl_inode_lower(struct inode *inode) { - struct dentry *lowerdentry = OVL_I(inode)->lowerpath.dentry; + struct ovl_path *lowerpath = ovl_lowerpath(OVL_I_E(inode)); - return lowerdentry ? d_inode(lowerdentry) : NULL; + return lowerpath ? d_inode(lowerpath->dentry) : NULL; } struct inode *ovl_inode_real(struct inode *inode) -- cgit v1.2.3-58-ga151 From ab1eb5ffb7d2a69c9b24083026a63cbdd758d96b Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sat, 1 Apr 2023 10:29:19 +0300 Subject: ovl: deduplicate lowerdata and lowerstack[] The ovl_inode contains a copy of lowerdata in lowerstack[], so the lowerdata inode member can be removed. Use accessors ovl_lowerdata*() to get the lowerdata whereever the member was accessed directly. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 2 -- fs/overlayfs/namei.c | 2 -- fs/overlayfs/overlayfs.h | 1 - fs/overlayfs/ovl_entry.h | 16 +++++++++++++++- fs/overlayfs/super.c | 3 --- fs/overlayfs/util.c | 15 +++++++-------- 6 files changed, 22 insertions(+), 17 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 4dbf8d880c0f..5239a5bfc6d1 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -1005,8 +1005,6 @@ void ovl_inode_init(struct inode *inode, struct ovl_inode_params *oip, oi->__upperdentry = oip->upperdentry; oi->oe = oip->oe; oi->redirect = oip->redirect; - if (oip->lowerdata) - oi->lowerdata = igrab(d_inode(oip->lowerdata)); realinode = ovl_inode_real(inode); ovl_copyattr(inode); diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 317ab1efc236..8ea2a8358a92 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -1110,8 +1110,6 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, .oe = oe, .index = index, .redirect = upperredirect, - .lowerdata = (ctr > 1 && !d.is_dir) ? - stack[ctr - 1].dentry : NULL, }; inode = ovl_get_inode(dentry->d_sb, &oip); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 8b38844ae341..2ccd6326c2c7 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -656,7 +656,6 @@ struct ovl_inode_params { struct ovl_entry *oe; bool index; char *redirect; - struct dentry *lowerdata; }; void ovl_inode_init(struct inode *inode, struct ovl_inode_params *oip, unsigned long ino, int fsid); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 0bb8ab3aa8a7..548c93e030fc 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -125,6 +125,20 @@ static inline struct ovl_path *ovl_lowerpath(struct ovl_entry *oe) return ovl_lowerstack(oe); } +static inline struct ovl_path *ovl_lowerdata(struct ovl_entry *oe) +{ + struct ovl_path *lowerstack = ovl_lowerstack(oe); + + return lowerstack ? &lowerstack[oe->__numlower - 1] : NULL; +} + +static inline struct dentry *ovl_lowerdata_dentry(struct ovl_entry *oe) +{ + struct ovl_path *lowerdata = ovl_lowerdata(oe); + + return lowerdata ? lowerdata->dentry : NULL; +} + /* private information held for every overlayfs dentry */ static inline unsigned long *OVL_E_FLAGS(struct dentry *dentry) { @@ -134,7 +148,7 @@ static inline unsigned long *OVL_E_FLAGS(struct dentry *dentry) struct ovl_inode { union { struct ovl_dir_cache *cache; /* directory */ - struct inode *lowerdata; /* regular file */ + /* place holder for non-dir */ /* regular file */ }; const char *redirect; u64 version; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 1efc4f252561..dace72ed36c2 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -172,7 +172,6 @@ static struct inode *ovl_alloc_inode(struct super_block *sb) oi->flags = 0; oi->__upperdentry = NULL; oi->oe = NULL; - oi->lowerdata = NULL; mutex_init(&oi->lock); return &oi->vfs_inode; @@ -195,8 +194,6 @@ static void ovl_destroy_inode(struct inode *inode) ovl_free_entry(oi->oe); if (S_ISDIR(inode->i_mode)) ovl_dir_cache_free(inode); - else - iput(oi->lowerdata); } static void ovl_free_fs(struct ovl_fs *ofs) diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 143e6898f50d..b6e66bf8135f 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -225,11 +225,11 @@ void ovl_path_lower(struct dentry *dentry, struct path *path) void ovl_path_lowerdata(struct dentry *dentry, struct path *path) { struct ovl_entry *oe = OVL_E(dentry); - struct ovl_path *lowerstack = ovl_lowerstack(oe); + struct ovl_path *lowerdata = ovl_lowerdata(oe); if (ovl_numlower(oe)) { - path->mnt = lowerstack[ovl_numlower(oe) - 1].layer->mnt; - path->dentry = lowerstack[ovl_numlower(oe) - 1].dentry; + path->mnt = lowerdata->layer->mnt; + path->dentry = lowerdata->dentry; } else { *path = (struct path) { }; } @@ -288,10 +288,7 @@ const struct ovl_layer *ovl_layer_lower(struct dentry *dentry) */ struct dentry *ovl_dentry_lowerdata(struct dentry *dentry) { - struct ovl_entry *oe = OVL_E(dentry); - - return ovl_numlower(oe) ? - ovl_lowerstack(oe)[ovl_numlower(oe) - 1].dentry : NULL; + return ovl_lowerdata_dentry(OVL_E(dentry)); } struct dentry *ovl_dentry_real(struct dentry *dentry) @@ -341,10 +338,12 @@ struct inode *ovl_inode_real(struct inode *inode) /* Return inode which contains lower data. Do not return metacopy */ struct inode *ovl_inode_lowerdata(struct inode *inode) { + struct dentry *lowerdata = ovl_lowerdata_dentry(OVL_I_E(inode)); + if (WARN_ON(!S_ISREG(inode->i_mode))) return NULL; - return OVL_I(inode)->lowerdata ?: ovl_inode_lower(inode); + return lowerdata ? d_inode(lowerdata) : NULL; } /* Return real inode which contains data. Does not return metacopy inode */ -- cgit v1.2.3-58-ga151 From 9e88f9052415289f2789abb6ae68a40a4701b8e1 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sat, 1 Apr 2023 12:51:55 +0300 Subject: ovl: remove unneeded goto instructions There is nothing in the out goto target of ovl_get_layers(). Reviewed-by: Alexander Larsson Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index dace72ed36c2..b1124cf85206 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1575,10 +1575,9 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, int err; unsigned int i; - err = -ENOMEM; ofs->fs = kcalloc(numlower + 1, sizeof(struct ovl_sb), GFP_KERNEL); if (ofs->fs == NULL) - goto out; + return -ENOMEM; /* idx/fsid 0 are reserved for upper fs even with lower only overlay */ ofs->numfs++; @@ -1592,7 +1591,7 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, err = get_anon_bdev(&ofs->fs[0].pseudo_dev); if (err) { pr_err("failed to get anonymous bdev for upper fs\n"); - goto out; + return err; } if (ovl_upper_mnt(ofs)) { @@ -1605,9 +1604,9 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, struct inode *trap; int fsid; - err = fsid = ovl_get_fsid(ofs, &stack[i]); - if (err < 0) - goto out; + fsid = ovl_get_fsid(ofs, &stack[i]); + if (fsid < 0) + return fsid; /* * Check if lower root conflicts with this overlay layers before @@ -1618,13 +1617,13 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, */ err = ovl_setup_trap(sb, stack[i].dentry, &trap, "lowerdir"); if (err) - goto out; + return err; if (ovl_is_inuse(stack[i].dentry)) { err = ovl_report_in_use(ofs, "lowerdir"); if (err) { iput(trap); - goto out; + return err; } } @@ -1633,7 +1632,7 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, if (IS_ERR(mnt)) { pr_err("failed to clone lowerpath\n"); iput(trap); - goto out; + return err; } /* @@ -1683,9 +1682,7 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, ofs->xino_mode); } - err = 0; -out: - return err; + return 0; } static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb, -- cgit v1.2.3-58-ga151 From 37ebf056d6cfc6638c821386bc33a9602bbc0d28 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Thu, 27 Apr 2023 12:48:46 +0300 Subject: ovl: introduce data-only lower layers Introduce the format lowerdir=lower1:lower2::lowerdata1::lowerdata2 where the lower layers on the right of the :: separators are not merged into the overlayfs merge dirs. Data-only lower layers are only allowed at the bottom of the stack. The files in those layers are only meant to be accessible via absolute redirect from metacopy files in lower layers. Following changes will implement lookup in the data layers. This feature was requested for composefs ostree use case, where the lower data layer should only be accessiable via absolute redirects from metacopy inodes. The lower data layers are not required to a have a unique uuid or any uuid at all, because they are never used to compose the overlayfs inode st_ino/st_dev. Reviewed-by: Alexander Larsson Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- Documentation/filesystems/overlayfs.rst | 35 +++++++++++++++++ fs/overlayfs/namei.c | 2 +- fs/overlayfs/ovl_entry.h | 9 +++++ fs/overlayfs/super.c | 69 +++++++++++++++++++++++++++++---- 4 files changed, 107 insertions(+), 8 deletions(-) (limited to 'fs/overlayfs') diff --git a/Documentation/filesystems/overlayfs.rst b/Documentation/filesystems/overlayfs.rst index 4c76fda07645..4f36b8919f7c 100644 --- a/Documentation/filesystems/overlayfs.rst +++ b/Documentation/filesystems/overlayfs.rst @@ -371,6 +371,41 @@ conflict with metacopy=on, and will result in an error. [*] redirect_dir=follow only conflicts with metacopy=on if upperdir=... is given. + +Data-only lower layers +---------------------- + +With "metacopy" feature enabled, an overlayfs regular file may be a composition +of information from up to three different layers: + + 1) metadata from a file in the upper layer + + 2) st_ino and st_dev object identifier from a file in a lower layer + + 3) data from a file in another lower layer (further below) + +The "lower data" file can be on any lower layer, except from the top most +lower layer. + +Below the top most lower layer, any number of lower most layers may be defined +as "data-only" lower layers, using double colon ("::") separators. +A normal lower layer is not allowed to be below a data-only layer, so single +colon separators are not allowed to the right of double colon ("::") separators. + + +For example: + + mount -t overlay overlay -olowerdir=/l1:/l2:/l3::/do1::/do2 /merged + +The paths of files in the "data-only" lower layers are not visible in the +merged overlayfs directories and the metadata and st_ino/st_dev of files +in the "data-only" lower layers are not visible in overlayfs inodes. + +Only the data of the files in the "data-only" lower layers may be visible +when a "metacopy" file in one of the lower layers above it, has a "redirect" +to the absolute path of the "lower data" file in the "data-only" lower layer. + + Sharing and copying layers -------------------------- diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 8ea2a8358a92..5c1b0397c8ce 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -356,7 +356,7 @@ int ovl_check_origin_fh(struct ovl_fs *ofs, struct ovl_fh *fh, bool connected, struct dentry *origin = NULL; int i; - for (i = 1; i < ofs->numlayer; i++) { + for (i = 1; i <= ovl_numlowerlayer(ofs); i++) { /* * If lower fs uuid is not unique among lower fs we cannot match * fh->uuid to layer. diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 548c93e030fc..93ff299da0dd 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -57,6 +57,8 @@ struct ovl_fs { unsigned int numlayer; /* Number of unique fs among layers including upper fs */ unsigned int numfs; + /* Number of data-only lower layers */ + unsigned int numdatalayer; const struct ovl_layer *layers; struct ovl_sb *fs; /* workbasedir is the path at workdir= mount option */ @@ -90,6 +92,13 @@ struct ovl_fs { errseq_t errseq; }; + +/* Number of lower layers, not including data-only layers */ +static inline unsigned int ovl_numlowerlayer(struct ovl_fs *ofs) +{ + return ofs->numlayer - ofs->numdatalayer - 1; +} + static inline struct vfsmount *ovl_upper_mnt(struct ovl_fs *ofs) { return ofs->layers[0].mnt; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index b1124cf85206..be2bdf37b0d5 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1568,6 +1568,16 @@ static int ovl_get_fsid(struct ovl_fs *ofs, const struct path *path) return ofs->numfs++; } +/* + * The fsid after the last lower fsid is used for the data layers. + * It is a "null fs" with a null sb, null uuid, and no pseudo dev. + */ +static int ovl_get_data_fsid(struct ovl_fs *ofs) +{ + return ofs->numfs; +} + + static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, struct path *stack, unsigned int numlower, struct ovl_layer *layers) @@ -1575,11 +1585,14 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, int err; unsigned int i; - ofs->fs = kcalloc(numlower + 1, sizeof(struct ovl_sb), GFP_KERNEL); + ofs->fs = kcalloc(numlower + 2, sizeof(struct ovl_sb), GFP_KERNEL); if (ofs->fs == NULL) return -ENOMEM; - /* idx/fsid 0 are reserved for upper fs even with lower only overlay */ + /* + * idx/fsid 0 are reserved for upper fs even with lower only overlay + * and the last fsid is reserved for "null fs" of the data layers. + */ ofs->numfs++; /* @@ -1604,7 +1617,10 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, struct inode *trap; int fsid; - fsid = ovl_get_fsid(ofs, &stack[i]); + if (i < numlower - ofs->numdatalayer) + fsid = ovl_get_fsid(ofs, &stack[i]); + else + fsid = ovl_get_data_fsid(ofs); if (fsid < 0) return fsid; @@ -1692,6 +1708,7 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb, int err; struct path *stack = NULL; struct ovl_path *lowerstack; + unsigned int numlowerdata = 0; unsigned int i; struct ovl_entry *oe; @@ -1704,13 +1721,50 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb, if (!stack) return ERR_PTR(-ENOMEM); - err = -EINVAL; - for (i = 0; i < numlower; i++) { + for (i = 0; i < numlower;) { err = ovl_lower_dir(lower, &stack[i], ofs, &sb->s_stack_depth); if (err) goto out_err; lower = strchr(lower, '\0') + 1; + + i++; + if (i == numlower) + break; + + err = -EINVAL; + /* + * Empty lower layer path could mean :: separator that indicates + * a data-only lower data. + * Several data-only layers are allowed, but they all need to be + * at the bottom of the stack. + */ + if (*lower) { + /* normal lower dir */ + if (numlowerdata) { + pr_err("lower data-only dirs must be at the bottom of the stack.\n"); + goto out_err; + } + } else { + /* data-only lower dir */ + if (!ofs->config.metacopy) { + pr_err("lower data-only dirs require metacopy support.\n"); + goto out_err; + } + if (i == numlower - 1) { + pr_err("lowerdir argument must not end with double colon.\n"); + goto out_err; + } + lower++; + numlower--; + numlowerdata++; + } + } + + if (numlowerdata) { + ofs->numdatalayer = numlowerdata; + pr_info("using the lowest %d of %d lowerdirs as data layers\n", + numlowerdata, numlower); } err = -EINVAL; @@ -1725,12 +1779,13 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb, goto out_err; err = -ENOMEM; - oe = ovl_alloc_entry(numlower); + /* Data-only layers are not merged in root directory */ + oe = ovl_alloc_entry(numlower - numlowerdata); if (!oe) goto out_err; lowerstack = ovl_lowerstack(oe); - for (i = 0; i < numlower; i++) { + for (i = 0; i < numlower - numlowerdata; i++) { lowerstack[i].dentry = dget(stack[i].dentry); lowerstack[i].layer = &ofs->layers[i+1]; } -- cgit v1.2.3-58-ga151 From 5436ab0a864e8bb263919e51c26395a67bde17ca Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Thu, 27 Apr 2023 14:52:13 +0300 Subject: ovl: implement lookup in data-only layers Lookup in data-only layers only for a lower metacopy with an absolute redirect xattr. The metacopy xattr is not checked on files found in the data-only layers and redirect xattr are not followed in the data-only layers. Reviewed-by: Alexander Larsson Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/namei.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 5c1b0397c8ce..517f31a28f72 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -14,6 +14,8 @@ #include #include "overlayfs.h" +#include "../internal.h" /* for vfs_path_lookup */ + struct ovl_lookup_data { struct super_block *sb; struct vfsmount *mnt; @@ -24,6 +26,8 @@ struct ovl_lookup_data { bool last; char *redirect; bool metacopy; + /* Referring to last redirect xattr */ + bool absolute_redirect; }; static int ovl_check_redirect(const struct path *path, struct ovl_lookup_data *d, @@ -33,11 +37,13 @@ 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; /* * One of the ancestor path elements in an absolute path * lookup in ovl_lookup_layer() could have been opaque and @@ -349,6 +355,61 @@ static int ovl_lookup_layer(struct dentry *base, struct ovl_lookup_data *d, return 0; } +static int ovl_lookup_data_layer(struct dentry *dentry, const char *redirect, + const struct ovl_layer *layer, + struct path *datapath) +{ + int err; + + err = vfs_path_lookup(layer->mnt->mnt_root, layer->mnt, redirect, + LOOKUP_BENEATH | LOOKUP_NO_SYMLINKS | LOOKUP_NO_XDEV, + datapath); + pr_debug("lookup lowerdata (%pd2, redirect=\"%s\", layer=%d, err=%i)\n", + dentry, redirect, layer->idx, err); + + if (err) + return err; + + err = -EREMOTE; + if (ovl_dentry_weird(datapath->dentry)) + goto out_path_put; + + err = -ENOENT; + /* Only regular file is acceptable as lower data */ + if (!d_is_reg(datapath->dentry)) + goto out_path_put; + + return 0; + +out_path_put: + path_put(datapath); + + return err; +} + +/* Lookup in data-only layers by absolute redirect to layer root */ +static int ovl_lookup_data_layers(struct dentry *dentry, const char *redirect, + struct ovl_path *lowerdata) +{ + struct ovl_fs *ofs = OVL_FS(dentry->d_sb); + const struct ovl_layer *layer; + struct path datapath; + int err = -ENOENT; + int i; + + layer = &ofs->layers[ofs->numlayer - ofs->numdatalayer]; + for (i = 0; i < ofs->numdatalayer; i++, layer++) { + err = ovl_lookup_data_layer(dentry, redirect, layer, &datapath); + if (!err) { + mntput(datapath.mnt); + lowerdata->dentry = datapath.dentry; + lowerdata->layer = layer; + return 0; + } + } + + return err; +} int ovl_check_origin_fh(struct ovl_fs *ofs, struct ovl_fh *fh, bool connected, struct dentry *upperdentry, struct ovl_path **stackp) @@ -917,7 +978,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (!ofs->config.redirect_follow) d.last = i == ovl_numlower(poe) - 1; - else + else if (d.is_dir || !ofs->numdatalayer) d.last = lower.layer->idx == ovl_numlower(roe); d.mnt = lower.layer->mnt; @@ -1011,6 +1072,16 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } } + /* Lookup absolute redirect from lower metacopy in data-only layers */ + if (d.metacopy && ctr && ofs->numdatalayer && d.absolute_redirect) { + err = ovl_lookup_data_layers(dentry, d.redirect, + &stack[ctr]); + if (!err) { + d.metacopy = false; + ctr++; + } + } + /* * For regular non-metacopy upper dentries, there is no lower * path based lookup, hence ctr will be zero. If a dentry is found -- cgit v1.2.3-58-ga151 From 2b21da920866ad20b5e3119f3b8d2267774b7b0a Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Thu, 27 Apr 2023 12:21:46 +0300 Subject: ovl: prepare to store lowerdata redirect for lazy lowerdata lookup Prepare to allow ovl_lookup() to leave the last entry in a non-dir lowerstack empty to signify lazy lowerdata lookup. In this case, ovl_lookup() stores the redirect path from metacopy to lowerdata in ovl_inode, which is going to be used later to perform the lazy lowerdata lookup. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 2 ++ fs/overlayfs/namei.c | 5 +++++ fs/overlayfs/overlayfs.h | 2 ++ fs/overlayfs/ovl_entry.h | 3 ++- fs/overlayfs/super.c | 3 +++ fs/overlayfs/util.c | 13 ++++++++++--- 6 files changed, 24 insertions(+), 4 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 5239a5bfc6d1..552059e759b6 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -1005,6 +1005,7 @@ void ovl_inode_init(struct inode *inode, struct ovl_inode_params *oip, oi->__upperdentry = oip->upperdentry; oi->oe = oip->oe; oi->redirect = oip->redirect; + oi->lowerdata_redirect = oip->lowerdata_redirect; realinode = ovl_inode_real(inode); ovl_copyattr(inode); @@ -1365,6 +1366,7 @@ struct inode *ovl_get_inode(struct super_block *sb, dput(upperdentry); ovl_free_entry(oip->oe); kfree(oip->redirect); + kfree(oip->lowerdata_redirect); goto out; } diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 517f31a28f72..605108f0dbe9 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -1183,6 +1183,11 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, .redirect = upperredirect, }; + /* Store lowerdata redirect for lazy lookup */ + if (ctr > 1 && !d.is_dir && !stack[ctr - 1].dentry) { + oip.lowerdata_redirect = d.redirect; + d.redirect = NULL; + } inode = ovl_get_inode(dentry->d_sb, &oip); err = PTR_ERR(inode); if (IS_ERR(inode)) diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 2ccd6326c2c7..5cc03b8d7b25 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -405,6 +405,7 @@ struct inode *ovl_inode_lower(struct inode *inode); struct inode *ovl_inode_lowerdata(struct inode *inode); struct inode *ovl_inode_real(struct inode *inode); struct inode *ovl_inode_realdata(struct inode *inode); +const char *ovl_lowerdata_redirect(struct inode *inode); struct ovl_dir_cache *ovl_dir_cache(struct inode *inode); void ovl_set_dir_cache(struct inode *inode, struct ovl_dir_cache *cache); void ovl_dentry_set_flag(unsigned long flag, struct dentry *dentry); @@ -656,6 +657,7 @@ struct ovl_inode_params { struct ovl_entry *oe; bool index; char *redirect; + char *lowerdata_redirect; }; void ovl_inode_init(struct inode *inode, struct ovl_inode_params *oip, unsigned long ino, int fsid); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 93ff299da0dd..513c2c499e41 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -141,6 +141,7 @@ static inline struct ovl_path *ovl_lowerdata(struct ovl_entry *oe) return lowerstack ? &lowerstack[oe->__numlower - 1] : NULL; } +/* May return NULL if lazy lookup of lowerdata is needed */ static inline struct dentry *ovl_lowerdata_dentry(struct ovl_entry *oe) { struct ovl_path *lowerdata = ovl_lowerdata(oe); @@ -157,7 +158,7 @@ static inline unsigned long *OVL_E_FLAGS(struct dentry *dentry) struct ovl_inode { union { struct ovl_dir_cache *cache; /* directory */ - /* place holder for non-dir */ /* regular file */ + const char *lowerdata_redirect; /* regular file */ }; const char *redirect; u64 version; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index be2bdf37b0d5..9de02ca2387e 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -171,6 +171,7 @@ static struct inode *ovl_alloc_inode(struct super_block *sb) oi->version = 0; oi->flags = 0; oi->__upperdentry = NULL; + oi->lowerdata_redirect = NULL; oi->oe = NULL; mutex_init(&oi->lock); @@ -194,6 +195,8 @@ static void ovl_destroy_inode(struct inode *inode) ovl_free_entry(oi->oe); if (S_ISDIR(inode->i_mode)) ovl_dir_cache_free(inode); + else + kfree(oi->lowerdata_redirect); } static void ovl_free_fs(struct ovl_fs *ofs) diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index b6e66bf8135f..72f565f640fa 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -226,10 +226,11 @@ void ovl_path_lowerdata(struct dentry *dentry, struct path *path) { struct ovl_entry *oe = OVL_E(dentry); struct ovl_path *lowerdata = ovl_lowerdata(oe); + struct dentry *lowerdata_dentry = ovl_lowerdata_dentry(oe); - if (ovl_numlower(oe)) { + if (lowerdata_dentry) { path->mnt = lowerdata->layer->mnt; - path->dentry = lowerdata->dentry; + path->dentry = lowerdata_dentry; } else { *path = (struct path) { }; } @@ -358,9 +359,15 @@ struct inode *ovl_inode_realdata(struct inode *inode) return ovl_inode_lowerdata(inode); } +const char *ovl_lowerdata_redirect(struct inode *inode) +{ + return inode && S_ISREG(inode->i_mode) ? + OVL_I(inode)->lowerdata_redirect : NULL; +} + struct ovl_dir_cache *ovl_dir_cache(struct inode *inode) { - return OVL_I(inode)->cache; + return inode && S_ISDIR(inode->i_mode) ? OVL_I(inode)->cache : NULL; } void ovl_set_dir_cache(struct inode *inode, struct ovl_dir_cache *cache) -- cgit v1.2.3-58-ga151 From 416656447864ef0fc2430525499f52a4a370a0ab Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sun, 2 Apr 2023 21:56:49 +0300 Subject: ovl: prepare for lazy lookup of lowerdata inode Make the code handle the case of numlower > 1 and missing lowerdata dentry gracefully. Missing lowerdata dentry is an indication for lazy lookup of lowerdata and in that case the lowerdata_redirect path is stored in ovl_inode. Following commits will defer lookup and perform the lazy lookup on access. Reviewed-by: Alexander Larsson Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/file.c | 7 +++++++ fs/overlayfs/inode.c | 18 ++++++++++++++---- fs/overlayfs/super.c | 11 +++++++++++ fs/overlayfs/util.c | 2 +- 4 files changed, 33 insertions(+), 5 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index 7c04f033aadd..951683a66ff6 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c @@ -115,6 +115,9 @@ static int ovl_real_fdget_meta(const struct file *file, struct fd *real, ovl_path_real(dentry, &realpath); else ovl_path_realdata(dentry, &realpath); + /* TODO: lazy lookup of lowerdata */ + if (!realpath.dentry) + return -EIO; /* Has it been copied up since we'd opened it? */ if (unlikely(file_inode(real->file) != d_inode(realpath.dentry))) { @@ -158,6 +161,10 @@ static int ovl_open(struct inode *inode, struct file *file) file->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); ovl_path_realdata(dentry, &realpath); + /* TODO: lazy lookup of lowerdata */ + if (!realpath.dentry) + return -EIO; + realfile = ovl_open_realfile(file, &realpath); if (IS_ERR(realfile)) return PTR_ERR(realfile); diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 552059e759b6..bf47a0a94d1e 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -240,15 +240,22 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, /* * If lower is not same as lowerdata or if there was * no origin on upper, we can end up here. + * With lazy lowerdata lookup, guess lowerdata blocks + * from size to avoid lowerdata lookup on stat(2). */ struct kstat lowerdatastat; u32 lowermask = STATX_BLOCKS; ovl_path_lowerdata(dentry, &realpath); - err = vfs_getattr(&realpath, &lowerdatastat, - lowermask, flags); - if (err) - goto out; + if (realpath.dentry) { + err = vfs_getattr(&realpath, &lowerdatastat, + lowermask, flags); + if (err) + goto out; + } else { + lowerdatastat.blocks = + round_up(stat->size, stat->blksize) >> 9; + } stat->blocks = lowerdatastat.blocks; } } @@ -709,6 +716,9 @@ static int ovl_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, struct inode *realinode = ovl_inode_realdata(inode); const struct cred *old_cred; + if (!realinode) + return -EIO; + if (!realinode->i_op->fiemap) return -EOPNOTSUPP; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 9de02ca2387e..f3e5b43c4106 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -81,6 +81,14 @@ static struct dentry *ovl_d_real(struct dentry *dentry, if (real && !inode && ovl_has_upperdata(d_inode(dentry))) return real; + /* + * XXX: We may need lazy lookup of lowerdata for !inode case to return + * the real lowerdata dentry. The only current caller of d_real() with + * NULL inode is d_real_inode() from trace_uprobe and this caller is + * likely going to be followed reading from the file, before placing + * uprobes on offset within the file, so lowerdata should be available + * when setting the uprobe. + */ lower = ovl_dentry_lowerdata(dentry); if (!lower) goto bug; @@ -103,6 +111,9 @@ static int ovl_revalidate_real(struct dentry *d, unsigned int flags, bool weak) { int ret = 1; + if (!d) + return 1; + if (weak) { if (d->d_flags & DCACHE_OP_WEAK_REVALIDATE) ret = d->d_op->d_weak_revalidate(d, flags); diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 72f565f640fa..23c0224779be 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -159,7 +159,7 @@ void ovl_dentry_init_flags(struct dentry *dentry, struct dentry *upperdentry, if (upperdentry) flags |= upperdentry->d_flags; - for (i = 0; i < ovl_numlower(oe); i++) + for (i = 0; i < ovl_numlower(oe) && lowerstack[i].dentry; i++) flags |= lowerstack[i].dentry->d_flags; spin_lock(&dentry->d_lock); -- cgit v1.2.3-58-ga151 From 42dd69ae1af42cf72d167e63a039b8c63653eb7f Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Thu, 27 Apr 2023 13:39:09 +0300 Subject: ovl: implement lazy lookup of lowerdata in data-only layers Defer lookup of lowerdata in the data-only layers to first data access or before copy up. We perform lowerdata lookup before copy up even if copy up is metadata only copy up. We can further optimize this lookup later if needed. We do best effort lazy lookup of lowerdata for d_real_inode(), because this interface does not expect errors. The only current in-tree caller of d_real_inode() is trace_uprobe and this caller is likely going to be followed reading from the file, before placing uprobes on offset within the file, so lowerdata should be available when setting the uprobe. Tested-by: kernel test robot Reviewed-by: Alexander Larsson Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 9 ++++++++ fs/overlayfs/file.c | 18 ++++++++++++---- fs/overlayfs/namei.c | 56 ++++++++++++++++++++++++++++++++++++++++++------ fs/overlayfs/overlayfs.h | 2 ++ fs/overlayfs/ovl_entry.h | 2 +- fs/overlayfs/super.c | 3 ++- fs/overlayfs/util.c | 31 ++++++++++++++++++++++++++- 7 files changed, 107 insertions(+), 14 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 95dce240ba17..568f743a5584 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -1073,6 +1073,15 @@ static int ovl_copy_up_flags(struct dentry *dentry, int flags) if (WARN_ON(disconnected && d_is_dir(dentry))) return -EIO; + /* + * We may not need lowerdata if we are only doing metacopy up, but it is + * not very important to optimize this case, so do lazy lowerdata lookup + * before any copy up, so we can do it before taking ovl_inode_lock(). + */ + err = ovl_maybe_lookup_lowerdata(dentry); + if (err) + return err; + old_cred = ovl_override_creds(dentry->d_sb); while (!err) { struct dentry *next; diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index 951683a66ff6..39737c2aaa84 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c @@ -107,15 +107,21 @@ static int ovl_real_fdget_meta(const struct file *file, struct fd *real, { struct dentry *dentry = file_dentry(file); struct path realpath; + int err; real->flags = 0; real->file = file->private_data; - if (allow_meta) + if (allow_meta) { ovl_path_real(dentry, &realpath); - else + } else { + /* lazy lookup of lowerdata */ + err = ovl_maybe_lookup_lowerdata(dentry); + if (err) + return err; + ovl_path_realdata(dentry, &realpath); - /* TODO: lazy lookup of lowerdata */ + } if (!realpath.dentry) return -EIO; @@ -153,6 +159,11 @@ static int ovl_open(struct inode *inode, struct file *file) struct path realpath; int err; + /* lazy lookup of lowerdata */ + err = ovl_maybe_lookup_lowerdata(dentry); + if (err) + return err; + err = ovl_maybe_copy_up(dentry, file->f_flags); if (err) return err; @@ -161,7 +172,6 @@ static int ovl_open(struct inode *inode, struct file *file) file->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); ovl_path_realdata(dentry, &realpath); - /* TODO: lazy lookup of lowerdata */ if (!realpath.dentry) return -EIO; diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 605108f0dbe9..b3b40bc9a5ab 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -889,6 +889,52 @@ static int ovl_fix_origin(struct ovl_fs *ofs, struct dentry *dentry, return err; } +/* Lazy lookup of lowerdata */ +int ovl_maybe_lookup_lowerdata(struct dentry *dentry) +{ + struct inode *inode = d_inode(dentry); + const char *redirect = ovl_lowerdata_redirect(inode); + struct ovl_path datapath = {}; + const struct cred *old_cred; + int err; + + if (!redirect || ovl_dentry_lowerdata(dentry)) + return 0; + + if (redirect[0] != '/') + return -EIO; + + err = ovl_inode_lock_interruptible(inode); + if (err) + return err; + + err = 0; + /* Someone got here before us? */ + if (ovl_dentry_lowerdata(dentry)) + goto out; + + old_cred = ovl_override_creds(dentry->d_sb); + err = ovl_lookup_data_layers(dentry, redirect, &datapath); + revert_creds(old_cred); + if (err) + goto out_err; + + err = ovl_dentry_set_lowerdata(dentry, &datapath); + if (err) + goto out_err; + +out: + ovl_inode_unlock(inode); + dput(datapath.dentry); + + return err; + +out_err: + pr_warn_ratelimited("lazy lowerdata lookup failed (%pd2, err=%i)\n", + dentry, err); + goto out; +} + struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { @@ -1072,14 +1118,10 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } } - /* Lookup absolute redirect from lower metacopy in data-only layers */ + /* Defer lookup of lowerdata in data-only layers to first access */ if (d.metacopy && ctr && ofs->numdatalayer && d.absolute_redirect) { - err = ovl_lookup_data_layers(dentry, d.redirect, - &stack[ctr]); - if (!err) { - d.metacopy = false; - ctr++; - } + d.metacopy = false; + ctr++; } /* diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 5cc03b8d7b25..fcac4e2c56ab 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -396,6 +396,7 @@ enum ovl_path_type ovl_path_realdata(struct dentry *dentry, struct path *path); struct dentry *ovl_dentry_upper(struct dentry *dentry); struct dentry *ovl_dentry_lower(struct dentry *dentry); struct dentry *ovl_dentry_lowerdata(struct dentry *dentry); +int ovl_dentry_set_lowerdata(struct dentry *dentry, struct ovl_path *datapath); const struct ovl_layer *ovl_i_layer_lower(struct inode *inode); const struct ovl_layer *ovl_layer_lower(struct dentry *dentry); struct dentry *ovl_dentry_real(struct dentry *dentry); @@ -558,6 +559,7 @@ struct dentry *ovl_get_index_fh(struct ovl_fs *ofs, struct ovl_fh *fh); struct dentry *ovl_lookup_index(struct ovl_fs *ofs, struct dentry *upper, struct dentry *origin, bool verify); int ovl_path_next(int idx, struct dentry *dentry, struct path *path); +int ovl_maybe_lookup_lowerdata(struct dentry *dentry); struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); bool ovl_lower_positive(struct dentry *dentry); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 513c2c499e41..c6c7d09b494e 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -146,7 +146,7 @@ static inline struct dentry *ovl_lowerdata_dentry(struct ovl_entry *oe) { struct ovl_path *lowerdata = ovl_lowerdata(oe); - return lowerdata ? lowerdata->dentry : NULL; + return lowerdata ? READ_ONCE(lowerdata->dentry) : NULL; } /* private information held for every overlayfs dentry */ diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index f3e5b43c4106..14a2ebdc8126 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -82,13 +82,14 @@ static struct dentry *ovl_d_real(struct dentry *dentry, return real; /* - * XXX: We may need lazy lookup of lowerdata for !inode case to return + * Best effort lazy lookup of lowerdata for !inode case to return * the real lowerdata dentry. The only current caller of d_real() with * NULL inode is d_real_inode() from trace_uprobe and this caller is * likely going to be followed reading from the file, before placing * uprobes on offset within the file, so lowerdata should be available * when setting the uprobe. */ + ovl_maybe_lookup_lowerdata(dentry); lower = ovl_dentry_lowerdata(dentry); if (!lower) goto bug; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 23c0224779be..939e4d586ec2 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -229,8 +229,14 @@ void ovl_path_lowerdata(struct dentry *dentry, struct path *path) struct dentry *lowerdata_dentry = ovl_lowerdata_dentry(oe); if (lowerdata_dentry) { - path->mnt = lowerdata->layer->mnt; path->dentry = lowerdata_dentry; + /* + * Pairs with smp_wmb() in ovl_dentry_set_lowerdata(). + * Make sure that if lowerdata->dentry is visible, then + * datapath->layer is visible as well. + */ + smp_rmb(); + path->mnt = READ_ONCE(lowerdata->layer)->mnt; } else { *path = (struct path) { }; } @@ -292,6 +298,29 @@ struct dentry *ovl_dentry_lowerdata(struct dentry *dentry) return ovl_lowerdata_dentry(OVL_E(dentry)); } +int ovl_dentry_set_lowerdata(struct dentry *dentry, struct ovl_path *datapath) +{ + struct ovl_entry *oe = OVL_E(dentry); + struct ovl_path *lowerdata = ovl_lowerdata(oe); + struct dentry *datadentry = datapath->dentry; + + if (WARN_ON_ONCE(ovl_numlower(oe) <= 1)) + return -EIO; + + WRITE_ONCE(lowerdata->layer, datapath->layer); + /* + * Pairs with smp_rmb() in ovl_path_lowerdata(). + * Make sure that if lowerdata->dentry is visible, then + * lowerdata->layer is visible as well. + */ + smp_wmb(); + WRITE_ONCE(lowerdata->dentry, dget(datadentry)); + + ovl_dentry_update_reval(dentry, datadentry); + + return 0; +} + struct dentry *ovl_dentry_real(struct dentry *dentry) { return ovl_dentry_upper(dentry) ?: ovl_dentry_lower(dentry); -- cgit v1.2.3-58-ga151 From f723edb8a532cd26e1ff0a2b271d73762d48f762 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 13 Jun 2023 10:13:37 +0200 Subject: ovl: check type and offset of struct vfsmount in ovl_entry Porting overlayfs to the new amount api I started experiencing random crashes that couldn't be explained easily. So after much debugging and reasoning it became clear that struct ovl_entry requires the point to struct vfsmount to be the first member and of type struct vfsmount. During the port I added a new member at the beginning of struct ovl_entry which broke all over the place in the form of random crashes and cache corruptions. While there's a comment in ovl_free_fs() to the effect of "Hack! Reuse ofs->layers as a vfsmount array before freeing it" there's no such comment on struct ovl_entry which makes this easy to trip over. Add a comment and two static asserts for both the offset and the type of pointer in struct ovl_entry. Signed-off-by: Christian Brauner Signed-off-by: Amir Goldstein --- fs/overlayfs/ovl_entry.h | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index c6c7d09b494e..e5207c4bf5b8 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -32,6 +32,7 @@ struct ovl_sb { }; struct ovl_layer { + /* ovl_free_fs() relies on @mnt being the first member! */ struct vfsmount *mnt; /* Trap in ovl inode cache */ struct inode *trap; @@ -42,6 +43,14 @@ struct ovl_layer { int fsid; }; +/* + * ovl_free_fs() relies on @mnt being the first member when unmounting + * the private mounts created for each layer. Let's check both the + * offset and type. + */ +static_assert(offsetof(struct ovl_layer, mnt) == 0); +static_assert(__same_type(typeof_member(struct ovl_layer, mnt), struct vfsmount *)); + struct ovl_path { const struct ovl_layer *layer; struct dentry *dentry; -- cgit v1.2.3-58-ga151 From e4599d4b1aeff031d5764b25b37100b4f98148fc Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sat, 17 Jun 2023 09:12:50 +0300 Subject: ovl: negate the ofs->share_whiteout boolean The default common case is that whiteout sharing is enabled. Change to storing the negated no_shared_whiteout state, so we will not need to initialize it. This is the first step towards removing all config and feature initializations out of ovl_fill_super(). Signed-off-by: Amir Goldstein --- fs/overlayfs/dir.c | 4 ++-- fs/overlayfs/ovl_entry.h | 4 ++-- fs/overlayfs/super.c | 3 --- 3 files changed, 4 insertions(+), 7 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 92bdcedfaaec..0da45727099b 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -83,7 +83,7 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs) ofs->whiteout = whiteout; } - if (ofs->share_whiteout) { + if (!ofs->no_shared_whiteout) { whiteout = ovl_lookup_temp(ofs, workdir); if (IS_ERR(whiteout)) goto out; @@ -95,7 +95,7 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs) if (err != -EMLINK) { pr_warn("Failed to link whiteout - disabling whiteout inode sharing(nlink=%u, err=%i)\n", ofs->whiteout->d_inode->i_nlink, err); - ofs->share_whiteout = false; + ofs->no_shared_whiteout = true; } dput(whiteout); } diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index e5207c4bf5b8..40232b056be8 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -86,7 +86,6 @@ struct ovl_fs { /* Did we take the inuse lock? */ bool upperdir_locked; bool workdir_locked; - bool share_whiteout; /* Traps in ovl inode cache */ struct inode *workbasedir_trap; struct inode *workdir_trap; @@ -95,8 +94,9 @@ struct ovl_fs { int xino_mode; /* For allocation of non-persistent inode numbers */ atomic_long_t last_ino; - /* Whiteout dentry cache */ + /* Shared whiteout cache */ struct dentry *whiteout; + bool no_shared_whiteout; /* r/o snapshot of upperdir sb's only taken on volatile mounts */ errseq_t errseq; }; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 14a2ebdc8126..ee9adb413d0e 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1954,9 +1954,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (!cred) goto out_err; - /* Is there a reason anyone would want not to share whiteouts? */ - ofs->share_whiteout = true; - ofs->config.index = ovl_index_def; ofs->config.uuid = true; ofs->config.nfs_export = ovl_nfs_export_def; -- cgit v1.2.3-58-ga151 From 367d002d6cd21b8f0afe710cd9276fffe7b56d69 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sat, 17 Jun 2023 11:00:24 +0300 Subject: ovl: clarify ovl_get_root() semantics Change the semantics to take a reference on upperdentry instead of transferrig the reference. This is needed for upcoming port to new mount api. Signed-off-by: Amir Goldstein --- fs/overlayfs/super.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index ee9adb413d0e..280f2aa2f356 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1922,6 +1922,8 @@ static struct dentry *ovl_get_root(struct super_block *sb, ovl_set_upperdata(d_inode(root)); ovl_inode_init(d_inode(root), &oip, ino, fsid); ovl_dentry_init_flags(root, upperdentry, oe, DCACHE_OP_WEAK_REVALIDATE); + /* root keeps a reference of upperdentry */ + dget(upperdentry); return root; } @@ -2100,7 +2102,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (!root_dentry) goto out_free_oe; - mntput(upperpath.mnt); + path_put(&upperpath); kfree(splitlower); sb->s_root = root_dentry; -- cgit v1.2.3-58-ga151 From dcb399de1e404f48b07ebdcc434ec600568c90eb Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Thu, 25 May 2023 08:40:54 +0300 Subject: ovl: pass ovl_fs to xino helpers Internal ovl methods should use ovl_fs and not sb as much as possible. Use a constant_table to translate from enum xino mode to string in preperation for new mount api option parsing. Signed-off-by: Amir Goldstein --- fs/overlayfs/inode.c | 18 ++++++++++-------- fs/overlayfs/overlayfs.h | 16 ++++++++-------- fs/overlayfs/readdir.c | 19 +++++++++++-------- fs/overlayfs/super.c | 21 ++++++++++++++------- 4 files changed, 43 insertions(+), 31 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index bf47a0a94d1e..a63e57447be9 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -97,8 +97,9 @@ out: static void ovl_map_dev_ino(struct dentry *dentry, struct kstat *stat, int fsid) { - bool samefs = ovl_same_fs(dentry->d_sb); - unsigned int xinobits = ovl_xino_bits(dentry->d_sb); + struct ovl_fs *ofs = OVL_FS(dentry->d_sb); + bool samefs = ovl_same_fs(ofs); + unsigned int xinobits = ovl_xino_bits(ofs); unsigned int xinoshift = 64 - xinobits; if (samefs) { @@ -123,7 +124,7 @@ static void ovl_map_dev_ino(struct dentry *dentry, struct kstat *stat, int fsid) stat->ino |= ((u64)fsid) << (xinoshift + 1); stat->dev = dentry->d_sb->s_dev; return; - } else if (ovl_xino_warn(dentry->d_sb)) { + } else if (ovl_xino_warn(ofs)) { pr_warn_ratelimited("inode number too big (%pd2, ino=%llu, xinobits=%d)\n", dentry, stat->ino, xinobits); } @@ -149,7 +150,7 @@ static void ovl_map_dev_ino(struct dentry *dentry, struct kstat *stat, int fsid) * is unique per underlying fs, so we use the unique anonymous * bdev assigned to the underlying fs. */ - stat->dev = OVL_FS(dentry->d_sb)->fs[fsid].pseudo_dev; + stat->dev = ofs->fs[fsid].pseudo_dev; } } @@ -186,7 +187,7 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, * If lower filesystem supports NFS file handles, this also guaranties * persistent st_ino across mount cycle. */ - if (!is_dir || ovl_same_dev(dentry->d_sb)) { + if (!is_dir || ovl_same_dev(OVL_FS(dentry->d_sb))) { if (!OVL_TYPE_UPPER(type)) { fsid = ovl_layer_lower(dentry)->fsid; } else if (OVL_TYPE_ORIGIN(type)) { @@ -961,7 +962,7 @@ static inline void ovl_lockdep_annotate_inode_mutex_key(struct inode *inode) static void ovl_next_ino(struct inode *inode) { - struct ovl_fs *ofs = inode->i_sb->s_fs_info; + struct ovl_fs *ofs = OVL_FS(inode->i_sb); inode->i_ino = atomic_long_inc_return(&ofs->last_ino); if (unlikely(!inode->i_ino)) @@ -970,7 +971,8 @@ static void ovl_next_ino(struct inode *inode) static void ovl_map_ino(struct inode *inode, unsigned long ino, int fsid) { - int xinobits = ovl_xino_bits(inode->i_sb); + struct ovl_fs *ofs = OVL_FS(inode->i_sb); + int xinobits = ovl_xino_bits(ofs); unsigned int xinoshift = 64 - xinobits; /* @@ -981,7 +983,7 @@ static void ovl_map_ino(struct inode *inode, unsigned long ino, int fsid) * with d_ino also causes nfsd readdirplus to fail. */ inode->i_ino = ino; - if (ovl_same_fs(inode->i_sb)) { + if (ovl_same_fs(ofs)) { return; } else if (xinobits && likely(!(ino >> xinoshift))) { inode->i_ino |= (unsigned long)fsid << (xinoshift + 1); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index fcac4e2c56ab..05e9acfe1590 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -494,26 +494,26 @@ static inline bool ovl_is_impuredir(struct super_block *sb, * d_ino consistent with st_ino. * With xino=on, we do the same effort but we warn if we failed. */ -static inline bool ovl_xino_warn(struct super_block *sb) +static inline bool ovl_xino_warn(struct ovl_fs *ofs) { - return OVL_FS(sb)->config.xino == OVL_XINO_ON; + return ofs->config.xino == OVL_XINO_ON; } /* All layers on same fs? */ -static inline bool ovl_same_fs(struct super_block *sb) +static inline bool ovl_same_fs(struct ovl_fs *ofs) { - return OVL_FS(sb)->xino_mode == 0; + return ofs->xino_mode == 0; } /* All overlay inodes have same st_dev? */ -static inline bool ovl_same_dev(struct super_block *sb) +static inline bool ovl_same_dev(struct ovl_fs *ofs) { - return OVL_FS(sb)->xino_mode >= 0; + return ofs->xino_mode >= 0; } -static inline unsigned int ovl_xino_bits(struct super_block *sb) +static inline unsigned int ovl_xino_bits(struct ovl_fs *ofs) { - return ovl_same_dev(sb) ? OVL_FS(sb)->xino_mode : 0; + return ovl_same_dev(ofs) ? ofs->xino_mode : 0; } static inline void ovl_inode_lock(struct inode *inode) diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index b6952b21a7ee..ee5c4736480f 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -118,7 +118,7 @@ static bool ovl_calc_d_ino(struct ovl_readdir_data *rdd, return false; /* Always recalc d_ino when remapping lower inode numbers */ - if (ovl_xino_bits(rdd->dentry->d_sb)) + if (ovl_xino_bits(OVL_FS(rdd->dentry->d_sb))) return true; /* Always recalc d_ino for parent */ @@ -460,13 +460,14 @@ static int ovl_cache_update_ino(const struct path *path, struct ovl_cache_entry { struct dentry *dir = path->dentry; + struct ovl_fs *ofs = OVL_FS(dir->d_sb); struct dentry *this = NULL; enum ovl_path_type type; u64 ino = p->real_ino; - int xinobits = ovl_xino_bits(dir->d_sb); + int xinobits = ovl_xino_bits(ofs); int err = 0; - if (!ovl_same_dev(dir->d_sb)) + if (!ovl_same_dev(ofs)) goto out; if (p->name[0] == '.') { @@ -515,7 +516,7 @@ get: ino = ovl_remap_lower_ino(ino, xinobits, ovl_layer_lower(this)->fsid, p->name, p->len, - ovl_xino_warn(dir->d_sb)); + ovl_xino_warn(ofs)); } out: @@ -694,12 +695,13 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx) int err; struct ovl_dir_file *od = file->private_data; struct dentry *dir = file->f_path.dentry; + struct ovl_fs *ofs = OVL_FS(dir->d_sb); const struct ovl_layer *lower_layer = ovl_layer_lower(dir); struct ovl_readdir_translate rdt = { .ctx.actor = ovl_fill_real, .orig_ctx = ctx, - .xinobits = ovl_xino_bits(dir->d_sb), - .xinowarn = ovl_xino_warn(dir->d_sb), + .xinobits = ovl_xino_bits(ofs), + .xinowarn = ovl_xino_warn(ofs), }; if (rdt.xinobits && lower_layer) @@ -735,6 +737,7 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx) { struct ovl_dir_file *od = file->private_data; struct dentry *dentry = file->f_path.dentry; + struct ovl_fs *ofs = OVL_FS(dentry->d_sb); struct ovl_cache_entry *p; const struct cred *old_cred; int err; @@ -749,8 +752,8 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx) * dir is impure then need to adjust d_ino for copied up * entries. */ - if (ovl_xino_bits(dentry->d_sb) || - (ovl_same_fs(dentry->d_sb) && + if (ovl_xino_bits(ofs) || + (ovl_same_fs(ofs) && (ovl_is_impure_dir(file) || OVL_TYPE_MERGE(ovl_path_type(dentry->d_parent))))) { err = ovl_iterate_real(file, ctx); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 280f2aa2f356..5bcb26528408 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "overlayfs.h" MODULE_AUTHOR("Miklos Szeredi "); @@ -334,12 +335,18 @@ static const char *ovl_redirect_mode_def(void) return ovl_redirect_dir_def ? "on" : "off"; } -static const char * const ovl_xino_str[] = { - "off", - "auto", - "on", +static const struct constant_table ovl_parameter_xino[] = { + { "off", OVL_XINO_OFF }, + { "auto", OVL_XINO_AUTO }, + { "on", OVL_XINO_ON }, + {} }; +static const char *ovl_xino_mode(struct ovl_config *config) +{ + return ovl_parameter_xino[config->xino].name; +} + static inline int ovl_xino_def(void) { return ovl_xino_auto_def ? OVL_XINO_AUTO : OVL_XINO_OFF; @@ -374,8 +381,8 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) if (ofs->config.nfs_export != ovl_nfs_export_def) seq_printf(m, ",nfs_export=%s", ofs->config.nfs_export ? "on" : "off"); - if (ofs->config.xino != ovl_xino_def() && !ovl_same_fs(sb)) - seq_printf(m, ",xino=%s", ovl_xino_str[ofs->config.xino]); + if (ofs->config.xino != ovl_xino_def() && !ovl_same_fs(ofs)) + seq_printf(m, ",xino=%s", ovl_xino_mode(&ofs->config)); if (ofs->config.metacopy != ovl_metacopy_def) seq_printf(m, ",metacopy=%s", ofs->config.metacopy ? "on" : "off"); @@ -1566,7 +1573,7 @@ static int ovl_get_fsid(struct ovl_fs *ofs, const struct path *path) pr_warn("%s uuid detected in lower fs '%pd2', falling back to xino=%s,index=off,nfs_export=off.\n", uuid_is_null(&sb->s_uuid) ? "null" : "conflicting", - path->dentry, ovl_xino_str[ofs->config.xino]); + path->dentry, ovl_xino_mode(&ofs->config)); } } -- cgit v1.2.3-58-ga151 From af5f2396b671c9857b113584d4bd43413b39cfdb Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sat, 17 Jun 2023 09:42:36 +0300 Subject: ovl: store enum redirect_mode in config instead of a string Do all the logic to set the mode during mount options parsing and do not keep the option string around. Use a constant_table to translate from enum redirect mode to string in preperation for new mount api option parsing. The mount option "off" is translated to either "follow" or "nofollow", depending on the "redirect_always_follow" build/module config, so in effect, there are only three possible redirect modes. This results in a minor change to the string that is displayed in show_options() - when redirect_dir is enabled by default and the user mounts with the option "redirect_dir=off", instead of displaying the mode "redirect_dir=off" in show_options(), the displayed mode will be either "redirect_dir=follow" or "redirect_dir=nofollow", depending on the value of "redirect_always_follow" build/module config. The displayed mode reflects the effective mode, so mounting overlayfs again with the dispalyed redirect_dir option will result with the same effective and displayed mode. Signed-off-by: Amir Goldstein --- Documentation/filesystems/overlayfs.rst | 9 +-- fs/overlayfs/dir.c | 2 +- fs/overlayfs/namei.c | 6 +- fs/overlayfs/overlayfs.h | 39 +++++++--- fs/overlayfs/ovl_entry.h | 4 +- fs/overlayfs/super.c | 129 ++++++++++++++++++-------------- fs/overlayfs/util.c | 7 -- 7 files changed, 107 insertions(+), 89 deletions(-) (limited to 'fs/overlayfs') diff --git a/Documentation/filesystems/overlayfs.rst b/Documentation/filesystems/overlayfs.rst index 4f36b8919f7c..eb7d2c88ddec 100644 --- a/Documentation/filesystems/overlayfs.rst +++ b/Documentation/filesystems/overlayfs.rst @@ -231,12 +231,11 @@ Mount options: Redirects are enabled. - "redirect_dir=follow": Redirects are not created, but followed. -- "redirect_dir=off": - Redirects are not created and only followed if "redirect_always_follow" - feature is enabled in the kernel/module config. - "redirect_dir=nofollow": - Redirects are not created and not followed (equivalent to "redirect_dir=off" - if "redirect_always_follow" feature is not enabled). + Redirects are not created and not followed. +- "redirect_dir=off": + If "redirect_always_follow" is enabled in the kernel/module config, + this "off" traslates to "follow", otherwise it translates to "nofollow". 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 diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 0da45727099b..033fc0458a3d 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -952,7 +952,7 @@ static bool ovl_type_merge_or_lower(struct dentry *dentry) static bool ovl_can_move(struct dentry *dentry) { - return ovl_redirect_dir(dentry->d_sb) || + return ovl_redirect_dir(OVL_FS(dentry->d_sb)) || !d_is_dir(dentry) || !ovl_type_merge_or_lower(dentry); } diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index b3b40bc9a5ab..57adf911735f 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -961,7 +961,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, .is_dir = false, .opaque = false, .stop = false, - .last = ofs->config.redirect_follow ? false : !ovl_numlower(poe), + .last = ovl_redirect_follow(ofs) ? false : !ovl_numlower(poe), .redirect = NULL, .metacopy = false, }; @@ -1022,7 +1022,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, for (i = 0; !d.stop && i < ovl_numlower(poe); i++) { struct ovl_path lower = ovl_lowerstack(poe)[i]; - if (!ofs->config.redirect_follow) + if (!ovl_redirect_follow(ofs)) d.last = i == ovl_numlower(poe) - 1; else if (d.is_dir || !ofs->numdatalayer) d.last = lower.layer->idx == ovl_numlower(roe); @@ -1102,7 +1102,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, * this attack vector when not necessary. */ err = -EPERM; - if (d.redirect && !ofs->config.redirect_follow) { + if (d.redirect && !ovl_redirect_follow(ofs)) { pr_warn_ratelimited("refusing to follow redirect for (%pd2)\n", dentry); goto out_put; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 05e9acfe1590..80c10228bd64 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -57,6 +57,13 @@ enum ovl_entry_flag { OVL_E_CONNECTED, }; +enum { + OVL_REDIRECT_OFF, /* "off" mode is never used. In effect */ + OVL_REDIRECT_FOLLOW, /* ...it translates to either "follow" */ + OVL_REDIRECT_NOFOLLOW, /* ...or "nofollow". */ + OVL_REDIRECT_ON, +}; + enum { OVL_XINO_OFF, OVL_XINO_AUTO, @@ -352,17 +359,6 @@ static inline bool ovl_open_flags_need_copy_up(int flags) return ((OPEN_FMODE(flags) & FMODE_WRITE) || (flags & O_TRUNC)); } -static inline bool ovl_allow_offline_changes(struct ovl_fs *ofs) -{ - /* - * To avoid regressions in existing setups with overlay lower offline - * changes, we allow lower changes only if none of the new features - * are used. - */ - return (!ofs->config.index && !ofs->config.metacopy && - !ofs->config.redirect_dir && ofs->config.xino != OVL_XINO_ON); -} - /* util.c */ int ovl_want_write(struct dentry *dentry); @@ -421,7 +417,6 @@ bool ovl_dentry_needs_data_copy_up(struct dentry *dentry, int flags); bool ovl_dentry_needs_data_copy_up_locked(struct dentry *dentry, int flags); bool ovl_has_upperdata(struct inode *inode); void ovl_set_upperdata(struct inode *inode); -bool ovl_redirect_dir(struct super_block *sb); const char *ovl_dentry_get_redirect(struct dentry *dentry); void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect); void ovl_inode_update(struct inode *inode, struct dentry *upperdentry); @@ -489,6 +484,16 @@ static inline bool ovl_is_impuredir(struct super_block *sb, return ovl_path_check_dir_xattr(ofs, &upperpath, OVL_XATTR_IMPURE); } +static inline bool ovl_redirect_follow(struct ovl_fs *ofs) +{ + return ofs->config.redirect_mode != OVL_REDIRECT_NOFOLLOW; +} + +static inline bool ovl_redirect_dir(struct ovl_fs *ofs) +{ + return ofs->config.redirect_mode == OVL_REDIRECT_ON; +} + /* * With xino=auto, we do best effort to keep all inodes on same st_dev and * d_ino consistent with st_ino. @@ -499,6 +504,16 @@ static inline bool ovl_xino_warn(struct ovl_fs *ofs) return ofs->config.xino == OVL_XINO_ON; } +/* + * To avoid regressions in existing setups with overlay lower offline changes, + * we allow lower changes only if none of the new features are used. + */ +static inline bool ovl_allow_offline_changes(struct ovl_fs *ofs) +{ + return (!ofs->config.index && !ofs->config.metacopy && + !ovl_redirect_dir(ofs) && !ovl_xino_warn(ofs)); +} + /* All layers on same fs? */ static inline bool ovl_same_fs(struct ovl_fs *ofs) { diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 40232b056be8..b4eb0bd5d0b6 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -10,9 +10,7 @@ struct ovl_config { char *upperdir; char *workdir; bool default_permissions; - bool redirect_dir; - bool redirect_follow; - const char *redirect_mode; + int redirect_mode; bool index; bool uuid; bool nfs_export; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 5bcb26528408..5a84af92c91e 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -244,7 +244,6 @@ static void ovl_free_fs(struct ovl_fs *ofs) kfree(ofs->config.lowerdir); kfree(ofs->config.upperdir); kfree(ofs->config.workdir); - kfree(ofs->config.redirect_mode); if (ofs->creator_cred) put_cred(ofs->creator_cred); kfree(ofs); @@ -330,9 +329,24 @@ static bool ovl_force_readonly(struct ovl_fs *ofs) return (!ovl_upper_mnt(ofs) || !ofs->workdir); } -static const char *ovl_redirect_mode_def(void) +static const struct constant_table ovl_parameter_redirect_dir[] = { + { "off", OVL_REDIRECT_OFF }, + { "follow", OVL_REDIRECT_FOLLOW }, + { "nofollow", OVL_REDIRECT_NOFOLLOW }, + { "on", OVL_REDIRECT_ON }, + {} +}; + +static const char *ovl_redirect_mode(struct ovl_config *config) { - return ovl_redirect_dir_def ? "on" : "off"; + return ovl_parameter_redirect_dir[config->redirect_mode].name; +} + +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; } static const struct constant_table ovl_parameter_xino[] = { @@ -372,8 +386,9 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) } if (ofs->config.default_permissions) seq_puts(m, ",default_permissions"); - if (strcmp(ofs->config.redirect_mode, ovl_redirect_mode_def()) != 0) - seq_printf(m, ",redirect_dir=%s", ofs->config.redirect_mode); + if (ofs->config.redirect_mode != ovl_redirect_mode_def()) + seq_printf(m, ",redirect_dir=%s", + ovl_redirect_mode(&ofs->config)); if (ofs->config.index != ovl_index_def) seq_printf(m, ",index=%s", ofs->config.index ? "on" : "off"); if (!ofs->config.uuid) @@ -431,7 +446,10 @@ enum { OPT_UPPERDIR, OPT_WORKDIR, OPT_DEFAULT_PERMISSIONS, - OPT_REDIRECT_DIR, + OPT_REDIRECT_DIR_ON, + OPT_REDIRECT_DIR_OFF, + OPT_REDIRECT_DIR_FOLLOW, + OPT_REDIRECT_DIR_NOFOLLOW, OPT_INDEX_ON, OPT_INDEX_OFF, OPT_UUID_ON, @@ -453,7 +471,10 @@ static const match_table_t ovl_tokens = { {OPT_UPPERDIR, "upperdir=%s"}, {OPT_WORKDIR, "workdir=%s"}, {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, - {OPT_REDIRECT_DIR, "redirect_dir=%s"}, + {OPT_REDIRECT_DIR_ON, "redirect_dir=on"}, + {OPT_REDIRECT_DIR_OFF, "redirect_dir=off"}, + {OPT_REDIRECT_DIR_FOLLOW, "redirect_dir=follow"}, + {OPT_REDIRECT_DIR_NOFOLLOW, "redirect_dir=nofollow"}, {OPT_INDEX_ON, "index=on"}, {OPT_INDEX_OFF, "index=off"}, {OPT_USERXATTR, "userxattr"}, @@ -493,40 +514,12 @@ static char *ovl_next_opt(char **s) return sbegin; } -static int ovl_parse_redirect_mode(struct ovl_config *config, const char *mode) -{ - if (strcmp(mode, "on") == 0) { - config->redirect_dir = true; - /* - * Does not make sense to have redirect creation without - * redirect following. - */ - config->redirect_follow = true; - } else if (strcmp(mode, "follow") == 0) { - config->redirect_follow = true; - } else if (strcmp(mode, "off") == 0) { - if (ovl_redirect_always_follow) - config->redirect_follow = true; - } else if (strcmp(mode, "nofollow") != 0) { - pr_err("bad mount option \"redirect_dir=%s\"\n", - mode); - return -EINVAL; - } - - return 0; -} - static int ovl_parse_opt(char *opt, struct ovl_config *config) { char *p; - int err; bool metacopy_opt = false, redirect_opt = false; bool nfs_export_opt = false, index_opt = false; - config->redirect_mode = kstrdup(ovl_redirect_mode_def(), GFP_KERNEL); - if (!config->redirect_mode) - return -ENOMEM; - while ((p = ovl_next_opt(&opt)) != NULL) { int token; substring_t args[MAX_OPT_ARGS]; @@ -561,11 +554,25 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) config->default_permissions = true; break; - case OPT_REDIRECT_DIR: - kfree(config->redirect_mode); - config->redirect_mode = match_strdup(&args[0]); - if (!config->redirect_mode) - return -ENOMEM; + case OPT_REDIRECT_DIR_ON: + config->redirect_mode = OVL_REDIRECT_ON; + redirect_opt = true; + break; + + case OPT_REDIRECT_DIR_OFF: + config->redirect_mode = ovl_redirect_always_follow ? + OVL_REDIRECT_FOLLOW : + OVL_REDIRECT_NOFOLLOW; + redirect_opt = true; + break; + + case OPT_REDIRECT_DIR_FOLLOW: + config->redirect_mode = OVL_REDIRECT_FOLLOW; + redirect_opt = true; + break; + + case OPT_REDIRECT_DIR_NOFOLLOW: + config->redirect_mode = OVL_REDIRECT_NOFOLLOW; redirect_opt = true; break; @@ -654,22 +661,18 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) config->ovl_volatile = false; } - err = ovl_parse_redirect_mode(config, config->redirect_mode); - if (err) - return err; - /* * This is to make the logic below simpler. It doesn't make any other - * difference, since config->redirect_dir is only used for upper. + * difference, since redirect_dir=on is only used for upper. */ - if (!config->upperdir && config->redirect_follow) - config->redirect_dir = true; + if (!config->upperdir && config->redirect_mode == OVL_REDIRECT_FOLLOW) + config->redirect_mode = OVL_REDIRECT_ON; /* Resolve metacopy -> redirect_dir dependency */ - if (config->metacopy && !config->redirect_dir) { + if (config->metacopy && config->redirect_mode != OVL_REDIRECT_ON) { if (metacopy_opt && redirect_opt) { pr_err("conflicting options: metacopy=on,redirect_dir=%s\n", - config->redirect_mode); + ovl_redirect_mode(config)); return -EINVAL; } if (redirect_opt) { @@ -678,17 +681,18 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) * in this conflict. */ pr_info("disabling metacopy due to redirect_dir=%s\n", - config->redirect_mode); + ovl_redirect_mode(config)); config->metacopy = false; } else { /* Automatically enable redirect otherwise. */ - config->redirect_follow = config->redirect_dir = true; + config->redirect_mode = OVL_REDIRECT_ON; } } /* Resolve nfs_export -> index dependency */ if (config->nfs_export && !config->index) { - if (!config->upperdir && config->redirect_follow) { + if (!config->upperdir && + config->redirect_mode != OVL_REDIRECT_NOFOLLOW) { pr_info("NFS export requires \"redirect_dir=nofollow\" on non-upper mount, falling back to nfs_export=off.\n"); config->nfs_export = false; } else if (nfs_export_opt && index_opt) { @@ -733,9 +737,10 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) /* Resolve userxattr -> !redirect && !metacopy dependency */ if (config->userxattr) { - if (config->redirect_follow && redirect_opt) { + if (redirect_opt && + config->redirect_mode != OVL_REDIRECT_NOFOLLOW) { pr_err("conflicting options: userxattr,redirect_dir=%s\n", - config->redirect_mode); + ovl_redirect_mode(config)); return -EINVAL; } if (config->metacopy && metacopy_opt) { @@ -748,7 +753,7 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) * options must be explicitly enabled if used together with * userxattr. */ - config->redirect_dir = config->redirect_follow = false; + config->redirect_mode = OVL_REDIRECT_NOFOLLOW; config->metacopy = false; } @@ -1332,10 +1337,17 @@ 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 (ofs->config.index || ofs->config.metacopy) { - ofs->config.index = false; + if (ovl_redirect_follow(ofs)) { + ofs->config.redirect_mode = OVL_REDIRECT_NOFOLLOW; + pr_warn("...falling back to redirect_dir=nofollow.\n"); + } + if (ofs->config.metacopy) { ofs->config.metacopy = false; - pr_warn("...falling back to index=off,metacopy=off.\n"); + pr_warn("...falling back to metacopy=off.\n"); + } + if (ofs->config.index) { + ofs->config.index = false; + pr_warn("...falling back to index=off.\n"); } /* * xattr support is required for persistent st_ino. @@ -1963,6 +1975,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (!cred) goto out_err; + ofs->config.redirect_mode = ovl_redirect_mode_def(); ofs->config.index = ovl_index_def; ofs->config.uuid = true; ofs->config.nfs_export = ovl_nfs_export_def; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 939e4d586ec2..7ef9e13c404a 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -506,13 +506,6 @@ bool ovl_dentry_needs_data_copy_up(struct dentry *dentry, int flags) return !ovl_has_upperdata(d_inode(dentry)); } -bool ovl_redirect_dir(struct super_block *sb) -{ - struct ovl_fs *ofs = sb->s_fs_info; - - return ofs->config.redirect_dir && !ofs->noxattr; -} - const char *ovl_dentry_get_redirect(struct dentry *dentry) { return OVL_I(d_inode(dentry))->redirect; -- cgit v1.2.3-58-ga151 From ac519625edf2004fd34f6deec3110b154f621780 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sat, 17 Jun 2023 09:05:52 +0300 Subject: ovl: factor out ovl_parse_options() helper For parsing a single mount option. Signed-off-by: Amir Goldstein --- fs/overlayfs/overlayfs.h | 8 ++ fs/overlayfs/super.c | 243 +++++++++++++++++++++++++---------------------- 2 files changed, 135 insertions(+), 116 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 80c10228bd64..30227ccc758d 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -70,6 +70,14 @@ enum { OVL_XINO_ON, }; +/* The set of options that user requested explicitly via mount options */ +struct ovl_opt_set { + bool metacopy; + bool redirect; + bool nfs_export; + bool index; +}; + /* * The tuple (fh,uuid) is a universal unique identifier for a copy up origin, * where: diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 5a84af92c91e..dbd9151dc073 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -514,131 +514,142 @@ static char *ovl_next_opt(char **s) return sbegin; } -static int ovl_parse_opt(char *opt, struct ovl_config *config) +static int ovl_parse_opt(char *opt, struct ovl_config *config, + struct ovl_opt_set *set) { - char *p; - bool metacopy_opt = false, redirect_opt = false; - bool nfs_export_opt = false, index_opt = false; - - while ((p = ovl_next_opt(&opt)) != NULL) { - int token; - substring_t args[MAX_OPT_ARGS]; - - if (!*p) - continue; + int err = 0; + int token; + substring_t args[MAX_OPT_ARGS]; - token = match_token(p, ovl_tokens, args); - switch (token) { - case OPT_UPPERDIR: - kfree(config->upperdir); - config->upperdir = match_strdup(&args[0]); - if (!config->upperdir) - return -ENOMEM; - break; + if (!*opt) + return 0; - case OPT_LOWERDIR: - kfree(config->lowerdir); - config->lowerdir = match_strdup(&args[0]); - if (!config->lowerdir) - return -ENOMEM; - break; + token = match_token(opt, ovl_tokens, args); + switch (token) { + case OPT_UPPERDIR: + kfree(config->upperdir); + config->upperdir = match_strdup(&args[0]); + if (!config->upperdir) + return -ENOMEM; + break; + + case OPT_LOWERDIR: + kfree(config->lowerdir); + config->lowerdir = match_strdup(&args[0]); + if (!config->lowerdir) + return -ENOMEM; + break; + + case OPT_WORKDIR: + kfree(config->workdir); + config->workdir = match_strdup(&args[0]); + if (!config->workdir) + return -ENOMEM; + break; + + case OPT_DEFAULT_PERMISSIONS: + config->default_permissions = true; + break; + + case OPT_REDIRECT_DIR_ON: + config->redirect_mode = OVL_REDIRECT_ON; + set->redirect = true; + break; + + case OPT_REDIRECT_DIR_OFF: + config->redirect_mode = ovl_redirect_always_follow ? + OVL_REDIRECT_FOLLOW : + OVL_REDIRECT_NOFOLLOW; + set->redirect = true; + break; + + case OPT_REDIRECT_DIR_FOLLOW: + config->redirect_mode = OVL_REDIRECT_FOLLOW; + set->redirect = true; + break; + + case OPT_REDIRECT_DIR_NOFOLLOW: + config->redirect_mode = OVL_REDIRECT_NOFOLLOW; + set->redirect = true; + break; - case OPT_WORKDIR: - kfree(config->workdir); - config->workdir = match_strdup(&args[0]); - if (!config->workdir) - return -ENOMEM; - break; + case OPT_INDEX_ON: + config->index = true; + set->index = true; + break; - case OPT_DEFAULT_PERMISSIONS: - config->default_permissions = true; - break; + case OPT_INDEX_OFF: + config->index = false; + set->index = true; + break; - case OPT_REDIRECT_DIR_ON: - config->redirect_mode = OVL_REDIRECT_ON; - redirect_opt = true; - break; + case OPT_UUID_ON: + config->uuid = true; + break; - case OPT_REDIRECT_DIR_OFF: - config->redirect_mode = ovl_redirect_always_follow ? - OVL_REDIRECT_FOLLOW : - OVL_REDIRECT_NOFOLLOW; - redirect_opt = true; - break; + case OPT_UUID_OFF: + config->uuid = false; + break; - case OPT_REDIRECT_DIR_FOLLOW: - config->redirect_mode = OVL_REDIRECT_FOLLOW; - redirect_opt = true; - break; + case OPT_NFS_EXPORT_ON: + config->nfs_export = true; + set->nfs_export = true; + break; - case OPT_REDIRECT_DIR_NOFOLLOW: - config->redirect_mode = OVL_REDIRECT_NOFOLLOW; - redirect_opt = true; - break; + case OPT_NFS_EXPORT_OFF: + config->nfs_export = false; + set->nfs_export = true; + break; - case OPT_INDEX_ON: - config->index = true; - index_opt = true; - break; + case OPT_XINO_ON: + config->xino = OVL_XINO_ON; + break; - case OPT_INDEX_OFF: - config->index = false; - index_opt = true; - break; + case OPT_XINO_OFF: + config->xino = OVL_XINO_OFF; + break; - case OPT_UUID_ON: - config->uuid = true; - break; + case OPT_XINO_AUTO: + config->xino = OVL_XINO_AUTO; + break; - case OPT_UUID_OFF: - config->uuid = false; - break; + case OPT_METACOPY_ON: + config->metacopy = true; + set->metacopy = true; + break; - case OPT_NFS_EXPORT_ON: - config->nfs_export = true; - nfs_export_opt = true; - break; - - case OPT_NFS_EXPORT_OFF: - config->nfs_export = false; - nfs_export_opt = true; - break; - - case OPT_XINO_ON: - config->xino = OVL_XINO_ON; - break; - - case OPT_XINO_OFF: - config->xino = OVL_XINO_OFF; - break; + case OPT_METACOPY_OFF: + config->metacopy = false; + set->metacopy = true; + break; - case OPT_XINO_AUTO: - config->xino = OVL_XINO_AUTO; - break; + case OPT_VOLATILE: + config->ovl_volatile = true; + break; - case OPT_METACOPY_ON: - config->metacopy = true; - metacopy_opt = true; - break; + case OPT_USERXATTR: + config->userxattr = true; + break; - case OPT_METACOPY_OFF: - config->metacopy = false; - metacopy_opt = true; - break; + default: + pr_err("unrecognized mount option \"%s\" or missing value\n", + opt); + return -EINVAL; + } - case OPT_VOLATILE: - config->ovl_volatile = true; - break; + return err; +} - case OPT_USERXATTR: - config->userxattr = true; - break; +static int ovl_parse_options(char *opt, struct ovl_config *config) +{ + char *p; + int err; + struct ovl_opt_set set = {}; - default: - pr_err("unrecognized mount option \"%s\" or missing value\n", - p); - return -EINVAL; - } + while ((p = ovl_next_opt(&opt)) != NULL) { + err = ovl_parse_opt(p, config, &set); + if (err) + return err; } /* Workdir/index are useless in non-upper mount */ @@ -649,9 +660,9 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) kfree(config->workdir); config->workdir = NULL; } - if (config->index && index_opt) { + if (config->index && set.index) { pr_info("option \"index=on\" is useless in a non-upper mount, ignore\n"); - index_opt = false; + set.index = false; } config->index = false; } @@ -670,12 +681,12 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) /* Resolve metacopy -> redirect_dir dependency */ if (config->metacopy && config->redirect_mode != OVL_REDIRECT_ON) { - if (metacopy_opt && redirect_opt) { + if (set.metacopy && set.redirect) { pr_err("conflicting options: metacopy=on,redirect_dir=%s\n", ovl_redirect_mode(config)); return -EINVAL; } - if (redirect_opt) { + if (set.redirect) { /* * There was an explicit redirect_dir=... that resulted * in this conflict. @@ -695,10 +706,10 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) config->redirect_mode != OVL_REDIRECT_NOFOLLOW) { pr_info("NFS export requires \"redirect_dir=nofollow\" on non-upper mount, falling back to nfs_export=off.\n"); config->nfs_export = false; - } else if (nfs_export_opt && index_opt) { + } else if (set.nfs_export && set.index) { pr_err("conflicting options: nfs_export=on,index=off\n"); return -EINVAL; - } else if (index_opt) { + } else if (set.index) { /* * There was an explicit index=off that resulted * in this conflict. @@ -713,11 +724,11 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) /* Resolve nfs_export -> !metacopy dependency */ if (config->nfs_export && config->metacopy) { - if (nfs_export_opt && metacopy_opt) { + if (set.nfs_export && set.metacopy) { pr_err("conflicting options: nfs_export=on,metacopy=on\n"); return -EINVAL; } - if (metacopy_opt) { + if (set.metacopy) { /* * There was an explicit metacopy=on that resulted * in this conflict. @@ -737,13 +748,13 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) /* Resolve userxattr -> !redirect && !metacopy dependency */ if (config->userxattr) { - if (redirect_opt && + if (set.redirect && config->redirect_mode != OVL_REDIRECT_NOFOLLOW) { pr_err("conflicting options: userxattr,redirect_dir=%s\n", ovl_redirect_mode(config)); return -EINVAL; } - if (config->metacopy && metacopy_opt) { + if (config->metacopy && set.metacopy) { pr_err("conflicting options: userxattr,metacopy=on\n"); return -EINVAL; } @@ -1981,7 +1992,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) ofs->config.nfs_export = ovl_nfs_export_def; ofs->config.xino = ovl_xino_def(); ofs->config.metacopy = ovl_metacopy_def; - err = ovl_parse_opt((char *) data, &ofs->config); + err = ovl_parse_options((char *) data, &ofs->config); if (err) goto out_err; -- cgit v1.2.3-58-ga151 From 1784fbc2ed9c888ea4e895f30a53207ed7ee8208 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 16 Jun 2023 15:53:58 +0300 Subject: ovl: port to new mount api We recently ported util-linux to the new mount api. Now the mount(8) tool will by default use the new mount api. While trying hard to fall back to the old mount api gracefully there are still cases where we run into issues that are difficult to handle nicely. Now with mount(8) and libmount supporting the new mount api I expect an increase in the number of bug reports and issues we're going to see with filesystems that don't yet support the new mount api. So it's time we rectify this. When ovl_fill_super() fails before setting sb->s_root, we need to cleanup sb->s_fs_info. The logic is a bit convoluted but tl;dr: If sget_fc() has succeeded fc->s_fs_info will have been transferred to sb->s_fs_info. So by the time ->fill_super()/ovl_fill_super() is called fc->s_fs_info is NULL consequently fs_context->free() won't call ovl_free_fs(). If we fail before sb->s_root() is set then ->put_super() won't be called which would call ovl_free_fs(). IOW, if we fail in ->fill_super() before sb->s_root we have to clean it up. Signed-off-by: Christian Brauner Signed-off-by: Amir Goldstein --- fs/overlayfs/super.c | 398 +++++++++++++++++++++++++-------------------------- 1 file changed, 193 insertions(+), 205 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index dbd9151dc073..cc389701dd6d 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "overlayfs.h" @@ -253,7 +254,8 @@ static void ovl_put_super(struct super_block *sb) { struct ovl_fs *ofs = sb->s_fs_info; - ovl_free_fs(ofs); + if (ofs) + ovl_free_fs(ofs); } /* Sync real dirty inodes in upper filesystem (if it exists) */ @@ -408,16 +410,17 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) return 0; } -static int ovl_remount(struct super_block *sb, int *flags, char *data) +static int ovl_reconfigure(struct fs_context *fc) { + struct super_block *sb = fc->root->d_sb; struct ovl_fs *ofs = sb->s_fs_info; struct super_block *upper_sb; int ret = 0; - if (!(*flags & SB_RDONLY) && ovl_force_readonly(ofs)) + if (!(fc->sb_flags & SB_RDONLY) && ovl_force_readonly(ofs)) return -EROFS; - if (*flags & SB_RDONLY && !sb_rdonly(sb)) { + if (fc->sb_flags & SB_RDONLY && !sb_rdonly(sb)) { upper_sb = ovl_upper_mnt(ofs)->mnt_sb; if (ovl_should_sync(ofs)) { down_read(&upper_sb->s_umount); @@ -438,219 +441,151 @@ static const struct super_operations ovl_super_operations = { .sync_fs = ovl_sync_fs, .statfs = ovl_statfs, .show_options = ovl_show_options, - .remount_fs = ovl_remount, }; enum { - OPT_LOWERDIR, - OPT_UPPERDIR, - OPT_WORKDIR, - OPT_DEFAULT_PERMISSIONS, - OPT_REDIRECT_DIR_ON, - OPT_REDIRECT_DIR_OFF, - OPT_REDIRECT_DIR_FOLLOW, - OPT_REDIRECT_DIR_NOFOLLOW, - OPT_INDEX_ON, - OPT_INDEX_OFF, - OPT_UUID_ON, - OPT_UUID_OFF, - OPT_NFS_EXPORT_ON, - OPT_USERXATTR, - OPT_NFS_EXPORT_OFF, - OPT_XINO_ON, - OPT_XINO_OFF, - OPT_XINO_AUTO, - OPT_METACOPY_ON, - OPT_METACOPY_OFF, - OPT_VOLATILE, - OPT_ERR, + Opt_lowerdir, + Opt_upperdir, + Opt_workdir, + Opt_default_permissions, + Opt_redirect_dir, + Opt_index, + Opt_uuid, + Opt_nfs_export, + Opt_userxattr, + Opt_xino, + Opt_metacopy, + Opt_volatile, }; -static const match_table_t ovl_tokens = { - {OPT_LOWERDIR, "lowerdir=%s"}, - {OPT_UPPERDIR, "upperdir=%s"}, - {OPT_WORKDIR, "workdir=%s"}, - {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, - {OPT_REDIRECT_DIR_ON, "redirect_dir=on"}, - {OPT_REDIRECT_DIR_OFF, "redirect_dir=off"}, - {OPT_REDIRECT_DIR_FOLLOW, "redirect_dir=follow"}, - {OPT_REDIRECT_DIR_NOFOLLOW, "redirect_dir=nofollow"}, - {OPT_INDEX_ON, "index=on"}, - {OPT_INDEX_OFF, "index=off"}, - {OPT_USERXATTR, "userxattr"}, - {OPT_UUID_ON, "uuid=on"}, - {OPT_UUID_OFF, "uuid=off"}, - {OPT_NFS_EXPORT_ON, "nfs_export=on"}, - {OPT_NFS_EXPORT_OFF, "nfs_export=off"}, - {OPT_XINO_ON, "xino=on"}, - {OPT_XINO_OFF, "xino=off"}, - {OPT_XINO_AUTO, "xino=auto"}, - {OPT_METACOPY_ON, "metacopy=on"}, - {OPT_METACOPY_OFF, "metacopy=off"}, - {OPT_VOLATILE, "volatile"}, - {OPT_ERR, NULL} +static const struct constant_table ovl_parameter_bool[] = { + { "on", true }, + { "off", false }, + {} }; -static char *ovl_next_opt(char **s) -{ - char *sbegin = *s; - char *p; - - if (sbegin == NULL) - return NULL; +static const struct fs_parameter_spec ovl_parameter_spec[] = { + fsparam_string("lowerdir", Opt_lowerdir), + fsparam_string("upperdir", Opt_upperdir), + fsparam_string("workdir", Opt_workdir), + fsparam_flag("default_permissions", Opt_default_permissions), + fsparam_enum("redirect_dir", Opt_redirect_dir, ovl_parameter_redirect_dir), + fsparam_enum("index", Opt_index, ovl_parameter_bool), + fsparam_enum("uuid", Opt_uuid, ovl_parameter_bool), + fsparam_enum("nfs_export", Opt_nfs_export, ovl_parameter_bool), + fsparam_flag("userxattr", Opt_userxattr), + fsparam_enum("xino", Opt_xino, ovl_parameter_xino), + fsparam_enum("metacopy", Opt_metacopy, ovl_parameter_bool), + fsparam_flag("volatile", Opt_volatile), + {} +}; - for (p = sbegin; *p; p++) { - if (*p == '\\') { - p++; - if (!*p) - break; - } else if (*p == ',') { - *p = '\0'; - *s = p + 1; - return sbegin; - } - } - *s = NULL; - return sbegin; -} +struct ovl_fs_context { + struct ovl_opt_set set; +}; -static int ovl_parse_opt(char *opt, struct ovl_config *config, - struct ovl_opt_set *set) +static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param) { int err = 0; - int token; - substring_t args[MAX_OPT_ARGS]; + struct fs_parse_result result; + struct ovl_fs *ofs = fc->s_fs_info; + struct ovl_config *config = &ofs->config; + struct ovl_fs_context *ctx = fc->fs_private; + char *dup; + int opt; - if (!*opt) + /* + * On remount overlayfs has always ignored all mount options no + * matter if malformed or not so for backwards compatibility we + * do the same here. + */ + if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) return 0; - token = match_token(opt, ovl_tokens, args); - switch (token) { - case OPT_UPPERDIR: - kfree(config->upperdir); - config->upperdir = match_strdup(&args[0]); - if (!config->upperdir) - return -ENOMEM; - break; + opt = fs_parse(fc, ovl_parameter_spec, param, &result); + if (opt < 0) + return opt; - case OPT_LOWERDIR: - kfree(config->lowerdir); - config->lowerdir = match_strdup(&args[0]); - if (!config->lowerdir) - return -ENOMEM; - break; - - case OPT_WORKDIR: - kfree(config->workdir); - config->workdir = match_strdup(&args[0]); - if (!config->workdir) - return -ENOMEM; - break; - - case OPT_DEFAULT_PERMISSIONS: - config->default_permissions = true; - break; - - case OPT_REDIRECT_DIR_ON: - config->redirect_mode = OVL_REDIRECT_ON; - set->redirect = true; - break; - - case OPT_REDIRECT_DIR_OFF: - config->redirect_mode = ovl_redirect_always_follow ? - OVL_REDIRECT_FOLLOW : - OVL_REDIRECT_NOFOLLOW; - set->redirect = true; - break; - - case OPT_REDIRECT_DIR_FOLLOW: - config->redirect_mode = OVL_REDIRECT_FOLLOW; - set->redirect = true; - break; - - case OPT_REDIRECT_DIR_NOFOLLOW: - config->redirect_mode = OVL_REDIRECT_NOFOLLOW; - set->redirect = true; - break; - - case OPT_INDEX_ON: - config->index = true; - set->index = true; - break; + switch (opt) { + case Opt_lowerdir: + dup = kstrdup(param->string, GFP_KERNEL); + if (!dup) { + err = -ENOMEM; + break; + } - case OPT_INDEX_OFF: - config->index = false; - set->index = true; + kfree(config->lowerdir); + config->lowerdir = dup; break; + case Opt_upperdir: + dup = kstrdup(param->string, GFP_KERNEL); + if (!dup) { + err = -ENOMEM; + break; + } - case OPT_UUID_ON: - config->uuid = true; + kfree(config->upperdir); + config->upperdir = dup; break; + case Opt_workdir: + dup = kstrdup(param->string, GFP_KERNEL); + if (!dup) { + err = -ENOMEM; + break; + } - case OPT_UUID_OFF: - config->uuid = false; + kfree(config->workdir); + config->workdir = dup; break; - - case OPT_NFS_EXPORT_ON: - config->nfs_export = true; - set->nfs_export = true; + case Opt_default_permissions: + config->default_permissions = true; break; - - case OPT_NFS_EXPORT_OFF: - config->nfs_export = false; - set->nfs_export = true; + 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_XINO_ON: - config->xino = OVL_XINO_ON; + case Opt_index: + config->index = result.uint_32; + ctx->set.index = true; break; - - case OPT_XINO_OFF: - config->xino = OVL_XINO_OFF; + case Opt_uuid: + config->uuid = result.uint_32; break; - - case OPT_XINO_AUTO: - config->xino = OVL_XINO_AUTO; + case Opt_nfs_export: + config->nfs_export = result.uint_32; + ctx->set.nfs_export = true; break; - - case OPT_METACOPY_ON: - config->metacopy = true; - set->metacopy = true; + case Opt_xino: + config->xino = result.uint_32; break; - - case OPT_METACOPY_OFF: - config->metacopy = false; - set->metacopy = true; + case Opt_metacopy: + config->metacopy = result.uint_32; + ctx->set.metacopy = true; break; - - case OPT_VOLATILE: + case Opt_volatile: config->ovl_volatile = true; break; - - case OPT_USERXATTR: + case Opt_userxattr: config->userxattr = true; break; - default: pr_err("unrecognized mount option \"%s\" or missing value\n", - opt); + param->key); return -EINVAL; } return err; } -static int ovl_parse_options(char *opt, struct ovl_config *config) +static int ovl_fs_params_verify(const struct ovl_fs_context *ctx, + struct ovl_config *config) { - char *p; - int err; - struct ovl_opt_set set = {}; - - while ((p = ovl_next_opt(&opt)) != NULL) { - err = ovl_parse_opt(p, config, &set); - if (err) - return err; - } + struct ovl_opt_set set = ctx->set; /* Workdir/index are useless in non-upper mount */ if (!config->upperdir) { @@ -1958,12 +1893,13 @@ static struct dentry *ovl_get_root(struct super_block *sb, return root; } -static int ovl_fill_super(struct super_block *sb, void *data, int silent) +static int ovl_fill_super(struct super_block *sb, struct fs_context *fc) { - struct path upperpath = { }; + struct ovl_fs *ofs = sb->s_fs_info; + struct ovl_fs_context *ctx = fc->fs_private; + struct path upperpath = {}; struct dentry *root_dentry; struct ovl_entry *oe; - struct ovl_fs *ofs; struct ovl_layer *layers; struct cred *cred; char *splitlower = NULL; @@ -1971,34 +1907,23 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) int err; err = -EIO; - if (WARN_ON(sb->s_user_ns != current_user_ns())) - goto out; + if (WARN_ON(fc->user_ns != current_user_ns())) + goto out_err; sb->s_d_op = &ovl_dentry_operations; - err = -ENOMEM; - ofs = kzalloc(sizeof(struct ovl_fs), GFP_KERNEL); - if (!ofs) - goto out; - err = -ENOMEM; ofs->creator_cred = cred = prepare_creds(); if (!cred) goto out_err; - ofs->config.redirect_mode = ovl_redirect_mode_def(); - ofs->config.index = ovl_index_def; - ofs->config.uuid = true; - ofs->config.nfs_export = ovl_nfs_export_def; - ofs->config.xino = ovl_xino_def(); - ofs->config.metacopy = ovl_metacopy_def; - err = ovl_parse_options((char *) data, &ofs->config); + err = ovl_fs_params_verify(ctx, &ofs->config); if (err) goto out_err; err = -EINVAL; if (!ofs->config.lowerdir) { - if (!silent) + if (!(fc->sb_flags & SB_SILENT)) pr_err("missing 'lowerdir'\n"); goto out_err; } @@ -2143,25 +2068,88 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) out_free_oe: ovl_free_entry(oe); out_err: - kfree(splitlower); - path_put(&upperpath); ovl_free_fs(ofs); -out: + sb->s_fs_info = NULL; return err; } -static struct dentry *ovl_mount(struct file_system_type *fs_type, int flags, - const char *dev_name, void *raw_data) +static int ovl_get_tree(struct fs_context *fc) { - return mount_nodev(fs_type, flags, raw_data, ovl_fill_super); + return get_tree_nodev(fc, ovl_fill_super); +} + +static inline void ovl_fs_context_free(struct ovl_fs_context *ctx) +{ + kfree(ctx); +} + +static void ovl_free(struct fs_context *fc) +{ + struct ovl_fs *ofs = fc->s_fs_info; + struct ovl_fs_context *ctx = fc->fs_private; + + /* + * ofs is stored in the fs_context when it is initialized. + * ofs is transferred to the superblock on a successful mount, + * but if an error occurs before the transfer we have to free + * it here. + */ + if (ofs) + ovl_free_fs(ofs); + + if (ctx) + ovl_fs_context_free(ctx); +} + +static const struct fs_context_operations ovl_context_ops = { + .parse_param = ovl_parse_param, + .get_tree = ovl_get_tree, + .reconfigure = ovl_reconfigure, + .free = ovl_free, +}; + +/* + * This is called during fsopen() and will record the user namespace of + * the caller in fc->user_ns since we've raised FS_USERNS_MOUNT. We'll + * need it when we actually create the superblock to verify that the + * process creating the superblock is in the same user namespace as + * process that called fsopen(). + */ +static int ovl_init_fs_context(struct fs_context *fc) +{ + struct ovl_fs_context *ctx; + struct ovl_fs *ofs; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL_ACCOUNT); + if (!ctx) + return -ENOMEM; + + ofs = kzalloc(sizeof(struct ovl_fs), GFP_KERNEL); + if (!ofs) { + ovl_fs_context_free(ctx); + return -ENOMEM; + } + + ofs->config.redirect_mode = ovl_redirect_mode_def(); + ofs->config.index = ovl_index_def; + ofs->config.uuid = true; + ofs->config.nfs_export = ovl_nfs_export_def; + ofs->config.xino = ovl_xino_def(); + ofs->config.metacopy = ovl_metacopy_def; + + fc->s_fs_info = ofs; + fc->fs_private = ctx; + fc->ops = &ovl_context_ops; + return 0; } static struct file_system_type ovl_fs_type = { - .owner = THIS_MODULE, - .name = "overlay", - .fs_flags = FS_USERNS_MOUNT, - .mount = ovl_mount, - .kill_sb = kill_anon_super, + .owner = THIS_MODULE, + .name = "overlay", + .init_fs_context = ovl_init_fs_context, + .parameters = ovl_parameter_spec, + .fs_flags = FS_USERNS_MOUNT, + .kill_sb = kill_anon_super, }; MODULE_ALIAS_FS("overlay"); -- cgit v1.2.3-58-ga151 From b36a5780cb44a16d4384639740d87e08d4cee627 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 16 Jun 2023 16:54:19 +0300 Subject: ovl: modify layer parameter parsing We ran into issues where mount(8) passed multiple lower layers as one big string through fsconfig(). But the fsconfig() FSCONFIG_SET_STRING option is limited to 256 bytes in strndup_user(). While this would be fixable by extending the fsconfig() buffer I'd rather encourage users to append layers via multiple fsconfig() calls as the interface allows nicely for this. This has also been requested as a feature before. With this port to the new mount api the following will be possible: fsconfig(fs_fd, FSCONFIG_SET_STRING, "lowerdir", "/lower1", 0); /* set upper layer */ fsconfig(fs_fd, FSCONFIG_SET_STRING, "upperdir", "/upper", 0); /* append "/lower2", "/lower3", and "/lower4" */ fsconfig(fs_fd, FSCONFIG_SET_STRING, "lowerdir", ":/lower2:/lower3:/lower4", 0); /* turn index feature on */ fsconfig(fs_fd, FSCONFIG_SET_STRING, "index", "on", 0); /* append "/lower5" */ fsconfig(fs_fd, FSCONFIG_SET_STRING, "lowerdir", ":/lower5", 0); Specifying ':' would have been rejected so this isn't a regression. And we can't simply use "lowerdir=/lower" to append on top of existing layers as "lowerdir=/lower,lowerdir=/other-lower" would make "/other-lower" the only lower layer so we'd break uapi if we changed this. So the ':' prefix seems a good compromise. Users can choose to specify multiple layers at once or individual layers. A layer is appended if it starts with ":". This requires that the user has already added at least one layer before. If lowerdir is specified again without a leading ":" then all previous layers are dropped and replaced with the new layers. If lowerdir is specified and empty than all layers are simply dropped. An additional change is that overlayfs will now parse and resolve layers right when they are specified in fsconfig() instead of deferring until super block creation. This allows users to receive early errors. It also allows users to actually use up to 500 layers something which was theoretically possible but ended up not working due to the mount option string passed via mount(2) being too large. This also allows a more privileged process to set config options for a lesser privileged process as the creds for fsconfig() and the creds for fsopen() can differ. We could restrict that they match by enforcing that the creds of fsopen() and fsconfig() match but I don't see why that needs to be the case and allows for a good delegation mechanism. Plus, in the future it means we're able to extend overlayfs mount options and allow users to specify layers via file descriptors instead of paths: fsconfig(FSCONFIG_SET_PATH{_EMPTY}, "lowerdir", "lower1", dirfd); /* append */ fsconfig(FSCONFIG_SET_PATH{_EMPTY}, "lowerdir", "lower2", dirfd); /* append */ fsconfig(FSCONFIG_SET_PATH{_EMPTY}, "lowerdir", "lower3", dirfd); /* clear all layers specified until now */ fsconfig(FSCONFIG_SET_STRING, "lowerdir", NULL, 0); This would be especially nice if users create an overlayfs mount on top of idmapped layers or just in general private mounts created via open_tree(OPEN_TREE_CLONE). Those mounts would then never have to appear anywhere in the filesystem. But for now just do the minimal thing. We should probably aim to move more validation into ovl_fs_parse_param() so users get errors before fsconfig(FSCONFIG_CMD_CREATE). But that can be done in additional patches later. This is now also rebased on top of the lazy lowerdata lookup which allows the specificatin of data only layers using the new "::" syntax. The rules are simple. A data only layers cannot be followed by any regular layers and data layers must be preceeded by at least one regular layer. Parsing the lowerdir mount option must change because of this. The original patchset used the old lowerdir parsing function to split a lowerdir mount option string such as: lowerdir=/lower1:/lower2::/lower3::/lower4 simply replacing each non-escaped ":" by "\0". So sequences of non-escaped ":" were counted as layers. For example, the previous lowerdir mount option above would've counted 6 layers instead of 4 and a lowerdir mount option such as: lowerdir="/lower1:/lower2::/lower3::/lower4:::::::::::::::::::::::::::" would be counted as 33 layers. Other than being ugly this didn't matter much because kern_path() would reject the first "\0" layer. However, this overcounting of layers becomes problematic when we base allocations on it where we very much only want to allocate space for 4 layers instead of 33. So the new parsing function rejects non-escaped sequences of colons other than ":" and "::" immediately instead of relying on kern_path(). Link: https://github.com/util-linux/util-linux/issues/2287 Link: https://github.com/util-linux/util-linux/issues/1992 Link: https://bugs.archlinux.org/task/78702 Link: https://lore.kernel.org/linux-unionfs/20230530-klagen-zudem-32c0908c2108@brauner Signed-off-by: Christian Brauner Signed-off-by: Amir Goldstein --- fs/overlayfs/Makefile | 2 +- fs/overlayfs/overlayfs.h | 23 +++ fs/overlayfs/ovl_entry.h | 3 +- fs/overlayfs/params.c | 389 +++++++++++++++++++++++++++++++++++++++++++++++ fs/overlayfs/super.c | 361 +++++++++++++------------------------------ 5 files changed, 520 insertions(+), 258 deletions(-) create mode 100644 fs/overlayfs/params.c (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/Makefile b/fs/overlayfs/Makefile index 9164c585eb2f..4e173d56b11f 100644 --- a/fs/overlayfs/Makefile +++ b/fs/overlayfs/Makefile @@ -6,4 +6,4 @@ obj-$(CONFIG_OVERLAY_FS) += overlay.o overlay-objs := super.o namei.o util.o inode.o file.o dir.o readdir.o \ - copy_up.o export.o + copy_up.o export.o params.o diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 30227ccc758d..5b6ac03af192 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -368,6 +368,29 @@ static inline bool ovl_open_flags_need_copy_up(int flags) } +/* params.c */ +#define OVL_MAX_STACK 500 + +struct ovl_fs_context_layer { + char *name; + struct path path; +}; + +struct ovl_fs_context { + struct path upper; + struct path work; + size_t capacity; + size_t nr; /* includes nr_data */ + size_t nr_data; + struct ovl_opt_set set; + struct ovl_fs_context_layer *lower; +}; + +int ovl_parse_param_upperdir(const char *name, struct fs_context *fc, + bool workdir); +int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc); +void ovl_parse_param_drop_lowerdir(struct ovl_fs_context *ctx); + /* util.c */ int ovl_want_write(struct dentry *dentry); void ovl_drop_write(struct dentry *dentry); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index b4eb0bd5d0b6..306e1ecdc96d 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -6,7 +6,6 @@ */ struct ovl_config { - char *lowerdir; char *upperdir; char *workdir; bool default_permissions; @@ -39,6 +38,7 @@ struct ovl_layer { int idx; /* One fsid per unique underlying sb (upper fsid == 0) */ int fsid; + char *name; }; /* @@ -99,7 +99,6 @@ struct ovl_fs { errseq_t errseq; }; - /* Number of lower layers, not including data-only layers */ static inline unsigned int ovl_numlowerlayer(struct ovl_fs *ofs) { diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c new file mode 100644 index 000000000000..d17d6b483dd0 --- /dev/null +++ b/fs/overlayfs/params.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include "overlayfs.h" + +static ssize_t ovl_parse_param_split_lowerdirs(char *str) +{ + ssize_t nr_layers = 1, nr_colons = 0; + char *s, *d; + + for (s = d = str;; s++, d++) { + if (*s == '\\') { + s++; + } else if (*s == ':') { + bool next_colon = (*(s + 1) == ':'); + + nr_colons++; + if (nr_colons == 2 && next_colon) { + pr_err("only single ':' or double '::' sequences of unescaped colons in lowerdir mount option allowed.\n"); + return -EINVAL; + } + /* count layers, not colons */ + if (!next_colon) + nr_layers++; + + *d = '\0'; + continue; + } + + *d = *s; + if (!*s) { + /* trailing colons */ + if (nr_colons) { + pr_err("unescaped trailing colons in lowerdir mount option.\n"); + return -EINVAL; + } + break; + } + nr_colons = 0; + } + + return nr_layers; +} + +static int ovl_mount_dir_noesc(const char *name, struct path *path) +{ + int err = -EINVAL; + + if (!*name) { + pr_err("empty lowerdir\n"); + goto out; + } + err = kern_path(name, LOOKUP_FOLLOW, path); + if (err) { + pr_err("failed to resolve '%s': %i\n", name, err); + goto out; + } + err = -EINVAL; + if (ovl_dentry_weird(path->dentry)) { + pr_err("filesystem on '%s' not supported\n", name); + goto out_put; + } + if (!d_is_dir(path->dentry)) { + pr_err("'%s' not a directory\n", name); + goto out_put; + } + return 0; + +out_put: + path_put_init(path); +out: + return err; +} + +static void ovl_unescape(char *s) +{ + char *d = s; + + for (;; s++, d++) { + if (*s == '\\') + s++; + *d = *s; + if (!*s) + break; + } +} + +static int ovl_mount_dir(const char *name, struct path *path) +{ + int err = -ENOMEM; + char *tmp = kstrdup(name, GFP_KERNEL); + + if (tmp) { + ovl_unescape(tmp); + err = ovl_mount_dir_noesc(tmp, path); + + if (!err && path->dentry->d_flags & DCACHE_OP_REAL) { + pr_err("filesystem on '%s' not supported as upperdir\n", + tmp); + path_put_init(path); + err = -EINVAL; + } + kfree(tmp); + } + return err; +} + +int ovl_parse_param_upperdir(const char *name, struct fs_context *fc, + bool workdir) +{ + int err; + struct ovl_fs *ofs = fc->s_fs_info; + struct ovl_config *config = &ofs->config; + struct ovl_fs_context *ctx = fc->fs_private; + struct path path; + char *dup; + + err = ovl_mount_dir(name, &path); + if (err) + return err; + + /* + * Check whether upper path is read-only here to report failures + * early. Don't forget to recheck when the superblock is created + * as the mount attributes could change. + */ + if (__mnt_is_readonly(path.mnt)) { + path_put(&path); + return -EINVAL; + } + + dup = kstrdup(name, GFP_KERNEL); + if (!dup) { + path_put(&path); + return -ENOMEM; + } + + if (workdir) { + kfree(config->workdir); + config->workdir = dup; + path_put(&ctx->work); + ctx->work = path; + } else { + kfree(config->upperdir); + config->upperdir = dup; + path_put(&ctx->upper); + ctx->upper = path; + } + return 0; +} + +void ovl_parse_param_drop_lowerdir(struct ovl_fs_context *ctx) +{ + for (size_t nr = 0; nr < ctx->nr; nr++) { + path_put(&ctx->lower[nr].path); + kfree(ctx->lower[nr].name); + ctx->lower[nr].name = NULL; + } + ctx->nr = 0; + ctx->nr_data = 0; +} + +/* + * Parse lowerdir= mount option: + * + * (1) lowerdir=/lower1:/lower2:/lower3::/data1::/data2 + * Set "/lower1", "/lower2", and "/lower3" as lower layers and + * "/data1" and "/data2" as data lower layers. Any existing lower + * layers are replaced. + * (2) lowerdir=:/lower4 + * Append "/lower4" to current stack of lower layers. This requires + * that there already is at least one lower layer configured. + * (3) lowerdir=::/lower5 + * Append data "/lower5" as data lower layer. This requires that + * there's at least one regular lower layer present. + */ +int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc) +{ + int err; + struct ovl_fs_context *ctx = fc->fs_private; + struct ovl_fs_context_layer *l; + char *dup = NULL, *dup_iter; + ssize_t nr_lower = 0, nr = 0, nr_data = 0; + bool append = false, data_layer = false; + + /* + * Ensure we're backwards compatible with mount(2) + * by allowing relative paths. + */ + + /* drop all existing lower layers */ + if (!*name) { + ovl_parse_param_drop_lowerdir(ctx); + return 0; + } + + if (strncmp(name, "::", 2) == 0) { + /* + * This is a data layer. + * There must be at least one regular lower layer + * specified. + */ + if (ctx->nr == 0) { + pr_err("data lower layers without regular lower layers not allowed"); + return -EINVAL; + } + + /* Skip the leading "::". */ + name += 2; + data_layer = true; + /* + * A data layer is automatically an append as there + * must've been at least one regular lower layer. + */ + append = true; + } else if (*name == ':') { + /* + * This is a regular lower layer. + * If users want to append a layer enforce that they + * have already specified a first layer before. It's + * better to be strict. + */ + if (ctx->nr == 0) { + pr_err("cannot append layer if no previous layer has been specified"); + return -EINVAL; + } + + /* + * Once a sequence of data layers has started regular + * lower layers are forbidden. + */ + if (ctx->nr_data > 0) { + pr_err("regular lower layers cannot follow data lower layers"); + return -EINVAL; + } + + /* Skip the leading ":". */ + name++; + append = true; + } + + dup = kstrdup(name, GFP_KERNEL); + if (!dup) + return -ENOMEM; + + err = -EINVAL; + nr_lower = ovl_parse_param_split_lowerdirs(dup); + if (nr_lower < 0) + goto out_err; + + if ((nr_lower > OVL_MAX_STACK) || + (append && (size_add(ctx->nr, nr_lower) > OVL_MAX_STACK))) { + pr_err("too many lower directories, limit is %d\n", OVL_MAX_STACK); + goto out_err; + } + + if (!append) + ovl_parse_param_drop_lowerdir(ctx); + + /* + * (1) append + * + * We want nr <= nr_lower <= capacity We know nr > 0 and nr <= + * capacity. If nr == 0 this wouldn't be append. If nr + + * nr_lower is <= capacity then nr <= nr_lower <= capacity + * already holds. If nr + nr_lower exceeds capacity, we realloc. + * + * (2) replace + * + * Ensure we're backwards compatible with mount(2) which allows + * "lowerdir=/a:/b:/c,lowerdir=/d:/e:/f" causing the last + * specified lowerdir mount option to win. + * + * We want nr <= nr_lower <= capacity We know either (i) nr == 0 + * or (ii) nr > 0. We also know nr_lower > 0. The capacity + * could've been changed multiple times already so we only know + * nr <= capacity. If nr + nr_lower > capacity we realloc, + * otherwise nr <= nr_lower <= capacity holds already. + */ + nr_lower += ctx->nr; + if (nr_lower > ctx->capacity) { + err = -ENOMEM; + l = krealloc_array(ctx->lower, nr_lower, sizeof(*ctx->lower), + GFP_KERNEL_ACCOUNT); + if (!l) + goto out_err; + + ctx->lower = l; + ctx->capacity = nr_lower; + } + + /* + * (3) By (1) and (2) we know nr <= nr_lower <= capacity. + * (4) If ctx->nr == 0 => replace + * We have verified above that the lowerdir mount option + * isn't an append, i.e., the lowerdir mount option + * doesn't start with ":" or "::". + * (4.1) The lowerdir mount options only contains regular lower + * layers ":". + * => Nothing to verify. + * (4.2) The lowerdir mount options contains regular ":" and + * data "::" layers. + * => We need to verify that data lower layers "::" aren't + * followed by regular ":" lower layers + * (5) If ctx->nr > 0 => append + * We know that there's at least one regular layer + * otherwise we would've failed when parsing the previous + * lowerdir mount option. + * (5.1) The lowerdir mount option is a regular layer ":" append + * => We need to verify that no data layers have been + * specified before. + * (5.2) The lowerdir mount option is a data layer "::" append + * We know that there's at least one regular layer or + * other data layers. => There's nothing to verify. + */ + dup_iter = dup; + for (nr = ctx->nr; nr < nr_lower; nr++) { + l = &ctx->lower[nr]; + memset(l, 0, sizeof(*l)); + + err = ovl_mount_dir_noesc(dup_iter, &l->path); + if (err) + goto out_put; + + err = -ENOMEM; + l->name = kstrdup(dup_iter, GFP_KERNEL_ACCOUNT); + if (!l->name) + goto out_put; + + if (data_layer) + nr_data++; + + /* Calling strchr() again would overrun. */ + if ((nr + 1) == nr_lower) + break; + + err = -EINVAL; + dup_iter = strchr(dup_iter, '\0') + 1; + if (*dup_iter) { + /* + * This is a regular layer so we require that + * there are no data layers. + */ + if ((ctx->nr_data + nr_data) > 0) { + pr_err("regular lower layers cannot follow data lower layers"); + goto out_put; + } + + data_layer = false; + continue; + } + + /* This is a data lower layer. */ + data_layer = true; + dup_iter++; + } + ctx->nr = nr_lower; + ctx->nr_data += nr_data; + kfree(dup); + return 0; + +out_put: + /* + * We know nr >= ctx->nr < nr_lower. If we failed somewhere + * we want to undo until nr == ctx->nr. This is correct for + * both ctx->nr == 0 and ctx->nr > 0. + */ + for (; nr >= ctx->nr; nr--) { + l = &ctx->lower[nr]; + kfree(l->name); + l->name = NULL; + path_put(&l->path); + + /* don't overflow */ + if (nr == 0) + break; + } + +out_err: + kfree(dup); + + /* Intentionally don't realloc to a smaller size. */ + return err; +} diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index cc389701dd6d..ed4b35c9d647 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -27,8 +27,6 @@ MODULE_LICENSE("GPL"); struct ovl_dir_cache; -#define OVL_MAX_STACK 500 - static bool ovl_redirect_dir_def = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_DIR); module_param_named(redirect_dir, ovl_redirect_dir_def, bool, 0644); MODULE_PARM_DESC(redirect_dir, @@ -235,6 +233,7 @@ static void ovl_free_fs(struct ovl_fs *ofs) for (i = 0; i < ofs->numlayer; i++) { iput(ofs->layers[i].trap); mounts[i] = ofs->layers[i].mnt; + kfree(ofs->layers[i].name); } kern_unmount_array(mounts, ofs->numlayer); kfree(ofs->layers); @@ -242,7 +241,6 @@ static void ovl_free_fs(struct ovl_fs *ofs) free_anon_bdev(ofs->fs[i].pseudo_dev); kfree(ofs->fs); - kfree(ofs->config.lowerdir); kfree(ofs->config.upperdir); kfree(ofs->config.workdir); if (ofs->creator_cred) @@ -380,8 +378,17 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) { struct super_block *sb = dentry->d_sb; struct ovl_fs *ofs = sb->s_fs_info; - - seq_show_option(m, "lowerdir", ofs->config.lowerdir); + size_t nr, nr_merged_lower = ofs->numlayer - ofs->numdatalayer; + const struct ovl_layer *data_layers = &ofs->layers[nr_merged_lower]; + + /* ofs->layers[0] is the upper layer */ + seq_printf(m, ",lowerdir=%s", ofs->layers[1].name); + /* dump regular lower layers */ + for (nr = 2; nr < nr_merged_lower; nr++) + seq_printf(m, ":%s", ofs->layers[nr].name); + /* dump data lower layers */ + for (nr = 0; nr < ofs->numdatalayer; nr++) + seq_printf(m, "::%s", data_layers[nr].name); if (ofs->config.upperdir) { seq_show_option(m, "upperdir", ofs->config.upperdir); seq_show_option(m, "workdir", ofs->config.workdir); @@ -464,8 +471,11 @@ static const struct constant_table ovl_parameter_bool[] = { {} }; +#define fsparam_string_empty(NAME, OPT) \ + __fsparam(fs_param_is_string, NAME, OPT, fs_param_can_be_empty, NULL) + static const struct fs_parameter_spec ovl_parameter_spec[] = { - fsparam_string("lowerdir", Opt_lowerdir), + fsparam_string_empty("lowerdir", Opt_lowerdir), fsparam_string("upperdir", Opt_upperdir), fsparam_string("workdir", Opt_workdir), fsparam_flag("default_permissions", Opt_default_permissions), @@ -480,10 +490,6 @@ static const struct fs_parameter_spec ovl_parameter_spec[] = { {} }; -struct ovl_fs_context { - struct ovl_opt_set set; -}; - static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param) { int err = 0; @@ -491,7 +497,6 @@ static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param) struct ovl_fs *ofs = fc->s_fs_info; struct ovl_config *config = &ofs->config; struct ovl_fs_context *ctx = fc->fs_private; - char *dup; int opt; /* @@ -508,34 +513,13 @@ static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param) switch (opt) { case Opt_lowerdir: - dup = kstrdup(param->string, GFP_KERNEL); - if (!dup) { - err = -ENOMEM; - break; - } - - kfree(config->lowerdir); - config->lowerdir = dup; + err = ovl_parse_param_lowerdir(param->string, fc); break; case Opt_upperdir: - dup = kstrdup(param->string, GFP_KERNEL); - if (!dup) { - err = -ENOMEM; - break; - } - - kfree(config->upperdir); - config->upperdir = dup; - break; + fallthrough; case Opt_workdir: - dup = kstrdup(param->string, GFP_KERNEL); - if (!dup) { - err = -ENOMEM; - break; - } - - kfree(config->workdir); - config->workdir = dup; + err = ovl_parse_param_upperdir(param->string, fc, + (Opt_workdir == opt)); break; case Opt_default_permissions: config->default_permissions = true; @@ -587,6 +571,11 @@ static int ovl_fs_params_verify(const struct ovl_fs_context *ctx, { struct ovl_opt_set set = ctx->set; + if (ctx->nr_data > 0 && !config->metacopy) { + pr_err("lower data-only dirs require metacopy support.\n"); + return -EINVAL; + } + /* Workdir/index are useless in non-upper mount */ if (!config->upperdir) { if (config->workdir) { @@ -799,69 +788,6 @@ out_err: goto out_unlock; } -static void ovl_unescape(char *s) -{ - char *d = s; - - for (;; s++, d++) { - if (*s == '\\') - s++; - *d = *s; - if (!*s) - break; - } -} - -static int ovl_mount_dir_noesc(const char *name, struct path *path) -{ - int err = -EINVAL; - - if (!*name) { - pr_err("empty lowerdir\n"); - goto out; - } - err = kern_path(name, LOOKUP_FOLLOW, path); - if (err) { - pr_err("failed to resolve '%s': %i\n", name, err); - goto out; - } - err = -EINVAL; - if (ovl_dentry_weird(path->dentry)) { - pr_err("filesystem on '%s' not supported\n", name); - goto out_put; - } - if (!d_is_dir(path->dentry)) { - pr_err("'%s' not a directory\n", name); - goto out_put; - } - return 0; - -out_put: - path_put_init(path); -out: - return err; -} - -static int ovl_mount_dir(const char *name, struct path *path) -{ - int err = -ENOMEM; - char *tmp = kstrdup(name, GFP_KERNEL); - - if (tmp) { - ovl_unescape(tmp); - err = ovl_mount_dir_noesc(tmp, path); - - if (!err && path->dentry->d_flags & DCACHE_OP_REAL) { - pr_err("filesystem on '%s' not supported as upperdir\n", - tmp); - path_put_init(path); - err = -EINVAL; - } - kfree(tmp); - } - return err; -} - static int ovl_check_namelen(const struct path *path, struct ovl_fs *ofs, const char *name) { @@ -882,10 +808,6 @@ static int ovl_lower_dir(const char *name, struct path *path, int fh_type; int err; - err = ovl_mount_dir_noesc(name, path); - if (err) - return err; - err = ovl_check_namelen(path, ofs, name); if (err) return err; @@ -934,26 +856,6 @@ static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir) return ok; } -static unsigned int ovl_split_lowerdirs(char *str) -{ - unsigned int ctr = 1; - char *s, *d; - - for (s = d = str;; s++, d++) { - if (*s == '\\') { - s++; - } else if (*s == ':') { - *d = '\0'; - ctr++; - continue; - } - *d = *s; - if (!*s) - break; - } - return ctr; -} - static int ovl_own_xattr_get(const struct xattr_handler *handler, struct dentry *dentry, struct inode *inode, const char *name, void *buffer, size_t size) @@ -1054,15 +956,12 @@ static int ovl_report_in_use(struct ovl_fs *ofs, const char *name) } static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs, - struct ovl_layer *upper_layer, struct path *upperpath) + struct ovl_layer *upper_layer, + const struct path *upperpath) { struct vfsmount *upper_mnt; int err; - err = ovl_mount_dir(ofs->config.upperdir, upperpath); - if (err) - goto out; - /* Upperdir path should not be r/o */ if (__mnt_is_readonly(upperpath->mnt)) { pr_err("upper fs is r/o, try multi-lower layers mount\n"); @@ -1092,6 +991,11 @@ static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs, upper_layer->idx = 0; upper_layer->fsid = 0; + err = -ENOMEM; + upper_layer->name = kstrdup(ofs->config.upperdir, GFP_KERNEL); + if (!upper_layer->name) + goto out; + /* * Inherit SB_NOSEC flag from upperdir. * @@ -1356,46 +1260,37 @@ out: } static int ovl_get_workdir(struct super_block *sb, struct ovl_fs *ofs, - const struct path *upperpath) + const struct path *upperpath, + const struct path *workpath) { int err; - struct path workpath = { }; - - err = ovl_mount_dir(ofs->config.workdir, &workpath); - if (err) - goto out; err = -EINVAL; - if (upperpath->mnt != workpath.mnt) { + if (upperpath->mnt != workpath->mnt) { pr_err("workdir and upperdir must reside under the same mount\n"); - goto out; + return err; } - if (!ovl_workdir_ok(workpath.dentry, upperpath->dentry)) { + if (!ovl_workdir_ok(workpath->dentry, upperpath->dentry)) { pr_err("workdir and upperdir must be separate subtrees\n"); - goto out; + return err; } - ofs->workbasedir = dget(workpath.dentry); + ofs->workbasedir = dget(workpath->dentry); if (ovl_inuse_trylock(ofs->workbasedir)) { ofs->workdir_locked = true; } else { err = ovl_report_in_use(ofs, "workdir"); if (err) - goto out; + return err; } err = ovl_setup_trap(sb, ofs->workbasedir, &ofs->workbasedir_trap, "workdir"); if (err) - goto out; - - err = ovl_make_workdir(sb, ofs, &workpath); - -out: - path_put(&workpath); + return err; - return err; + return ovl_make_workdir(sb, ofs, workpath); } static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs, @@ -1559,13 +1454,13 @@ static int ovl_get_data_fsid(struct ovl_fs *ofs) static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, - struct path *stack, unsigned int numlower, - struct ovl_layer *layers) + struct ovl_fs_context *ctx, struct ovl_layer *layers) { int err; unsigned int i; + size_t nr_merged_lower; - ofs->fs = kcalloc(numlower + 2, sizeof(struct ovl_sb), GFP_KERNEL); + ofs->fs = kcalloc(ctx->nr + 2, sizeof(struct ovl_sb), GFP_KERNEL); if (ofs->fs == NULL) return -ENOMEM; @@ -1592,13 +1487,15 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, ofs->fs[0].is_lower = false; } - for (i = 0; i < numlower; i++) { + nr_merged_lower = ctx->nr - ctx->nr_data; + for (i = 0; i < ctx->nr; i++) { + struct ovl_fs_context_layer *l = &ctx->lower[i]; struct vfsmount *mnt; struct inode *trap; int fsid; - if (i < numlower - ofs->numdatalayer) - fsid = ovl_get_fsid(ofs, &stack[i]); + if (i < nr_merged_lower) + fsid = ovl_get_fsid(ofs, &l->path); else fsid = ovl_get_data_fsid(ofs); if (fsid < 0) @@ -1611,11 +1508,11 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, * the upperdir/workdir is in fact in-use by our * upperdir/workdir. */ - err = ovl_setup_trap(sb, stack[i].dentry, &trap, "lowerdir"); + err = ovl_setup_trap(sb, l->path.dentry, &trap, "lowerdir"); if (err) return err; - if (ovl_is_inuse(stack[i].dentry)) { + if (ovl_is_inuse(l->path.dentry)) { err = ovl_report_in_use(ofs, "lowerdir"); if (err) { iput(trap); @@ -1623,7 +1520,7 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, } } - mnt = clone_private_mount(&stack[i]); + mnt = clone_private_mount(&l->path); err = PTR_ERR(mnt); if (IS_ERR(mnt)) { pr_err("failed to clone lowerpath\n"); @@ -1642,6 +1539,8 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, layers[ofs->numlayer].idx = ofs->numlayer; layers[ofs->numlayer].fsid = fsid; layers[ofs->numlayer].fs = &ofs->fs[fsid]; + layers[ofs->numlayer].name = l->name; + l->name = NULL; ofs->numlayer++; ofs->fs[fsid].is_lower = true; } @@ -1682,104 +1581,59 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs, } static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb, - const char *lower, unsigned int numlower, - struct ovl_fs *ofs, struct ovl_layer *layers) + struct ovl_fs_context *ctx, + struct ovl_fs *ofs, + struct ovl_layer *layers) { int err; - struct path *stack = NULL; - struct ovl_path *lowerstack; - unsigned int numlowerdata = 0; unsigned int i; + size_t nr_merged_lower; struct ovl_entry *oe; + struct ovl_path *lowerstack; + + struct ovl_fs_context_layer *l; - if (!ofs->config.upperdir && numlower == 1) { + if (!ofs->config.upperdir && ctx->nr == 1) { pr_err("at least 2 lowerdir are needed while upperdir nonexistent\n"); return ERR_PTR(-EINVAL); } - stack = kcalloc(numlower, sizeof(struct path), GFP_KERNEL); - if (!stack) - return ERR_PTR(-ENOMEM); + err = -EINVAL; + for (i = 0; i < ctx->nr; i++) { + l = &ctx->lower[i]; - for (i = 0; i < numlower;) { - err = ovl_lower_dir(lower, &stack[i], ofs, &sb->s_stack_depth); + err = ovl_lower_dir(l->name, &l->path, ofs, &sb->s_stack_depth); if (err) - goto out_err; - - lower = strchr(lower, '\0') + 1; - - i++; - if (i == numlower) - break; - - err = -EINVAL; - /* - * Empty lower layer path could mean :: separator that indicates - * a data-only lower data. - * Several data-only layers are allowed, but they all need to be - * at the bottom of the stack. - */ - if (*lower) { - /* normal lower dir */ - if (numlowerdata) { - pr_err("lower data-only dirs must be at the bottom of the stack.\n"); - goto out_err; - } - } else { - /* data-only lower dir */ - if (!ofs->config.metacopy) { - pr_err("lower data-only dirs require metacopy support.\n"); - goto out_err; - } - if (i == numlower - 1) { - pr_err("lowerdir argument must not end with double colon.\n"); - goto out_err; - } - lower++; - numlower--; - numlowerdata++; - } - } - - if (numlowerdata) { - ofs->numdatalayer = numlowerdata; - pr_info("using the lowest %d of %d lowerdirs as data layers\n", - numlowerdata, numlower); + return ERR_PTR(err); } err = -EINVAL; sb->s_stack_depth++; if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) { pr_err("maximum fs stacking depth exceeded\n"); - goto out_err; + return ERR_PTR(err); } - err = ovl_get_layers(sb, ofs, stack, numlower, layers); + err = ovl_get_layers(sb, ofs, ctx, layers); if (err) - goto out_err; + return ERR_PTR(err); err = -ENOMEM; /* Data-only layers are not merged in root directory */ - oe = ovl_alloc_entry(numlower - numlowerdata); + nr_merged_lower = ctx->nr - ctx->nr_data; + oe = ovl_alloc_entry(nr_merged_lower); if (!oe) - goto out_err; + return ERR_PTR(err); lowerstack = ovl_lowerstack(oe); - for (i = 0; i < numlower - numlowerdata; i++) { - lowerstack[i].dentry = dget(stack[i].dentry); - lowerstack[i].layer = &ofs->layers[i+1]; + for (i = 0; i < nr_merged_lower; i++) { + l = &ctx->lower[i]; + lowerstack[i].dentry = dget(l->path.dentry); + lowerstack[i].layer = &ofs->layers[i + 1]; } - -out: - for (i = 0; i < numlower; i++) - path_put(&stack[i]); - kfree(stack); + ofs->numdatalayer = ctx->nr_data; return oe; - -out_err: - oe = ERR_PTR(err); - goto out; } /* @@ -1897,13 +1751,10 @@ static int ovl_fill_super(struct super_block *sb, struct fs_context *fc) { struct ovl_fs *ofs = sb->s_fs_info; struct ovl_fs_context *ctx = fc->fs_private; - struct path upperpath = {}; struct dentry *root_dentry; struct ovl_entry *oe; struct ovl_layer *layers; struct cred *cred; - char *splitlower = NULL; - unsigned int numlower; int err; err = -EIO; @@ -1922,27 +1773,14 @@ static int ovl_fill_super(struct super_block *sb, struct fs_context *fc) goto out_err; err = -EINVAL; - if (!ofs->config.lowerdir) { + if (ctx->nr == 0) { if (!(fc->sb_flags & SB_SILENT)) pr_err("missing 'lowerdir'\n"); goto out_err; } err = -ENOMEM; - splitlower = kstrdup(ofs->config.lowerdir, GFP_KERNEL); - if (!splitlower) - goto out_err; - - err = -EINVAL; - numlower = ovl_split_lowerdirs(splitlower); - if (numlower > OVL_MAX_STACK) { - pr_err("too many lower directories, limit is %d\n", - OVL_MAX_STACK); - goto out_err; - } - - err = -ENOMEM; - layers = kcalloc(numlower + 1, sizeof(struct ovl_layer), GFP_KERNEL); + layers = kcalloc(ctx->nr + 1, sizeof(struct ovl_layer), GFP_KERNEL); if (!layers) goto out_err; @@ -1974,7 +1812,7 @@ static int ovl_fill_super(struct super_block *sb, struct fs_context *fc) goto out_err; } - err = ovl_get_upper(sb, ofs, &layers[0], &upperpath); + err = ovl_get_upper(sb, ofs, &layers[0], &ctx->upper); if (err) goto out_err; @@ -1988,7 +1826,7 @@ static int ovl_fill_super(struct super_block *sb, struct fs_context *fc) } } - err = ovl_get_workdir(sb, ofs, &upperpath); + err = ovl_get_workdir(sb, ofs, &ctx->upper, &ctx->work); if (err) goto out_err; @@ -1998,7 +1836,7 @@ static int ovl_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_stack_depth = upper_sb->s_stack_depth; sb->s_time_gran = upper_sb->s_time_gran; } - oe = ovl_get_lowerstack(sb, splitlower, numlower, ofs, layers); + oe = ovl_get_lowerstack(sb, ctx, ofs, layers); err = PTR_ERR(oe); if (IS_ERR(oe)) goto out_err; @@ -2013,7 +1851,7 @@ static int ovl_fill_super(struct super_block *sb, struct fs_context *fc) } if (!ovl_force_readonly(ofs) && ofs->config.index) { - err = ovl_get_indexdir(sb, ofs, oe, &upperpath); + err = ovl_get_indexdir(sb, ofs, oe, &ctx->upper); if (err) goto out_free_oe; @@ -2054,13 +1892,10 @@ static int ovl_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_iflags |= SB_I_SKIP_SYNC; err = -ENOMEM; - root_dentry = ovl_get_root(sb, upperpath.dentry, oe); + root_dentry = ovl_get_root(sb, ctx->upper.dentry, oe); if (!root_dentry) goto out_free_oe; - path_put(&upperpath); - kfree(splitlower); - sb->s_root = root_dentry; return 0; @@ -2080,6 +1915,10 @@ static int ovl_get_tree(struct fs_context *fc) static inline void ovl_fs_context_free(struct ovl_fs_context *ctx) { + ovl_parse_param_drop_lowerdir(ctx); + path_put(&ctx->upper); + path_put(&ctx->work); + kfree(ctx->lower); kfree(ctx); } @@ -2124,11 +1963,18 @@ static int ovl_init_fs_context(struct fs_context *fc) if (!ctx) return -ENOMEM; + /* + * By default we allocate for three lower layers. It's likely + * that it'll cover most users. + */ + ctx->lower = kmalloc_array(3, sizeof(*ctx->lower), GFP_KERNEL_ACCOUNT); + if (!ctx->lower) + goto out_err; + ctx->capacity = 3; + ofs = kzalloc(sizeof(struct ovl_fs), GFP_KERNEL); - if (!ofs) { - ovl_fs_context_free(ctx); - return -ENOMEM; - } + if (!ofs) + goto out_err; ofs->config.redirect_mode = ovl_redirect_mode_def(); ofs->config.index = ovl_index_def; @@ -2141,6 +1987,11 @@ static int ovl_init_fs_context(struct fs_context *fc) fc->fs_private = ctx; fc->ops = &ovl_context_ops; return 0; + +out_err: + ovl_fs_context_free(ctx); + return -ENOMEM; + } static struct file_system_type ovl_fs_type = { -- cgit v1.2.3-58-ga151 From ceecc2d87f007f9fc34e847401282111c0c29786 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 20 Jun 2023 13:42:38 +0200 Subject: ovl: reserve ability to reconfigure mount options with new mount api Using the old mount api to remount an overlayfs superblock via mount(MS_REMOUNT) all mount options will be silently ignored. For example, if you create an overlayfs mount: mount -t overlay overlay -o lowerdir=/mnt/a:/mnt/b,upperdir=/mnt/upper,workdir=/mnt/work /mnt/merged and then issue a remount via: # force mount(8) to use mount(2) export LIBMOUNT_FORCE_MOUNT2=always mount -t overlay overlay -o remount,WOOTWOOT,lowerdir=/DOESNT-EXIST /mnt/merged with completely nonsensical mount options whatsoever it will succeed nonetheless. This prevents us from every changing any mount options we might introduce in the future that could reasonably be changed during a remount. We don't need to carry this issue into the new mount api port. Similar to FUSE we can use the fs_context::oldapi member to figure out that this is a request coming through the legacy mount api. If we detect it we continue silently ignoring all mount options. But for the new mount api we simply report that mount options cannot currently be changed. This will allow us to potentially alter mount properties for new or even old properties. It any case, silently ignoring everything is not something new apis should do. Signed-off-by: Christian Brauner Signed-off-by: Amir Goldstein --- fs/overlayfs/super.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) (limited to 'fs/overlayfs') diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index ed4b35c9d647..c14c52560fd6 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -499,13 +499,24 @@ static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param) struct ovl_fs_context *ctx = fc->fs_private; int opt; - /* - * On remount overlayfs has always ignored all mount options no - * matter if malformed or not so for backwards compatibility we - * do the same here. - */ - if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) - return 0; + if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) { + /* + * On remount overlayfs has always ignored all mount + * options no matter if malformed or not so for + * backwards compatibility we do the same here. + */ + if (fc->oldapi) + return 0; + + /* + * Give us the freedom to allow changing mount options + * with the new mount api in the future. So instead of + * silently ignoring everything we report a proper + * error. This is only visible for users of the new + * mount api. + */ + return invalfc(fc, "No changes allowed in reconfigure"); + } opt = fs_parse(fc, ovl_parameter_spec, param, &result); if (opt < 0) -- cgit v1.2.3-58-ga151