Re: ovl_copy_xattr() can write uninitialised heap into the upper file's xattr
Amir Goldstein <[email protected]>
| Newsgroups | org.kernel.vger.linux-unionfs,org.kernel.vger.linux-fsdevel |
|---|---|
| Message-ID | <CAOQ4uxhX_neohTAjZDOT0oSWC-JNiqNWqnfE5YSa7Ln05QPQ-A@mail.gmail.com> |
On Mon, Aug 24, 2026 at 11:49 AM Miklos Szeredi <[email protected]> wrote: > > On Mon, 24 Aug 2026 at 11:30, Amir Goldstein <[email protected]> wrote: > > > It's not really a retry loop, it is an opportunistic buffer auto grow > > loop, see > > e4ad29fa0d22 ("ovl: use a minimal buffer in ovl_copy_xattr") > > Ah, okay. > > The logic is too complicated, though. > > I'd still go with a separate helper that gets passed (..., void > **bufp, size_t *bufsizep). Single caller of ovl_getxattr_value() can > also be converted to that. OK. something like this? Fahad, can you test it with your fuse reproducer? Thanks, Amir.
0001-ovl-fortify-ovl_copy_xattr-against-underlying-layer-.patch
(text/x-patch, 4.3 KB)
From b983c2be3f7d404b362aa43dfd4f3c6c2c29ee80 Mon Sep 17 00:00:00 2001 From: Amir Goldstein <[email protected]> Date: Mon, 24 Aug 2026 13:38:29 +0200 Subject: [PATCH] ovl: fortify ovl_copy_xattr() against underlying layer modifications Adapt ovl_getxattr_value() to be used from ovl_copy_xattr(), by allowing to pass it a pre-allocated buffer, which can be resized and by handling the ENODATA/EOPNOTSUPP return codes in the caller. Check that the size returned from getxattr with non-NULL buffer matches the size queried with NULL buffer. If it doesn't that means underlying layer modification so return -EIO. Reported-by: Fahad Alharbi <[email protected]> Suggested-by: Miklos Szeredi <[email protected]> Link: https://lore.kernel.org/linux-fsdevel/CAJfpegsVoUyqy0dYkTvBGRZvgdv_nmzmCw_uF+LP-Vq3EhEkSg@mail.gmail.com/ Fixes: e4ad29fa0d22 ("ovl: use a minimal buffer in ovl_copy_xattr") Signed-off-by: Amir Goldstein <[email protected]> --- fs/overlayfs/copy_up.c | 64 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index e963701b4c877..eee22581b14f1 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -72,6 +72,9 @@ static int ovl_copy_acl(struct ovl_fs *ofs, const struct path *path, return err; } +static ssize_t ovl_getxattr_value(const struct path *path, char *name, + char **value, ssize_t *value_size); + int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct dentry *new) { struct dentry *old = oldpath->dentry; @@ -129,30 +132,12 @@ int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct de break; } -retry: - size = ovl_do_getxattr(oldpath, name, value, value_size); - if (size == -ERANGE) - size = ovl_do_getxattr(oldpath, name, NULL, 0); - + size = ovl_getxattr_value(oldpath, name, &value, &value_size); if (size < 0) { error = size; break; } - if (size > value_size) { - void *new; - - new = kvmalloc(size, GFP_KERNEL); - if (!new) { - error = -ENOMEM; - break; - } - kvfree(value); - value = new; - value_size = size; - goto retry; - } - error = ovl_do_setxattr(OVL_FS(sb), new, name, value, size, 0); if (error) { if (error != -EOPNOTSUPP || ovl_must_copy_xattr(name)) @@ -1055,26 +1040,39 @@ static bool ovl_need_meta_copy_up(struct dentry *dentry, umode_t mode, return true; } -static ssize_t ovl_getxattr_value(const struct path *path, char *name, char **value) +static ssize_t ovl_getxattr_value(const struct path *path, char *name, + char **value, ssize_t *value_size) { ssize_t res; char *buf; - res = ovl_do_getxattr(path, name, NULL, 0); - if (res == -ENODATA || res == -EOPNOTSUPP) - res = 0; + res = ovl_do_getxattr(path, name, *value, *value_size); + if (res == -ERANGE) { + res = ovl_do_getxattr(path, name, NULL, 0); + /* Xattr changed underneath us? */ + if (res >= 0 && res <= *value_size) + return -EIO; + } - if (res > 0) { - buf = kzalloc(res, GFP_KERNEL); + if (res > *value_size) { + buf = kvmalloc(res, GFP_KERNEL); if (!buf) return -ENOMEM; res = ovl_do_getxattr(path, name, buf, res); - if (res < 0) - kfree(buf); - else - *value = buf; + if (res < 0) { + kvfree(buf); + /* Xattr changed underneath us? */ + if (res == -ERANGE) + return -EIO; + return res; + } + + kvfree(*value); + *value = buf; + *value_size = res; } + return res; } @@ -1085,7 +1083,7 @@ static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c) struct path upperpath; int err; char *capability = NULL; - ssize_t cap_size; + ssize_t cap_size = 0; ovl_path_upper(c->dentry, &upperpath); if (WARN_ON(upperpath.dentry == NULL)) @@ -1093,7 +1091,9 @@ static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c) if (c->stat.size) { err = cap_size = ovl_getxattr_value(&upperpath, XATTR_NAME_CAPS, - &capability); + &capability, &cap_size); + if (cap_size == -ENODATA || cap_size == -EOPNOTSUPP) + cap_size = 0; if (cap_size < 0) goto out; } @@ -1123,7 +1123,7 @@ static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c) ovl_clear_flag(OVL_VERIFIED_DIGEST, d_inode(c->dentry)); ovl_set_upperdata(d_inode(c->dentry)); out_free: - kfree(capability); + kvfree(capability); out: return err; } -- 2.55.0