* [PATCH 01/13] ovl: don't warn when the lazy lowerdata lookup fails in ovl_d_real()
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:12 ` sashiko-bot
2026-11-15 14:54 ` Amir Goldstein
2026-11-15 14:01 ` [PATCH 02/13] fs: add d_real_data_inode() and d_real_metadata_inode() Christian Brauner
` (11 subsequent siblings)
12 siblings, 2 replies; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable), stable, Sashiko
ovl_d_real() looks up the data of a metacopy file in a data-only layer
itself if nothing has done that yet. If that lookup fails it WARN()s and
returns the overlayfs dentry.
Commit 42dd69ae1af4 ("ovl: implement lazy lookup of lowerdata in
data-only layers") assumed that the file would be read before
trace_uprobe places a uprobe on it so the data would be there already.
But nothing actually guarantees that. trace_uprobe takes the path from
kern_path() and calls d_real_inode() on it when the event is created and
again when it is enabled. So a uprobe on a metacopy file whose redirect
points at nothing warns twice:
ovl_d_real(/f, 0): real dentry not found
WARNING: fs/overlayfs/super.c:79 at ovl_d_real+0xc9/0x210, CPU#2: sh/279
Call Trace:
<TASK>
__trace_uprobe_create+0x1869/0x25b0
trace_probe_create+0xcc/0xe0
dyn_event_create+0x46/0x70
create_or_delete_trace_uprobe+0x3c/0x70
trace_parse_run_command+0x1a6/0x320
vfs_write+0x1cd/0xfa0
ksys_write+0x116/0x250
do_syscall_64+0x10e/0x5e0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
bpf's uprobe_multi does the same.
A failed lookup isn't a bug though. The data can be missing, its verity
digest can be wrong or a signal can interrupt the wait for the inode
lock. ovl_open() simply fails in that case and
ovl_maybe_lookup_lowerdata() already warns ratelimited about failed
lookups. So return the overlayfs dentry without warning. That's what the
WARN() path returned anyway.
Fixes: 42dd69ae1af4 ("ovl: implement lazy lookup of lowerdata in data-only layers")
Fixes: 184996e92e86 ("ovl: Validate verity xattr when resolving lowerdata")
Cc: stable@vger.kernel.org # v6.5+
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/all/sashiko-outbox-214581@kernel.org
Reported-by: Chris Down <chris@chrisdown.name>
Closes: https://lore.kernel.org/all/20261114120557.4bc1eb6e57218cdc@chrisdown.name
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/overlayfs/super.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 08695a99f4f93..1985b553a47b7 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -58,15 +58,13 @@ static struct dentry *ovl_d_real(struct dentry *dentry, enum d_real_type type)
/*
* Best effort lazy lookup of lowerdata for D_REAL_DATA case to return
- * the real lowerdata dentry. The only current caller of d_real() with
- * D_REAL_DATA 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.
+ * the real lowerdata dentry. Callers may ask for files that were never
+ * opened, e.g. for uprobes or O_PATH files, so the lookup can fail
+ * here. Return the overlayfs dentry then.
*/
err = ovl_verify_lowerdata(dentry);
if (err)
- goto bug;
+ return dentry;
lower = ovl_dentry_lowerdata(dentry);
if (!lower)
goto bug;
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 01/13] ovl: don't warn when the lazy lowerdata lookup fails in ovl_d_real()
2026-11-15 14:01 ` [PATCH 01/13] ovl: don't warn when the lazy lowerdata lookup fails in ovl_d_real() Christian Brauner
@ 2026-11-15 14:12 ` sashiko-bot
2026-11-15 14:54 ` Amir Goldstein
1 sibling, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-11-15 14:12 UTC (permalink / raw)
Cc: linux-security-module
> ovl_d_real() looks up the data of a metacopy file in a data-only layer
> itself if nothing has done that yet. If that lookup fails it WARN()s and
> returns the overlayfs dentry.
>
> Commit 42dd69ae1af4 ("ovl: implement lazy lookup of lowerdata in
> data-only layers") assumed that the file would be read before
> trace_uprobe places a uprobe on it so the data would be there already.
>
> But nothing actually guarantees that. trace_uprobe takes the path from
> kern_path() and calls d_real_inode() on it when the event is created and
> again when it is enabled. So a uprobe on a metacopy file whose redirect
> points at nothing warns twice:
>
> ovl_d_real(/f, 0): real dentry not found
> WARNING: fs/overlayfs/super.c:79 at ovl_d_real+0xc9/0x210, CPU#2: sh/279
> [ ... ]
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Closes: https://lore.kernel.org/all/sashiko-outbox-214581@kernel.org
> Reported-by: Chris Down <chris@chrisdown.name>
> Closes: https://lore.kernel.org/all/20261114120557.4bc1eb6e57218cdc@chrisdown.name
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115-work-ovl-d_real-v1-0-ca155efbcb2e@kernel.org?part=1
^ permalink raw reply [flat|nested] 32+ messages in thread* Re: [PATCH 01/13] ovl: don't warn when the lazy lowerdata lookup fails in ovl_d_real()
2026-11-15 14:01 ` [PATCH 01/13] ovl: don't warn when the lazy lowerdata lookup fails in ovl_d_real() Christian Brauner
2026-11-15 14:12 ` sashiko-bot
@ 2026-11-15 14:54 ` Amir Goldstein
1 sibling, 0 replies; 32+ messages in thread
From: Amir Goldstein @ 2026-11-15 14:54 UTC (permalink / raw)
To: Christian Brauner
Cc: linux-fsdevel, Chris Down, Miklos Szeredi, linux-unionfs,
linux-kernel, bpf, linux-trace-kernel, linux-integrity,
linux-security-module, linux-mm, stable, Sashiko
On Sun, Nov 15, 2026 at 3:02 PM Christian Brauner <brauner@kernel.org> wrote:
>
> ovl_d_real() looks up the data of a metacopy file in a data-only layer
> itself if nothing has done that yet. If that lookup fails it WARN()s and
> returns the overlayfs dentry.
>
> Commit 42dd69ae1af4 ("ovl: implement lazy lookup of lowerdata in
> data-only layers") assumed that the file would be read before
> trace_uprobe places a uprobe on it so the data would be there already.
>
> But nothing actually guarantees that. trace_uprobe takes the path from
> kern_path() and calls d_real_inode() on it when the event is created and
> again when it is enabled. So a uprobe on a metacopy file whose redirect
> points at nothing warns twice:
>
> ovl_d_real(/f, 0): real dentry not found
> WARNING: fs/overlayfs/super.c:79 at ovl_d_real+0xc9/0x210, CPU#2: sh/279
> Call Trace:
> <TASK>
> __trace_uprobe_create+0x1869/0x25b0
> trace_probe_create+0xcc/0xe0
> dyn_event_create+0x46/0x70
> create_or_delete_trace_uprobe+0x3c/0x70
> trace_parse_run_command+0x1a6/0x320
> vfs_write+0x1cd/0xfa0
> ksys_write+0x116/0x250
> do_syscall_64+0x10e/0x5e0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> </TASK>
>
> bpf's uprobe_multi does the same.
>
> A failed lookup isn't a bug though. The data can be missing, its verity
> digest can be wrong or a signal can interrupt the wait for the inode
> lock. ovl_open() simply fails in that case and
> ovl_maybe_lookup_lowerdata() already warns ratelimited about failed
> lookups. So return the overlayfs dentry without warning. That's what the
> WARN() path returned anyway.
>
> Fixes: 42dd69ae1af4 ("ovl: implement lazy lookup of lowerdata in data-only layers")
> Fixes: 184996e92e86 ("ovl: Validate verity xattr when resolving lowerdata")
Nit: 42dd69ae1af4 came first and opened the possibility of goto bug
and then 184996e92e86 came later and depends on 42dd69ae1af4.
I don't really see the benefit from stable POV to name the later as Fixes
> Cc: stable@vger.kernel.org # v6.5+
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Closes: https://lore.kernel.org/all/sashiko-outbox-214581@kernel.org
> Reported-by: Chris Down <chris@chrisdown.name>
> Closes: https://lore.kernel.org/all/20261114120557.4bc1eb6e57218cdc@chrisdown.name
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> ---
> fs/overlayfs/super.c | 10 ++++------
> 1 file changed, 4 insertions(+), 6 deletions(-)
>
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index 08695a99f4f93..1985b553a47b7 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -58,15 +58,13 @@ static struct dentry *ovl_d_real(struct dentry *dentry, enum d_real_type type)
>
> /*
> * Best effort lazy lookup of lowerdata for D_REAL_DATA case to return
> - * the real lowerdata dentry. The only current caller of d_real() with
> - * D_REAL_DATA 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.
> + * the real lowerdata dentry. Callers may ask for files that were never
> + * opened, e.g. for uprobes or O_PATH files, so the lookup can fail
> + * here. Return the overlayfs dentry then.
> */
> err = ovl_verify_lowerdata(dentry);
> if (err)
> - goto bug;
> + return dentry;
> lower = ovl_dentry_lowerdata(dentry);
> if (!lower)
> goto bug;
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 02/13] fs: add d_real_data_inode() and d_real_metadata_inode()
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
2026-11-15 14:01 ` [PATCH 01/13] ovl: don't warn when the lazy lowerdata lookup fails in ovl_d_real() Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:05 ` sashiko-bot
2026-11-15 15:00 ` Amir Goldstein
2026-11-15 14:01 ` [PATCH 03/13] tracing/uprobes: handle errors from resolving the real inode Christian Brauner
` (10 subsequent siblings)
12 siblings, 2 replies; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
Currently, d_real() can't fail. When overlayfs fails to look up the data
of a metacopy file in a data-only layer it hands back the overlayfs
dentry and callers silently work with the overlayfs inode. That's more
problematic than current callers realize.
For trace_uprobe and bpf's uprobe_multi it means uprobe_register() fails
with an -EIO that has nothing to do with the actual problem. For IMA it
means the stacked change detection is skipped and for IPE the file is
evaluated against an inode that has none of the properties of the real
file.
Teach d_real() to fail and teach all callers to handle errors. This adds
two helpers for the callers to switch over to:
- d_real_data_inode() returns the inode hosting the data or an ERR_PTR()
if that can't be resolved.
- d_real_metadata_inode() returns the inode hosting the metadata. It
never has to look anything up and so never fails (but no promises for
the future).
d_real() can't fail yet so this is a pure preparation. d_real_inode()
stays around until all callers are converted.
No functional changes.
Suggested-by: Amir Goldstein <amir73il@gmail.com>
Link: https://lore.kernel.org/all/CAOQ4uxhGxpBtMZ5kfuzR1tQAewiecoX7UjXQRo91QjJHH9T_Yw@mail.gmail.com
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
include/linux/dcache.h | 44 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 84b0cfe4ed0e7..674bc50b81827 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -3,6 +3,7 @@
#define __LINUX_DCACHE_H
#include <linux/atomic.h>
+#include <linux/err.h>
#include <linux/list.h>
#include <linux/math.h>
#include <linux/rculist.h>
@@ -660,6 +661,49 @@ static inline struct inode *d_real_inode(const struct dentry *dentry)
return d_inode(d_real((struct dentry *) dentry, D_REAL_DATA));
}
+/**
+ * d_real_data_inode - Return the real inode hosting the data
+ * @dentry: The dentry to query
+ *
+ * If @dentry is a regular file on a union/overlay, then return the
+ * underlying, real inode that hosts its data. Otherwise return d_inode().
+ *
+ * The union/overlay may have to look up the data first. This can sleep and it
+ * can fail, e.g., because the data is missing or because its verity digest
+ * doesn't match.
+ *
+ * Return: The real inode hosting the data of @dentry, or an ERR_PTR() if it
+ * can't be resolved.
+ */
+static inline struct inode *d_real_data_inode(const struct dentry *dentry)
+{
+ struct dentry *real;
+
+ /* This usage of d_real() results in const dentry */
+ real = d_real((struct dentry *)dentry, D_REAL_DATA);
+ if (IS_ERR(real))
+ return ERR_CAST(real);
+ return d_inode(real);
+}
+
+/**
+ * d_real_metadata_inode - Return the real inode hosting the metadata
+ * @dentry: The dentry to query
+ *
+ * If @dentry is a regular file on a union/overlay, then return the
+ * underlying, real inode that hosts its metadata. Otherwise return d_inode().
+ *
+ * Unlike d_real_data_inode() this never has to look anything up and so it
+ * never fails.
+ *
+ * Return: The real inode hosting the metadata of @dentry.
+ */
+static inline struct inode *d_real_metadata_inode(const struct dentry *dentry)
+{
+ /* This usage of d_real() results in const dentry */
+ return d_inode(d_real((struct dentry *)dentry, D_REAL_METADATA));
+}
+
struct name_snapshot {
struct qstr name;
union shortname_store inline_name;
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 02/13] fs: add d_real_data_inode() and d_real_metadata_inode()
2026-11-15 14:01 ` [PATCH 02/13] fs: add d_real_data_inode() and d_real_metadata_inode() Christian Brauner
@ 2026-11-15 14:05 ` sashiko-bot
2026-11-15 15:00 ` Amir Goldstein
1 sibling, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-11-15 14:05 UTC (permalink / raw)
Cc: linux-security-module
> Currently, d_real() can't fail. When overlayfs fails to look up the data
> of a metacopy file in a data-only layer it hands back the overlayfs
> dentry and callers silently work with the overlayfs inode. That's more
> problematic than current callers realize.
>
> For trace_uprobe and bpf's uprobe_multi it means uprobe_register() fails
> with an -EIO that has nothing to do with the actual problem. For IMA it
> means the stacked change detection is skipped and for IPE the file is
> evaluated against an inode that has none of the properties of the real
> file.
>
> Teach d_real() to fail and teach all callers to handle errors. This adds
> two helpers for the callers to switch over to:
>
> - d_real_data_inode() returns the inode hosting the data or an ERR_PTR()
> if that can't be resolved.
>
> - d_real_metadata_inode() returns the inode hosting the metadata. It
> never has to look anything up and so never fails (but no promises for
> the future).
>
> d_real() can't fail yet so this is a pure preparation. d_real_inode()
> stays around until all callers are converted.
>
> No functional changes.
>
> Suggested-by: Amir Goldstein <amir73il@gmail.com>
> Link: https://lore.kernel.org/all/CAOQ4uxhGxpBtMZ5kfuzR1tQAewiecoX7UjXQRo91QjJHH9T_Yw@mail.gmail.com
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115-work-ovl-d_real-v1-0-ca155efbcb2e@kernel.org?part=2
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 02/13] fs: add d_real_data_inode() and d_real_metadata_inode()
2026-11-15 14:01 ` [PATCH 02/13] fs: add d_real_data_inode() and d_real_metadata_inode() Christian Brauner
2026-11-15 14:05 ` sashiko-bot
@ 2026-11-15 15:00 ` Amir Goldstein
1 sibling, 0 replies; 32+ messages in thread
From: Amir Goldstein @ 2026-11-15 15:00 UTC (permalink / raw)
To: Christian Brauner
Cc: linux-fsdevel, Chris Down, Miklos Szeredi, linux-unionfs,
linux-kernel, bpf, linux-trace-kernel, linux-integrity,
linux-security-module, linux-mm
On Sun, Nov 15, 2026 at 3:02 PM Christian Brauner <brauner@kernel.org> wrote:
>
> Currently, d_real() can't fail. When overlayfs fails to look up the data
> of a metacopy file in a data-only layer it hands back the overlayfs
> dentry and callers silently work with the overlayfs inode. That's more
> problematic than current callers realize.
>
> For trace_uprobe and bpf's uprobe_multi it means uprobe_register() fails
> with an -EIO that has nothing to do with the actual problem. For IMA it
> means the stacked change detection is skipped and for IPE the file is
> evaluated against an inode that has none of the properties of the real
> file.
>
> Teach d_real() to fail and teach all callers to handle errors. This adds
> two helpers for the callers to switch over to:
>
> - d_real_data_inode() returns the inode hosting the data or an ERR_PTR()
> if that can't be resolved.
>
> - d_real_metadata_inode() returns the inode hosting the metadata. It
> never has to look anything up and so never fails (but no promises for
> the future).
About those promises...
>
> d_real() can't fail yet so this is a pure preparation. d_real_inode()
> stays around until all callers are converted.
>
> No functional changes.
>
> Suggested-by: Amir Goldstein <amir73il@gmail.com>
> Link: https://lore.kernel.org/all/CAOQ4uxhGxpBtMZ5kfuzR1tQAewiecoX7UjXQRo91QjJHH9T_Yw@mail.gmail.com
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
> ---
> include/linux/dcache.h | 44 ++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 44 insertions(+)
>
> diff --git a/include/linux/dcache.h b/include/linux/dcache.h
> index 84b0cfe4ed0e7..674bc50b81827 100644
> --- a/include/linux/dcache.h
> +++ b/include/linux/dcache.h
> @@ -3,6 +3,7 @@
> #define __LINUX_DCACHE_H
>
> #include <linux/atomic.h>
> +#include <linux/err.h>
> #include <linux/list.h>
> #include <linux/math.h>
> #include <linux/rculist.h>
> @@ -660,6 +661,49 @@ static inline struct inode *d_real_inode(const struct dentry *dentry)
> return d_inode(d_real((struct dentry *) dentry, D_REAL_DATA));
> }
>
> +/**
> + * d_real_data_inode - Return the real inode hosting the data
> + * @dentry: The dentry to query
> + *
> + * If @dentry is a regular file on a union/overlay, then return the
> + * underlying, real inode that hosts its data. Otherwise return d_inode().
> + *
> + * The union/overlay may have to look up the data first. This can sleep and it
> + * can fail, e.g., because the data is missing or because its verity digest
> + * doesn't match.
> + *
> + * Return: The real inode hosting the data of @dentry, or an ERR_PTR() if it
> + * can't be resolved.
> + */
> +static inline struct inode *d_real_data_inode(const struct dentry *dentry)
> +{
> + struct dentry *real;
> +
> + /* This usage of d_real() results in const dentry */
> + real = d_real((struct dentry *)dentry, D_REAL_DATA);
> + if (IS_ERR(real))
> + return ERR_CAST(real);
> + return d_inode(real);
> +}
> +
> +/**
> + * d_real_metadata_inode - Return the real inode hosting the metadata
> + * @dentry: The dentry to query
> + *
> + * If @dentry is a regular file on a union/overlay, then return the
> + * underlying, real inode that hosts its metadata. Otherwise return d_inode().
> + *
> + * Unlike d_real_data_inode() this never has to look anything up and so it
> + * never fails.
> + *
> + * Return: The real inode hosting the metadata of @dentry.
> + */
> +static inline struct inode *d_real_metadata_inode(const struct dentry *dentry)
> +{
> + /* This usage of d_real() results in const dentry */
> + return d_inode(d_real((struct dentry *)dentry, D_REAL_METADATA));
> +}
> +
Wondering out loud - since you needed to change IMA anyway and need
IMA patches reviews, do you think it is hard to also gracefully deal with errors
from d_real_metadata_inode() and not have to promise that it never fails?
This question is not intended to block your patches.
If there is a reason we should keep this promise for now,
I have no problem with it, so
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Thanks,
Amir.
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 03/13] tracing/uprobes: handle errors from resolving the real inode
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
2026-11-15 14:01 ` [PATCH 01/13] ovl: don't warn when the lazy lowerdata lookup fails in ovl_d_real() Christian Brauner
2026-11-15 14:01 ` [PATCH 02/13] fs: add d_real_data_inode() and d_real_metadata_inode() Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:07 ` sashiko-bot
2026-11-15 14:01 ` [PATCH 04/13] bpf: handle errors from resolving the real inode in uprobe_multi Christian Brauner
` (9 subsequent siblings)
12 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
d_real() is about to be able to fail for D_REAL_DATA. So switch
trace_uprobe from d_real_inode() to d_real_data_inode() and deal with
the error.
Currently, nothing checks whether the data can be resolved when a uprobe
is created. The first resolution happens in validate_ref_ctr_offset()
under event_mutex and the last one when the event is enabled. If the
data of a metacopy file in an overlayfs data-only layer can't be found
the event is created just fine and enabling it fails later with an -EIO
from uprobe_register() because the overlayfs inode has no
->read_folio().
Instead, resolve the inode hosting the data once in
__trace_uprobe_create() right after kern_path(). That happens outside
of event_mutex and if it fails the write to uprobe_events or
dynamic_events fails with the actual error and a new NO_FILE_DATA entry
in the error log. The resolved inode is passed down to
validate_ref_ctr_offset() and trace_uprobe_has_same_uprobe() so we don't
resolve it again under event_mutex.
Both functions still resolve the existing probes they compare against.
If that fails the existing probe can't be on the same inode so we just
skip it. For validate_ref_ctr_offset() that's fine because
uprobe_register() refuses a mismatching reference counter offset for the
same uprobe anyway.
trace_uprobe_enable() returns the error and probe_event_enable()
already unwinds the probes that were enabled before.
While at it, resolve the data in create_local_trace_uprobe() as well.
perf_event_open() of a uprobe PMU event registers the probe right away
so it would fail in trace_uprobe_enable() anyway. But this way the first
resolution happens before perf_uprobe_init() takes event_mutex just like
for uprobes created through tracefs.
This is a no-op until d_real() can actually fail.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
kernel/trace/trace_probe.h | 1 +
kernel/trace/trace_uprobe.c | 57 ++++++++++++++++++++++++++++++++++-----------
2 files changed, 45 insertions(+), 13 deletions(-)
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index d1fb3520700fb..91bf3a66d834a 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -610,6 +610,7 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
C(NO_EVENT_FIELD, "This event field is not found."), \
C(NO_EVENT_INFO, "This requires both group and event name to attach"), \
C(NO_EVENT_NAME, "Event name is not specified"), \
+ C(NO_FILE_DATA, "Failed to resolve the data of the given file"), \
C(NO_GROUP_NAME, "Group name is not specified"), \
C(NO_PTR_STRCT, "This is not a pointer to union/structure."), \
C(NO_REGULAR_FILE, "Not a regular file"), \
diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c
index 22cc3c8181b86..34cd4392decfd 100644
--- a/kernel/trace/trace_uprobe.c
+++ b/kernel/trace/trace_uprobe.c
@@ -415,14 +415,15 @@ static int unregister_trace_uprobe(struct trace_uprobe *tu)
}
static bool trace_uprobe_has_same_uprobe(struct trace_uprobe *orig,
- struct trace_uprobe *comp)
+ struct trace_uprobe *comp,
+ struct inode *comp_inode)
{
struct trace_probe_event *tpe = orig->tp.event;
- struct inode *comp_inode = d_real_inode(comp->path.dentry);
int i;
list_for_each_entry(orig, &tpe->probes, tp.list) {
- if (comp_inode != d_real_inode(orig->path.dentry) ||
+ /* If @orig's data can't be resolved it can't be the same. */
+ if (comp_inode != d_real_data_inode(orig->path.dentry) ||
comp->offset != orig->offset)
continue;
@@ -443,7 +444,8 @@ static bool trace_uprobe_has_same_uprobe(struct trace_uprobe *orig,
return false;
}
-static int append_trace_uprobe(struct trace_uprobe *tu, struct trace_uprobe *to)
+static int append_trace_uprobe(struct trace_uprobe *tu, struct trace_uprobe *to,
+ struct inode *inode)
{
int ret;
@@ -454,7 +456,7 @@ static int append_trace_uprobe(struct trace_uprobe *tu, struct trace_uprobe *to)
trace_probe_log_err(0, DIFF_ARG_TYPE);
return -EEXIST;
}
- if (trace_uprobe_has_same_uprobe(to, tu)) {
+ if (trace_uprobe_has_same_uprobe(to, tu, inode)) {
trace_probe_log_set_index(0);
trace_probe_log_err(0, SAME_PROBE);
return -EEXIST;
@@ -477,14 +479,19 @@ static int append_trace_uprobe(struct trace_uprobe *tu, struct trace_uprobe *to)
* as the new one does not conflict with any other existing
* ones.
*/
-static int validate_ref_ctr_offset(struct trace_uprobe *new)
+static int validate_ref_ctr_offset(struct trace_uprobe *new,
+ struct inode *new_inode)
{
struct dyn_event *pos;
struct trace_uprobe *tmp;
- struct inode *new_inode = d_real_inode(new->path.dentry);
for_each_trace_uprobe(tmp, pos) {
- if (new_inode == d_real_inode(tmp->path.dentry) &&
+ /*
+ * If @tmp's data can't be resolved it isn't on @new_inode.
+ * uprobe_register() still refuses a mismatching reference
+ * counter offset for the same uprobe.
+ */
+ if (new_inode == d_real_data_inode(tmp->path.dentry) &&
new->offset == tmp->offset &&
new->ref_ctr_offset != tmp->ref_ctr_offset) {
pr_warn("Reference counter offset mismatch.");
@@ -495,14 +502,14 @@ static int validate_ref_ctr_offset(struct trace_uprobe *new)
}
/* Register a trace_uprobe and probe_event */
-static int register_trace_uprobe(struct trace_uprobe *tu)
+static int register_trace_uprobe(struct trace_uprobe *tu, struct inode *inode)
{
struct trace_uprobe *old_tu;
int ret;
guard(mutex)(&event_mutex);
- ret = validate_ref_ctr_offset(tu);
+ ret = validate_ref_ctr_offset(tu, inode);
if (ret)
return ret;
@@ -515,7 +522,7 @@ static int register_trace_uprobe(struct trace_uprobe *tu)
trace_probe_log_err(0, DIFF_PROBE_TYPE);
return -EEXIST;
}
- return append_trace_uprobe(tu, old_tu);
+ return append_trace_uprobe(tu, old_tu, inode);
}
ret = register_uprobe_event(tu);
@@ -553,6 +560,7 @@ static int __trace_uprobe_create(int argc, const char **argv)
char *buf __free(kfree) = NULL;
enum probe_print_type ptype;
bool is_return = false;
+ struct inode *inode;
int i, ret;
ref_ctr_offset = 0;
@@ -606,6 +614,17 @@ static int __trace_uprobe_create(int argc, const char **argv)
return -EINVAL;
}
+ /*
+ * Resolve the inode hosting the data now and outside of event_mutex.
+ * If that fails the probe is useless and the user should know right
+ * away instead of when the event is enabled.
+ */
+ inode = d_real_data_inode(path.dentry);
+ if (IS_ERR(inode)) {
+ trace_probe_log_err(0, NO_FILE_DATA);
+ return PTR_ERR(inode);
+ }
+
/* Parse reference counter offset if specified. */
rctr = strchr(arg, '(');
if (rctr) {
@@ -717,7 +736,7 @@ static int __trace_uprobe_create(int argc, const char **argv)
if (ret < 0)
return ret;
- ret = register_trace_uprobe(tu);
+ ret = register_trace_uprobe(tu, inode);
if (!ret)
tu = NULL;
@@ -1105,9 +1124,13 @@ typedef bool (*filter_func_t)(struct uprobe_consumer *self, struct mm_struct *mm
static int trace_uprobe_enable(struct trace_uprobe *tu, filter_func_t filter)
{
- struct inode *inode = d_real_inode(tu->path.dentry);
+ struct inode *inode;
struct uprobe *uprobe;
+ inode = d_real_data_inode(tu->path.dentry);
+ if (IS_ERR(inode))
+ return PTR_ERR(inode);
+
tu->consumer.filter = filter;
uprobe = uprobe_register(inode, tu->offset, tu->ref_ctr_offset, &tu->consumer);
if (IS_ERR(uprobe))
@@ -1630,6 +1653,7 @@ create_local_trace_uprobe(char *name, unsigned long offs,
{
enum probe_print_type ptype;
struct trace_uprobe *tu;
+ struct inode *inode;
struct path path;
int ret;
@@ -1642,6 +1666,13 @@ create_local_trace_uprobe(char *name, unsigned long offs,
return ERR_PTR(-EINVAL);
}
+ /* Fail early if the data of the file can't be resolved. */
+ inode = d_real_data_inode(path.dentry);
+ if (IS_ERR(inode)) {
+ path_put(&path);
+ return ERR_CAST(inode);
+ }
+
/*
* local trace_kprobes are not added to dyn_event, so they are never
* searched in find_trace_kprobe(). Therefore, there is no concern of
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 03/13] tracing/uprobes: handle errors from resolving the real inode
2026-11-15 14:01 ` [PATCH 03/13] tracing/uprobes: handle errors from resolving the real inode Christian Brauner
@ 2026-11-15 14:07 ` sashiko-bot
0 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-11-15 14:07 UTC (permalink / raw)
Cc: linux-security-module
> d_real() is about to be able to fail for D_REAL_DATA. So switch
> trace_uprobe from d_real_inode() to d_real_data_inode() and deal with
> the error.
>
> Currently, nothing checks whether the data can be resolved when a uprobe
> is created. The first resolution happens in validate_ref_ctr_offset()
> under event_mutex and the last one when the event is enabled. If the
> data of a metacopy file in an overlayfs data-only layer can't be found
> the event is created just fine and enabling it fails later with an -EIO
> from uprobe_register() because the overlayfs inode has no
> ->read_folio().
>
> Instead, resolve the inode hosting the data once in
> __trace_uprobe_create() right after kern_path(). That happens outside
> of event_mutex and if it fails the write to uprobe_events or
> [ ... ]
> for uprobes created through tracefs.
>
> This is a no-op until d_real() can actually fail.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115-work-ovl-d_real-v1-0-ca155efbcb2e@kernel.org?part=3
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 04/13] bpf: handle errors from resolving the real inode in uprobe_multi
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
` (2 preceding siblings ...)
2026-11-15 14:01 ` [PATCH 03/13] tracing/uprobes: handle errors from resolving the real inode Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:06 ` sashiko-bot
2026-11-15 14:01 ` [PATCH 05/13] selftests/bpf: handle d_real_data_inode() errors in bpf_testmod Christian Brauner
` (8 subsequent siblings)
12 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
d_real() is about to be able to fail for D_REAL_DATA. So switch
bpf_uprobe_multi_link_attach() from d_real_inode() to
d_real_data_inode() and deal with the error.
Currently, the real inode is resolved again for every single uprobe of
the link. There's no point in doing that. Resolve it once right after
the d_is_reg() check and fail BPF_LINK_CREATE with the error if the data
of the file can't be resolved. Then all uprobes of the link are
registered on the same inode.
This is a no-op until d_real() can actually fail.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
kernel/trace/bpf_trace.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 2aec56d7f3d3e..15f32b971326e 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -3280,6 +3280,7 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr
struct bpf_uprobe *uprobes = NULL;
struct task_struct *task = NULL;
unsigned long __user *uoffsets;
+ struct inode *inode;
u64 __user *ucookies;
unsigned long size;
u32 flags, cnt, i;
@@ -3336,6 +3337,12 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr
goto error_path_put;
}
+ inode = d_real_data_inode(path.dentry);
+ if (IS_ERR(inode)) {
+ err = PTR_ERR(inode);
+ goto error_path_put;
+ }
+
if (pid) {
rcu_read_lock();
task = get_pid_task(find_vpid(pid), PIDTYPE_TGID);
@@ -3394,7 +3401,7 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr
&bpf_uprobe_multi_link_lops, prog, attr->link_create.attach_type);
for (i = 0; i < cnt; i++) {
- uprobes[i].uprobe = uprobe_register(d_real_inode(link->path.dentry),
+ uprobes[i].uprobe = uprobe_register(inode,
uprobes[i].offset,
uprobes[i].ref_ctr_offset,
&uprobes[i].consumer);
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 05/13] selftests/bpf: handle d_real_data_inode() errors in bpf_testmod
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
` (3 preceding siblings ...)
2026-11-15 14:01 ` [PATCH 04/13] bpf: handle errors from resolving the real inode in uprobe_multi Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:05 ` sashiko-bot
2026-11-15 14:01 ` [PATCH 06/13] ima: handle errors from resolving the real inode Christian Brauner
` (7 subsequent siblings)
12 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
d_real() is about to be able to fail for D_REAL_DATA. So switch
testmod_register_uprobe() from d_real_inode() to d_real_data_inode()
and drop the path again if the data of /proc/self/exe can't be
resolved.
That's hard to hit in practice because exec opened the file already but
it is the only modular user and d_real_inode() is going away.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
tools/testing/selftests/bpf/test_kmods/bpf_testmod.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 76fbe64755ef9..9d065a800f655 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -948,6 +948,7 @@ static struct testmod_uprobe uprobe = {
static __always_inline int testmod_register_uprobe(loff_t offset)
{
+ struct inode *inode;
int err = -EBUSY;
if (uprobe.uprobe)
@@ -962,8 +963,14 @@ static __always_inline int testmod_register_uprobe(loff_t offset)
if (err)
goto out;
- uprobe.uprobe = uprobe_register(d_real_inode(uprobe.path.dentry),
- offset, 0, &uprobe.consumer);
+ inode = d_real_data_inode(uprobe.path.dentry);
+ if (IS_ERR(inode)) {
+ err = PTR_ERR(inode);
+ path_put(&uprobe.path);
+ goto out;
+ }
+
+ uprobe.uprobe = uprobe_register(inode, offset, 0, &uprobe.consumer);
if (IS_ERR(uprobe.uprobe)) {
err = PTR_ERR(uprobe.uprobe);
path_put(&uprobe.path);
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 06/13] ima: handle errors from resolving the real inode
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
` (4 preceding siblings ...)
2026-11-15 14:01 ` [PATCH 05/13] selftests/bpf: handle d_real_data_inode() errors in bpf_testmod Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:10 ` sashiko-bot
2026-11-15 14:01 ` [PATCH 07/13] ima,evm: use d_real_metadata_inode() Christian Brauner
` (6 subsequent siblings)
12 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
d_real() is about to be able to fail for D_REAL_DATA. So switch IMA
from d_real_inode() to d_real_data_inode() and deal with the error.
Currently, if overlayfs fails to resolve the data of a file it hands
back the overlayfs inode. process_measurement() then sees
real_inode == inode, skips the detection of changes in the backing file
and returns the cached verdict. That's the wrong default. With
metacopy and verity this isn't limited to files nobody opened yet
either. A metadata-only copy-up of an open file makes overlayfs validate
the verity digest of the data again on the next D_REAL_DATA lookup and
that can fail.
So if the inode hosting the data can't be resolved in
process_measurement() we can't tell whether the data changed. Treat it
like a change and clear IMA_DONE_MASK and measured_pcrs so the file is
evaluated again. The metadata check for EVM stays independent of that.
This is a no-op until d_real() can actually fail.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
security/integrity/ima/ima_api.c | 17 +++++++++++++----
security/integrity/ima/ima_main.c | 8 +++++---
2 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index 45701f04ba62f..f8c3437559943 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -246,7 +246,7 @@ int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file,
{
const char *audit_cause = "failed";
struct inode *inode = file_inode(file);
- struct inode *real_inode = d_real_inode(file_dentry(file));
+ struct inode *real_inode;
struct ima_max_digest_data hash;
struct ima_digest_data *hash_hdr = container_of(&hash.hdr,
struct ima_digest_data, hdr);
@@ -268,6 +268,13 @@ int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file,
if (iint->flags & IMA_COLLECTED)
goto out;
+ /*
+ * On stacked filesystems the inode hosting the data may have to be
+ * looked up and that can fail. Treat it like failing to calculate
+ * the hash.
+ */
+ real_inode = d_real_data_inode(file_dentry(file));
+
/*
* Detect file change based on STATX_CHANGE_COOKIE, when supported,
* and fallback to detecting file change based on i_version.
@@ -280,7 +287,7 @@ int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file,
AT_STATX_SYNC_AS_STAT);
if (!result && (stat.result_mask & STATX_CHANGE_COOKIE))
i_version = stat.change_cookie;
- else if (IS_I_VERSION(real_inode))
+ else if (!IS_ERR(real_inode) && IS_I_VERSION(real_inode))
i_version = inode_peek_iversion(real_inode);
hash.hdr.algo = algo;
@@ -289,7 +296,9 @@ int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file,
/* Initialize hash digest to 0's in case of failure */
memset(&hash.digest, 0, sizeof(hash.digest));
- if (iint->flags & IMA_VERITY_REQUIRED) {
+ if (IS_ERR(real_inode)) {
+ result = PTR_ERR(real_inode);
+ } else if (iint->flags & IMA_VERITY_REQUIRED) {
if (!ima_get_verity_digest(iint, inode, &hash)) {
audit_cause = "no-verity-digest";
result = -ENODATA;
@@ -314,7 +323,7 @@ int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file,
memcpy(iint->ima_hash, &hash, length);
if (real_inode == inode)
iint->real_inode.version = i_version;
- else
+ else if (!IS_ERR(real_inode))
integrity_inode_attrs_store(&iint->real_inode, i_version,
real_inode);
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index fed80da3ee096..e463cfc1e3f43 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -342,12 +342,14 @@ static int process_measurement(struct file *file, const struct cred *cred,
/*
* On stacked filesystems, detect and re-evaluate file data and
- * metadata changes.
+ * metadata changes. If the inode hosting the data can't be resolved
+ * we can't tell whether the data changed. Re-evaluate the file then
+ * and let ima_collect_measurement() fail.
*/
- real_inode = d_real_inode(file_dentry(file));
+ real_inode = d_real_data_inode(file_dentry(file));
if (real_inode != inode &&
(action & IMA_DO_MASK) && (iint->flags & IMA_DONE_MASK)) {
- if (!IS_I_VERSION(real_inode) ||
+ if (IS_ERR(real_inode) || !IS_I_VERSION(real_inode) ||
integrity_inode_attrs_changed(&iint->real_inode,
real_inode)) {
iint->flags &= ~IMA_DONE_MASK;
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 06/13] ima: handle errors from resolving the real inode
2026-11-15 14:01 ` [PATCH 06/13] ima: handle errors from resolving the real inode Christian Brauner
@ 2026-11-15 14:10 ` sashiko-bot
0 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-11-15 14:10 UTC (permalink / raw)
Cc: linux-security-module
> d_real() is about to be able to fail for D_REAL_DATA. So switch IMA
> from d_real_inode() to d_real_data_inode() and deal with the error.
>
> Currently, if overlayfs fails to resolve the data of a file it hands
> back the overlayfs inode. process_measurement() then sees
> real_inode == inode, skips the detection of changes in the backing file
> and returns the cached verdict. That's the wrong default. With
> metacopy and verity this isn't limited to files nobody opened yet
> either. A metadata-only copy-up of an open file makes overlayfs validate
> the verity digest of the data again on the next D_REAL_DATA lookup and
> that can fail.
>
> So if the inode hosting the data can't be resolved in
> process_measurement() we can't tell whether the data changed. Treat it
> like a change and clear IMA_DONE_MASK and measured_pcrs so the file is
> evaluated again. The metadata check for EVM stays independent of that.
>
> This is a no-op until d_real() can actually fail.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115-work-ovl-d_real-v1-0-ca155efbcb2e@kernel.org?part=6
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 07/13] ima,evm: use d_real_metadata_inode()
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
` (5 preceding siblings ...)
2026-11-15 14:01 ` [PATCH 06/13] ima: handle errors from resolving the real inode Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:05 ` sashiko-bot
2026-11-15 14:01 ` [PATCH 08/13] ipe: handle errors from resolving the real inode Christian Brauner
` (5 subsequent siblings)
12 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
d_real() is about to be able to fail for D_REAL_DATA. D_REAL_METADATA
never has to look anything up and can't fail. Make that explicit and
switch the open-coded d_inode(d_real(..., D_REAL_METADATA)) in
process_measurement() and evm_calc_hmac_or_hash() over to
d_real_metadata_inode().
No functional changes.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
security/integrity/evm/evm_crypto.c | 2 +-
security/integrity/ima/ima_main.c | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c
index 1c41af2f91a60..1b50cb4913d0b 100644
--- a/security/integrity/evm/evm_crypto.c
+++ b/security/integrity/evm/evm_crypto.c
@@ -231,7 +231,7 @@ static int evm_calc_hmac_or_hash(struct dentry *dentry,
uint8_t type, struct evm_digest *data,
struct evm_iint_cache *iint)
{
- struct inode *inode = d_inode(d_real(dentry, D_REAL_METADATA));
+ struct inode *inode = d_real_metadata_inode(dentry);
struct xattr_list *xattr;
struct shash_desc *desc;
size_t xattr_size = 0;
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index e463cfc1e3f43..9df7fbe3523e8 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -359,8 +359,7 @@ static int process_measurement(struct file *file, const struct cred *cred,
/*
* Reset the EVM status when metadata changed.
*/
- metadata_inode = d_inode(d_real(file_dentry(file),
- D_REAL_METADATA));
+ metadata_inode = d_real_metadata_inode(file_dentry(file));
if (evm_metadata_changed(inode, metadata_inode))
iint->flags &= ~(IMA_APPRAISED |
IMA_APPRAISED_SUBMASK);
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 08/13] ipe: handle errors from resolving the real inode
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
` (6 preceding siblings ...)
2026-11-15 14:01 ` [PATCH 07/13] ima,evm: use d_real_metadata_inode() Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:07 ` sashiko-bot
2026-11-15 14:37 ` bot+bpf-ci
2026-11-15 14:01 ` [PATCH 09/13] bpf: return NULL from bpf_real_data_inode() on errors Christian Brauner
` (4 subsequent siblings)
12 siblings, 2 replies; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
d_real() is about to be able to fail for D_REAL_DATA. So switch
ipe_build_eval_ctx() from d_real_inode() to d_real_data_inode() and deal
with the error.
Currently, if overlayfs fails to resolve the data of a file it hands
back the overlayfs inode and IPE evaluates the policy against that. The
overlayfs inode has neither a block device nor any fs-verity state so
none of the properties of the file hosting the data are visible.
Remember the error in the evaluation context and if a policy is loaded
refuse the operation with -EACCES without looking at the rules. That's
what IPE returns for every denial and what IMA returns when it can't
appraise a file. Permissive mode allows it like any other denial and
without a policy IPE still allows everything.
Note that the operation wouldn't necessarily fail on its own. overlayfs
doesn't verify the data again on mmap() so a file whose data can't be
resolved could otherwise be mapped executable.
This is a no-op until d_real() can actually fail.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
security/ipe/eval.c | 15 ++++++++++++++-
security/ipe/eval.h | 1 +
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/security/ipe/eval.c b/security/ipe/eval.c
index 4a7be96c84f03..3751963509df4 100644
--- a/security/ipe/eval.c
+++ b/security/ipe/eval.c
@@ -102,7 +102,11 @@ void ipe_build_eval_ctx(struct ipe_eval_ctx *ctx,
if (file) {
build_ipe_sb_ctx(ctx, file);
- ino = d_real_inode(file->f_path.dentry);
+ ino = d_real_data_inode(file->f_path.dentry);
+ if (IS_ERR(ino)) {
+ ctx->file_err = PTR_ERR(ino);
+ return;
+ }
build_ipe_bdev_ctx(ctx, ino);
build_ipe_inode_ctx(ctx, ino);
}
@@ -335,6 +339,15 @@ int ipe_evaluate_event(const struct ipe_eval_ctx *const ctx)
return 0;
}
+ /*
+ * Without the inode hosting the data none of the properties of the
+ * file can be evaluated. Refuse it unless IPE is permissive.
+ */
+ if (ctx->file_err) {
+ rcu_read_unlock();
+ return READ_ONCE(enforce) ? -EACCES : 0;
+ }
+
if (ctx->op == IPE_OP_INVALID) {
if (pol->parsed->global_default_action == IPE_ACTION_INVALID) {
WARN(1, "no default rule set for unknown op, ALLOW it");
diff --git a/security/ipe/eval.h b/security/ipe/eval.h
index b26d1147d0708..58ca50987b2b5 100644
--- a/security/ipe/eval.h
+++ b/security/ipe/eval.h
@@ -42,6 +42,7 @@ struct ipe_eval_ctx {
enum ipe_hook_type hook;
const struct file *file;
+ int file_err;
bool initramfs;
#ifdef CONFIG_IPE_PROP_DM_VERITY
const struct ipe_bdev *ipe_bdev;
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 08/13] ipe: handle errors from resolving the real inode
2026-11-15 14:01 ` [PATCH 08/13] ipe: handle errors from resolving the real inode Christian Brauner
@ 2026-11-15 14:07 ` sashiko-bot
2026-11-15 14:37 ` bot+bpf-ci
1 sibling, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-11-15 14:07 UTC (permalink / raw)
Cc: linux-security-module
> d_real() is about to be able to fail for D_REAL_DATA. So switch
> ipe_build_eval_ctx() from d_real_inode() to d_real_data_inode() and deal
> with the error.
>
> Currently, if overlayfs fails to resolve the data of a file it hands
> back the overlayfs inode and IPE evaluates the policy against that. The
> overlayfs inode has neither a block device nor any fs-verity state so
> none of the properties of the file hosting the data are visible.
>
> Remember the error in the evaluation context and if a policy is loaded
> refuse the operation with -EACCES without looking at the rules. That's
> what IPE returns for every denial and what IMA returns when it can't
> appraise a file. Permissive mode allows it like any other denial and
> without a policy IPE still allows everything.
>
> Note that the operation wouldn't necessarily fail on its own. overlayfs
> doesn't verify the data again on mmap() so a file whose data can't be
> resolved could otherwise be mapped executable.
>
> This is a no-op until d_real() can actually fail.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115-work-ovl-d_real-v1-0-ca155efbcb2e@kernel.org?part=8
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 08/13] ipe: handle errors from resolving the real inode
2026-11-15 14:01 ` [PATCH 08/13] ipe: handle errors from resolving the real inode Christian Brauner
2026-11-15 14:07 ` sashiko-bot
@ 2026-11-15 14:37 ` bot+bpf-ci
1 sibling, 0 replies; 32+ messages in thread
From: bot+bpf-ci @ 2026-11-15 14:37 UTC (permalink / raw)
To: brauner, amir73il, linux-fsdevel
Cc: chris, mszeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, brauner, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2833 bytes --]
> ipe: handle errors from resolving the real inode
TL;DR: Behaviour question: the new ipe_evaluate_event() early return for an
unresolvable real inode skips ipe_audit_match(), so enforce-mode -EACCES and
permissive-mode allows may leave no IPE audit record. Should it audit?
> diff --git a/security/ipe/eval.c b/security/ipe/eval.c
> --- a/security/ipe/eval.c
> +++ b/security/ipe/eval.c
[ ... ]
> @@ -335,6 +339,15 @@ int ipe_evaluate_event(const struct ipe_eval_ctx *const ctx)
> return 0;
> }
>
> + /*
> + * Without the inode hosting the data none of the properties of the
> + * file can be evaluated. Refuse it unless IPE is permissive.
> + */
> + if (ctx->file_err) {
> + rcu_read_unlock();
> + return READ_ONCE(enforce) ? -EACCES : 0;
> + }
> +
Should this path emit an AUDIT_IPE_ACCESS record?
This early return skips the eval: label, so ipe_audit_match() is never
called. Every other way ipe_evaluate_event() ends in a denial goes through:
eval:
ipe_audit_match(ctx, match_type, action, rule);
rcu_read_unlock();
if (action == IPE_ACTION_DENY)
rc = -EACCES;
if (!READ_ONCE(enforce))
rc = 0;
With this patch, in permissive mode a file whose data cannot be resolved
returns 0 and leaves no record at all, even though
Documentation/admin-guide/LSM/ipe.rst says that in permissive mode policy
violations are logged. In enforce mode the -EACCES has no IPE audit record
to explain it. The commit message says "Permissive mode allows it like any
other denial", but other denials are audited and this one is not.
This path can be reached for an already open file. ovl_open() verifies
lowerdata and sets OVL_VERIFIED_DIGEST, but a metacopy copy-up clears that
flag (ovl_clear_flag(OVL_VERIFIED_DIGEST, inode) in fs/overlayfs/copy_up.c).
A later mmap(PROT_EXEC) on the open fd then goes:
vm_mmap_pgoff()
security_mmap_file()
ipe_mmap_file()
ipe_build_eval_ctx()
d_real_data_inode()
ovl_d_real()
ovl_verify_lowerdata()
ovl_maybe_validate_verity()
which can fail with -EINTR from ovl_inode_lock_interruptible() or with an
error from ovl_validate_verity(), setting ctx->file_err. The IMA patch in
this series (c9952f8fed84) describes the same scenario, and its error path
in ima_collect_measurement() still calls integrity_audit_msg().
Could this set action = IPE_ACTION_DENY with a suitable match type and jump
to eval:, or use a dedicated audit helper that records the error, so that
denials and permissive-mode would-be denials are still logged? No later
commit in the series touches security/ipe.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/43723438893
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 09/13] bpf: return NULL from bpf_real_data_inode() on errors
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
` (7 preceding siblings ...)
2026-11-15 14:01 ` [PATCH 08/13] ipe: handle errors from resolving the real inode Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:08 ` sashiko-bot
2026-11-15 14:01 ` [PATCH 10/13] ovl: return the error from the lazy lowerdata lookup in ovl_d_real() Christian Brauner
` (3 subsequent siblings)
12 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
d_real() is about to be able to fail for D_REAL_DATA. So switch
bpf_real_data_inode() from d_real_inode() to d_real_data_inode() and
return NULL if the data can't be resolved.
The kfunc is already KF_SLEEPABLE | KF_RET_NULL and its documentation
says it may return NULL. So programs already have to deal with NULL and
the verifier enforces that. Note that this is the caller that most
easily ends up with a file whose data was never looked up.
lsm.s/file_open runs before ovl_open() and lsm.s/file_receive sees
O_PATH files passed via SCM_RIGHTS.
Spell that out in the documentation.
This is a no-op until d_real() can actually fail.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/bpf_fs_kfuncs.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 35ccd98a4de64..ab24e6bcd64c4 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -491,11 +491,20 @@ __bpf_kfunc int bpf_sock_read_xattr(struct socket *sock, const char *name__str,
* inode itself is returned; for any file on a non-union filesystem the inode
* attached to @file is returned.
*
- * Return: The inode hosting @file's data, or NULL.
+ * The union/overlay may have to look up the data first, e.g., when @file was
+ * opened with O_PATH or the program runs before the file is opened. If that
+ * fails NULL is returned.
+ *
+ * Return: The inode hosting @file's data, or NULL if it can't be resolved.
*/
__bpf_kfunc struct inode *bpf_real_data_inode(struct file *file)
{
- return d_real_inode(file_dentry(file));
+ struct inode *inode;
+
+ inode = d_real_data_inode(file_dentry(file));
+ if (IS_ERR(inode))
+ return NULL;
+ return inode;
}
__bpf_kfunc_end_defs();
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 10/13] ovl: return the error from the lazy lowerdata lookup in ovl_d_real()
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
` (8 preceding siblings ...)
2026-11-15 14:01 ` [PATCH 09/13] bpf: return NULL from bpf_real_data_inode() on errors Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:15 ` sashiko-bot
2026-11-15 14:47 ` Amir Goldstein
2026-11-15 14:01 ` [PATCH 11/13] fs: remove d_real_inode() Christian Brauner
` (2 subsequent siblings)
12 siblings, 2 replies; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
All callers have been converted to d_real_data_inode() and handle an
error now. So return the error from ovl_verify_lowerdata() just like
ovl_open() does. The lowerdata dentry missing after a successful
ovl_verify_lowerdata() would be an overlayfs bug, so keep warning about
that but return -EIO like ovl_open() does when it has no real path.
D_REAL_METADATA never looks anything up and still can't fail.
Update the comment in ovl_d_real() and the documentation of d_real() in
dcache.h and vfs.rst accordingly.
Suggested-by: Amir Goldstein <amir73il@gmail.com>
Link: https://lore.kernel.org/all/CAOQ4uxhGxpBtMZ5kfuzR1tQAewiecoX7UjXQRo91QjJHH9T_Yw@mail.gmail.com
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Documentation/filesystems/vfs.rst | 6 ++++++
fs/overlayfs/super.c | 17 +++++++++--------
include/linux/dcache.h | 6 ++++++
3 files changed, 21 insertions(+), 8 deletions(-)
diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
index 1824462a43711..6953f399672f3 100644
--- a/Documentation/filesystems/vfs.rst
+++ b/Documentation/filesystems/vfs.rst
@@ -1374,6 +1374,12 @@ defined:
for returning the real underlying dentry that refers to the inode
hosting the file's data or metadata respectively.
+ For D_REAL_DATA the filesystem may have to look up the data first,
+ e.g., for a file that was never opened. If that fails an ERR_PTR()
+ is returned and callers must handle it. D_REAL_METADATA must not
+ fail. Most callers should use d_real_data_inode() or
+ d_real_metadata_inode() instead of calling d_real() directly.
+
For non-regular files, the 'dentry' argument is returned.
``d_unalias_trylock``
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 1985b553a47b7..39d4d57dec230 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -42,7 +42,7 @@ static struct dentry *ovl_d_real(struct dentry *dentry, enum d_real_type type)
}
if (!d_is_reg(dentry)) {
- /* d_real_inode() is only relevant for regular files */
+ /* d_real() is only relevant for regular files */
return dentry;
}
@@ -57,17 +57,18 @@ static struct dentry *ovl_d_real(struct dentry *dentry, enum d_real_type type)
}
/*
- * Best effort lazy lookup of lowerdata for D_REAL_DATA case to return
- * the real lowerdata dentry. Callers may ask for files that were never
- * opened, e.g. for uprobes or O_PATH files, so the lookup can fail
- * here. Return the overlayfs dentry then.
+ * Lazy lookup of lowerdata for the D_REAL_DATA case to return the real
+ * lowerdata dentry. Callers may ask for files that were never opened,
+ * e.g., for uprobes or O_PATH files, and the lookup or the verity
+ * validation can fail. Return the error then, just like ovl_open()
+ * would. Callers of d_real() have to deal with that.
*/
err = ovl_verify_lowerdata(dentry);
if (err)
- return dentry;
+ return ERR_PTR(err);
lower = ovl_dentry_lowerdata(dentry);
- if (!lower)
- goto bug;
+ if (WARN_ON_ONCE(!lower))
+ return ERR_PTR(-EIO);
real_lower:
/* Handle recursion into stacked lower fs */
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 674bc50b81827..5b3b34d271bd0 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -638,7 +638,13 @@ static inline struct inode *d_backing_inode(const struct dentry *upper)
* If dentry is on a union/overlay, then return the underlying, real dentry.
* Otherwise return the dentry itself.
*
+ * For D_REAL_DATA the union/overlay may have to look up the data first. This
+ * can sleep and it can fail. D_REAL_METADATA never fails.
+ *
* See also: Documentation/filesystems/vfs.rst
+ *
+ * Return: The real dentry, or an ERR_PTR() for D_REAL_DATA if the data can't
+ * be resolved.
*/
static inline struct dentry *d_real(struct dentry *dentry, enum d_real_type type)
{
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 10/13] ovl: return the error from the lazy lowerdata lookup in ovl_d_real()
2026-11-15 14:01 ` [PATCH 10/13] ovl: return the error from the lazy lowerdata lookup in ovl_d_real() Christian Brauner
@ 2026-11-15 14:15 ` sashiko-bot
2026-11-15 14:47 ` Amir Goldstein
1 sibling, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-11-15 14:15 UTC (permalink / raw)
Cc: linux-security-module
> All callers have been converted to d_real_data_inode() and handle an
> error now. So return the error from ovl_verify_lowerdata() just like
> ovl_open() does. The lowerdata dentry missing after a successful
> ovl_verify_lowerdata() would be an overlayfs bug, so keep warning about
> that but return -EIO like ovl_open() does when it has no real path.
> D_REAL_METADATA never looks anything up and still can't fail.
>
> Update the comment in ovl_d_real() and the documentation of d_real() in
> dcache.h and vfs.rst accordingly.
>
> Suggested-by: Amir Goldstein <amir73il@gmail.com>
> Link: https://lore.kernel.org/all/CAOQ4uxhGxpBtMZ5kfuzR1tQAewiecoX7UjXQRo91QjJHH9T_Yw@mail.gmail.com
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115-work-ovl-d_real-v1-0-ca155efbcb2e@kernel.org?part=10
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 10/13] ovl: return the error from the lazy lowerdata lookup in ovl_d_real()
2026-11-15 14:01 ` [PATCH 10/13] ovl: return the error from the lazy lowerdata lookup in ovl_d_real() Christian Brauner
2026-11-15 14:15 ` sashiko-bot
@ 2026-11-15 14:47 ` Amir Goldstein
1 sibling, 0 replies; 32+ messages in thread
From: Amir Goldstein @ 2026-11-15 14:47 UTC (permalink / raw)
To: Christian Brauner
Cc: linux-fsdevel, Chris Down, Miklos Szeredi, linux-unionfs,
linux-kernel, bpf, linux-trace-kernel, linux-integrity,
linux-security-module, linux-mm
On Sun, Nov 15, 2026 at 3:02 PM Christian Brauner <brauner@kernel.org> wrote:
>
> All callers have been converted to d_real_data_inode() and handle an
> error now. So return the error from ovl_verify_lowerdata() just like
> ovl_open() does. The lowerdata dentry missing after a successful
> ovl_verify_lowerdata() would be an overlayfs bug, so keep warning about
> that but return -EIO like ovl_open() does when it has no real path.
> D_REAL_METADATA never looks anything up and still can't fail.
>
> Update the comment in ovl_d_real() and the documentation of d_real() in
> dcache.h and vfs.rst accordingly.
>
> Suggested-by: Amir Goldstein <amir73il@gmail.com>
> Link: https://lore.kernel.org/all/CAOQ4uxhGxpBtMZ5kfuzR1tQAewiecoX7UjXQRo91QjJHH9T_Yw@mail.gmail.com
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Nice!
Thanks for cleaning up this mess.
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> ---
> Documentation/filesystems/vfs.rst | 6 ++++++
> fs/overlayfs/super.c | 17 +++++++++--------
> include/linux/dcache.h | 6 ++++++
> 3 files changed, 21 insertions(+), 8 deletions(-)
>
> diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
> index 1824462a43711..6953f399672f3 100644
> --- a/Documentation/filesystems/vfs.rst
> +++ b/Documentation/filesystems/vfs.rst
> @@ -1374,6 +1374,12 @@ defined:
> for returning the real underlying dentry that refers to the inode
> hosting the file's data or metadata respectively.
>
> + For D_REAL_DATA the filesystem may have to look up the data first,
> + e.g., for a file that was never opened. If that fails an ERR_PTR()
> + is returned and callers must handle it. D_REAL_METADATA must not
> + fail. Most callers should use d_real_data_inode() or
> + d_real_metadata_inode() instead of calling d_real() directly.
> +
> For non-regular files, the 'dentry' argument is returned.
>
> ``d_unalias_trylock``
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index 1985b553a47b7..39d4d57dec230 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -42,7 +42,7 @@ static struct dentry *ovl_d_real(struct dentry *dentry, enum d_real_type type)
> }
>
> if (!d_is_reg(dentry)) {
> - /* d_real_inode() is only relevant for regular files */
> + /* d_real() is only relevant for regular files */
> return dentry;
> }
>
> @@ -57,17 +57,18 @@ static struct dentry *ovl_d_real(struct dentry *dentry, enum d_real_type type)
> }
>
> /*
> - * Best effort lazy lookup of lowerdata for D_REAL_DATA case to return
> - * the real lowerdata dentry. Callers may ask for files that were never
> - * opened, e.g. for uprobes or O_PATH files, so the lookup can fail
> - * here. Return the overlayfs dentry then.
> + * Lazy lookup of lowerdata for the D_REAL_DATA case to return the real
> + * lowerdata dentry. Callers may ask for files that were never opened,
> + * e.g., for uprobes or O_PATH files, and the lookup or the verity
> + * validation can fail. Return the error then, just like ovl_open()
> + * would. Callers of d_real() have to deal with that.
> */
> err = ovl_verify_lowerdata(dentry);
> if (err)
> - return dentry;
> + return ERR_PTR(err);
> lower = ovl_dentry_lowerdata(dentry);
> - if (!lower)
> - goto bug;
> + if (WARN_ON_ONCE(!lower))
> + return ERR_PTR(-EIO);
>
> real_lower:
> /* Handle recursion into stacked lower fs */
> diff --git a/include/linux/dcache.h b/include/linux/dcache.h
> index 674bc50b81827..5b3b34d271bd0 100644
> --- a/include/linux/dcache.h
> +++ b/include/linux/dcache.h
> @@ -638,7 +638,13 @@ static inline struct inode *d_backing_inode(const struct dentry *upper)
> * If dentry is on a union/overlay, then return the underlying, real dentry.
> * Otherwise return the dentry itself.
> *
> + * For D_REAL_DATA the union/overlay may have to look up the data first. This
> + * can sleep and it can fail. D_REAL_METADATA never fails.
> + *
> * See also: Documentation/filesystems/vfs.rst
> + *
> + * Return: The real dentry, or an ERR_PTR() for D_REAL_DATA if the data can't
> + * be resolved.
> */
> static inline struct dentry *d_real(struct dentry *dentry, enum d_real_type type)
> {
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 11/13] fs: remove d_real_inode()
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
` (9 preceding siblings ...)
2026-11-15 14:01 ` [PATCH 10/13] ovl: return the error from the lazy lowerdata lookup in ovl_d_real() Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:07 ` sashiko-bot
2026-11-15 14:48 ` Amir Goldstein
2026-11-15 14:01 ` [PATCH 12/13] selftests/ftrace: add uprobe tests on overlayfs Christian Brauner
2026-11-15 14:01 ` [PATCH 13/13] selftests/bpf: add uprobe_multi test on overlayfs without data Christian Brauner
12 siblings, 2 replies; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
All callers have been converted to d_real_data_inode() or
d_real_metadata_inode(). Remove d_real_inode() so that any user we
missed, or one that gets added in another tree, fails to build instead
of passing an ERR_PTR() from d_real() to d_inode().
Add a porting.rst entry.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Documentation/filesystems/porting.rst | 10 ++++++++++
include/linux/dcache.h | 13 -------------
2 files changed, 10 insertions(+), 13 deletions(-)
diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst
index c38f5df755bb5..28a24b702f2a6 100644
--- a/Documentation/filesystems/porting.rst
+++ b/Documentation/filesystems/porting.rst
@@ -1422,3 +1422,13 @@ helpers take a const struct mnt_idmap *. mnt_idmap() and file_mnt_idmap()
return one. The idmapping is immutable so nothing should have modified it
anyway. References are taken and dropped via mnt_idmap_get() and
mnt_idmap_put() as before, both accept a const pointer.
+
+---
+
+**mandatory**
+
+d_real_inode() is gone. d_real() can fail for D_REAL_DATA now and returns an
+ERR_PTR() then, e.g., when overlayfs can't look up the data of a metacopy file
+in a data-only layer. Use d_real_data_inode(), which returns the inode hosting
+the data or an ERR_PTR(), and handle the error. Use d_real_metadata_inode() for
+the inode hosting the metadata. That one never fails.
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 5b3b34d271bd0..2b15179e9baf9 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -654,19 +654,6 @@ static inline struct dentry *d_real(struct dentry *dentry, enum d_real_type type
return dentry;
}
-/**
- * d_real_inode - Return the real inode hosting the data
- * @dentry: The dentry to query
- *
- * If dentry is on a union/overlay, then return the underlying, real inode.
- * Otherwise return d_inode().
- */
-static inline struct inode *d_real_inode(const struct dentry *dentry)
-{
- /* This usage of d_real() results in const dentry */
- return d_inode(d_real((struct dentry *) dentry, D_REAL_DATA));
-}
-
/**
* d_real_data_inode - Return the real inode hosting the data
* @dentry: The dentry to query
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 11/13] fs: remove d_real_inode()
2026-11-15 14:01 ` [PATCH 11/13] fs: remove d_real_inode() Christian Brauner
@ 2026-11-15 14:07 ` sashiko-bot
2026-11-15 14:48 ` Amir Goldstein
1 sibling, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-11-15 14:07 UTC (permalink / raw)
Cc: linux-security-module
> All callers have been converted to d_real_data_inode() or
> d_real_metadata_inode(). Remove d_real_inode() so that any user we
> missed, or one that gets added in another tree, fails to build instead
> of passing an ERR_PTR() from d_real() to d_inode().
>
> Add a porting.rst entry.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115-work-ovl-d_real-v1-0-ca155efbcb2e@kernel.org?part=11
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 11/13] fs: remove d_real_inode()
2026-11-15 14:01 ` [PATCH 11/13] fs: remove d_real_inode() Christian Brauner
2026-11-15 14:07 ` sashiko-bot
@ 2026-11-15 14:48 ` Amir Goldstein
1 sibling, 0 replies; 32+ messages in thread
From: Amir Goldstein @ 2026-11-15 14:48 UTC (permalink / raw)
To: Christian Brauner
Cc: linux-fsdevel, Chris Down, Miklos Szeredi, linux-unionfs,
linux-kernel, bpf, linux-trace-kernel, linux-integrity,
linux-security-module, linux-mm
On Sun, Nov 15, 2026 at 3:02 PM Christian Brauner <brauner@kernel.org> wrote:
>
> All callers have been converted to d_real_data_inode() or
> d_real_metadata_inode(). Remove d_real_inode() so that any user we
> missed, or one that gets added in another tree, fails to build instead
> of passing an ERR_PTR() from d_real() to d_inode().
>
> Add a porting.rst entry.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
> ---
> Documentation/filesystems/porting.rst | 10 ++++++++++
> include/linux/dcache.h | 13 -------------
> 2 files changed, 10 insertions(+), 13 deletions(-)
>
> diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst
> index c38f5df755bb5..28a24b702f2a6 100644
> --- a/Documentation/filesystems/porting.rst
> +++ b/Documentation/filesystems/porting.rst
> @@ -1422,3 +1422,13 @@ helpers take a const struct mnt_idmap *. mnt_idmap() and file_mnt_idmap()
> return one. The idmapping is immutable so nothing should have modified it
> anyway. References are taken and dropped via mnt_idmap_get() and
> mnt_idmap_put() as before, both accept a const pointer.
> +
> +---
> +
> +**mandatory**
> +
> +d_real_inode() is gone. d_real() can fail for D_REAL_DATA now and returns an
> +ERR_PTR() then, e.g., when overlayfs can't look up the data of a metacopy file
> +in a data-only layer. Use d_real_data_inode(), which returns the inode hosting
> +the data or an ERR_PTR(), and handle the error. Use d_real_metadata_inode() for
> +the inode hosting the metadata. That one never fails.
> diff --git a/include/linux/dcache.h b/include/linux/dcache.h
> index 5b3b34d271bd0..2b15179e9baf9 100644
> --- a/include/linux/dcache.h
> +++ b/include/linux/dcache.h
> @@ -654,19 +654,6 @@ static inline struct dentry *d_real(struct dentry *dentry, enum d_real_type type
> return dentry;
> }
>
> -/**
> - * d_real_inode - Return the real inode hosting the data
> - * @dentry: The dentry to query
> - *
> - * If dentry is on a union/overlay, then return the underlying, real inode.
> - * Otherwise return d_inode().
> - */
> -static inline struct inode *d_real_inode(const struct dentry *dentry)
> -{
> - /* This usage of d_real() results in const dentry */
> - return d_inode(d_real((struct dentry *) dentry, D_REAL_DATA));
> -}
> -
> /**
> * d_real_data_inode - Return the real inode hosting the data
> * @dentry: The dentry to query
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 12/13] selftests/ftrace: add uprobe tests on overlayfs
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
` (10 preceding siblings ...)
2026-11-15 14:01 ` [PATCH 11/13] fs: remove d_real_inode() Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:18 ` sashiko-bot
2026-11-15 14:01 ` [PATCH 13/13] selftests/bpf: add uprobe_multi test on overlayfs without data Christian Brauner
12 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
Add uprobe tests on overlayfs with a data-only layer:
- a metacopy file whose redirect points at nothing: creating the probe
fails, the error log has the new message and nothing gets tainted by a
WARN
- a metacopy file whose data lives in the data-only layer: the data is
looked up when the probe is created and the probe hits when the file
is executed
None of the existing tests puts a uprobe on overlayfs.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
.../test.d/dynevent/add_remove_uprobe_overlayfs.tc | 92 ++++++++++++++++++++++
1 file changed, 92 insertions(+)
diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe_overlayfs.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe_overlayfs.tc
new file mode 100644
index 0000000000000..a4351e6a3b181
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe_overlayfs.tc
@@ -0,0 +1,92 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: Generic dynamic event - add/remove uprobe events on overlayfs
+# requires: uprobe_events error_log setfattr:program readelf:program
+
+grep -qw overlay /proc/filesystems || modprobe overlay 2> /dev/null || :
+if ! grep -qw overlay /proc/filesystems; then
+ echo "No overlayfs support. skipped."
+ exit_unsupported
+fi
+
+echo 0 > events/enable
+echo > dynamic_events
+
+OVL=$TMPDIR/ovl
+mkdir $OVL
+mount -t tmpfs tmpfs $OVL
+
+cleanup() {
+ echo > uprobe_events
+ umount $OVL/merged 2> /dev/null || :
+ umount $OVL 2> /dev/null || :
+}
+trap cleanup EXIT
+
+mkdir $OVL/lower $OVL/data $OVL/merged
+
+# A metacopy file whose data lives in the data-only layer.
+REALBIN=`readlink -f /bin/sh`
+cp $REALBIN $OVL/data/sh
+truncate -s `stat -c %s $REALBIN` $OVL/lower/sh
+chmod 755 $OVL/lower/sh
+setfattr -n trusted.overlay.metacopy $OVL/lower/sh
+setfattr -n trusted.overlay.redirect -v /sh $OVL/lower/sh
+
+# A metacopy file whose data doesn't exist.
+touch $OVL/lower/broken
+setfattr -n trusted.overlay.metacopy $OVL/lower/broken
+setfattr -n trusted.overlay.redirect -v /missing $OVL/lower/broken
+
+mount -t overlay overlay -o lowerdir=$OVL/lower::$OVL/data $OVL/merged
+
+TAINT_WARN=512
+TAINTED=`cat /proc/sys/kernel/tainted`
+
+# The data of the broken file can't be resolved so the probe must fail
+# right away and not only when it is enabled.
+echo > error_log
+if echo "p:broken $OVL/merged/broken:0x0" >> uprobe_events; then
+ echo "uprobe on a file without data was created"
+ exit_fail
+fi
+grep -q "trace_uprobe: error: Failed to resolve the data of the given file" error_log
+! grep -q broken uprobe_events
+test ! -d events/uprobes/broken
+
+# Get the entry point virtual address from ELF header and convert it to
+# a file offset. See add_remove_uprobe.tc.
+ENTRY=`readelf -hW ${REALBIN} | grep "Entry point" | awk '{print $NF}'`
+ENTRY_DEC=$(printf '%d' "$ENTRY")
+OFFSET=$ENTRY
+while IFS= read -r line; do
+ set -- $line
+ [ "$1" = "LOAD" ] || continue
+ VA_DEC=$(printf '%d' "$3")
+ OFF_DEC=$(printf '%d' "$2")
+ FSZ_DEC=$(printf '%d' "$5")
+ if [ "$ENTRY_DEC" -ge "$VA_DEC" ] && [ "$ENTRY_DEC" -lt "$((VA_DEC + FSZ_DEC))" ]; then
+ OFFSET=$(printf '0x%x' "$((ENTRY_DEC - VA_DEC + OFF_DEC))")
+ break
+ fi
+done << EOF
+$(readelf -lW ${REALBIN} | grep LOAD)
+EOF
+
+# The data of the metacopy file is looked up when the probe is created.
+echo "p:myevent $OVL/merged/sh:${OFFSET}" >> uprobe_events
+grep -q myevent uprobe_events
+test -d events/uprobes/myevent
+
+echo 1 > events/uprobes/myevent/enable
+echo 'ls' | $OVL/merged/sh > /dev/null
+echo 0 > events/uprobes/myevent/enable
+grep -q myevent trace
+
+echo "-:myevent" >> uprobe_events
+! grep -q myevent uprobe_events
+
+# Nothing above may have warned.
+test $((`cat /proc/sys/kernel/tainted` & TAINT_WARN)) -eq $((TAINTED & TAINT_WARN))
+
+clear_trace
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 12/13] selftests/ftrace: add uprobe tests on overlayfs
2026-11-15 14:01 ` [PATCH 12/13] selftests/ftrace: add uprobe tests on overlayfs Christian Brauner
@ 2026-11-15 14:18 ` sashiko-bot
0 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-11-15 14:18 UTC (permalink / raw)
To: Christian Brauner, Amir Goldstein
Cc: Chris Down, Miklos Szeredi, bpf, linux-trace-kernel,
linux-security-module, linux-mm
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Low] selftests/ftrace: cleanup() in uprobe overlayfs test fails if
event is enabled
- [Low] selftests/ftrace: uprobe overlayfs test misses tmpfs xattr skip
and configs
--
commit cdef3f0121f6552797f32e8a89d02391a6081a77
Author: Christian Brauner <brauner@kernel.org>
selftests/ftrace: add uprobe tests on overlayfs
This commit adds an ftrace selftest that exercises creating and firing uprobe
events on overlayfs metacopy files backed by a data-only layer.
> diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe_overlayfs.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe_overlayfs.tc
> new file mode 100644
> index 0000000000000..a4351e6a3b181
> --- /dev/null
> +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe_overlayfs.tc
> @@ -0,0 +1,92 @@
[ ... ]
> +cleanup() {
> + echo > uprobe_events
> + umount $OVL/merged 2> /dev/null || :
> + umount $OVL 2> /dev/null || :
> +}
> +trap cleanup EXIT
[Severity: Low]
Does cleanup() fail to remove myevent and unmount the filesystems if the test
exits while myevent is enabled?
ftracetest runs test scripts under set -e. If executing $OVL/merged/sh fails
between enabling and disabling events/uprobes/myevent/enable, the EXIT trap
runs while myevent is still enabled. Truncating uprobe_events then returns
-EBUSY from trace_uprobe_is_busy() and aborts cleanup() under set -e before
the umount commands run, leaving the uprobe registered and the overlayfs and
tmpfs mounts leaked. Writing 0 to events/enable at the start of cleanup()
would avoid this.
> +
> +mkdir $OVL/lower $OVL/data $OVL/merged
> +
> +# A metacopy file whose data lives in the data-only layer.
> +REALBIN=`readlink -f /bin/sh`
> +cp $REALBIN $OVL/data/sh
> +truncate -s `stat -c %s $REALBIN` $OVL/lower/sh
> +chmod 755 $OVL/lower/sh
> +setfattr -n trusted.overlay.metacopy $OVL/lower/sh
> +setfattr -n trusted.overlay.redirect -v /sh $OVL/lower/sh
[Severity: Low]
Will this test fail instead of skipping with exit_unsupported when
CONFIG_TMPFS_XATTR is disabled?
tmpfs only supports trusted.* xattrs when CONFIG_TMPFS_XATTR is enabled, which
defaults to n and is not selected by CONFIG_OVERLAY_FS. Without it, setfattr
on $OVL/lower/sh fails with EOPNOTSUPP and aborts the script under set -e.
Also, should CONFIG_OVERLAY_FS=y and CONFIG_TMPFS_XATTR=y be added to
tools/testing/selftests/ftrace/config?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261115-work-ovl-d_real-v1-0-ca155efbcb2e@kernel.org?part=12
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 13/13] selftests/bpf: add uprobe_multi test on overlayfs without data
2026-11-15 14:01 [PATCH 00/13] fs: let d_real() fail Christian Brauner
` (11 preceding siblings ...)
2026-11-15 14:01 ` [PATCH 12/13] selftests/ftrace: add uprobe tests on overlayfs Christian Brauner
@ 2026-11-15 14:01 ` Christian Brauner
2026-11-15 14:12 ` sashiko-bot
12 siblings, 1 reply; 32+ messages in thread
From: Christian Brauner @ 2026-11-15 14:01 UTC (permalink / raw)
To: Amir Goldstein, linux-fsdevel
Cc: Chris Down, Miklos Szeredi, linux-unionfs, linux-kernel, bpf,
linux-trace-kernel, linux-integrity, linux-security-module,
linux-mm, Christian Brauner (Amutable)
Add a uprobe_multi test that attaches to a metacopy file on overlayfs
whose redirect into a data-only layer points at nothing:
- the overlayfs is set up in a child in its own mount namespace
- BPF_LINK_CREATE must fail with ENOENT from the lazy lowerdata lookup
- skip if overlayfs with data-only layers isn't available
Before d_real() could fail this got an EIO from uprobe_register().
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
.../selftests/bpf/prog_tests/uprobe_multi_test.c | 141 +++++++++++++++++++++
1 file changed, 141 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
index 7fbcfddc3cc36..907d8c6d5ec93 100644
--- a/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe_multi_test.c
@@ -3,6 +3,10 @@
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
+#include <sched.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
#include <test_progs.h>
#include "uprobe_multi.skel.h"
#include "uprobe_multi_bench.skel.h"
@@ -575,6 +579,141 @@ static void test_attach_api_fails(void)
uprobe_multi__destroy(skel);
}
+enum ovl_no_data_stage {
+ OVL_NO_DATA_SETUP,
+ OVL_NO_DATA_MOUNT,
+ OVL_NO_DATA_ATTACH,
+};
+
+struct ovl_no_data_result {
+ enum ovl_no_data_stage stage;
+ int err;
+};
+
+/*
+ * Runs in a child in its own mount namespace. Create an overlayfs with a
+ * metacopy file whose redirect into a data-only layer points at nothing
+ * and try to attach to it.
+ */
+static void ovl_no_data_child(int prog_fd, const char *dir, int wfd)
+{
+ struct ovl_no_data_result res = { .stage = OVL_NO_DATA_SETUP };
+ LIBBPF_OPTS(bpf_link_create_opts, opts);
+ char path[PATH_MAX], data[PATH_MAX * 2 + 32];
+ unsigned long offset = 0;
+ int fd;
+
+ if (unshare(CLONE_NEWNS) ||
+ mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) ||
+ mount("tmpfs", dir, "tmpfs", 0, NULL))
+ goto out_errno;
+
+ snprintf(path, sizeof(path), "%s/lower", dir);
+ if (mkdir(path, 0755))
+ goto out_errno;
+ snprintf(path, sizeof(path), "%s/data", dir);
+ if (mkdir(path, 0755))
+ goto out_errno;
+ snprintf(path, sizeof(path), "%s/merged", dir);
+ if (mkdir(path, 0755))
+ goto out_errno;
+
+ snprintf(path, sizeof(path), "%s/lower/f", dir);
+ fd = open(path, O_CREAT | O_WRONLY, 0755);
+ if (fd < 0)
+ goto out_errno;
+ close(fd);
+ if (setxattr(path, "trusted.overlay.metacopy", "", 0, 0) ||
+ setxattr(path, "trusted.overlay.redirect", "/missing",
+ strlen("/missing"), 0))
+ goto out_errno;
+
+ res.stage = OVL_NO_DATA_MOUNT;
+ snprintf(data, sizeof(data), "lowerdir=%s/lower::%s/data", dir, dir);
+ snprintf(path, sizeof(path), "%s/merged", dir);
+ if (mount("overlay", path, "overlay", 0, data))
+ goto out_errno;
+
+ res.stage = OVL_NO_DATA_ATTACH;
+ snprintf(path, sizeof(path), "%s/merged/f", dir);
+ opts.uprobe_multi.path = path;
+ opts.uprobe_multi.offsets = &offset;
+ opts.uprobe_multi.cnt = 1;
+ res.err = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
+ if (res.err >= 0)
+ close(res.err);
+ goto out;
+
+out_errno:
+ res.err = -errno;
+out:
+ if (write(wfd, &res, sizeof(res)) != sizeof(res))
+ _exit(1);
+ _exit(0);
+}
+
+/*
+ * The data of a metacopy file in an overlayfs data-only layer is looked up
+ * lazily. If it can't be found attaching must fail with the error from the
+ * lookup.
+ */
+static void test_attach_api_fails_ovl_no_data(void)
+{
+ struct ovl_no_data_result res = {};
+ char dir[] = "/tmp/uprobe_multi_ovl.XXXXXX";
+ struct uprobe_multi *skel = NULL;
+ int pipefd[2] = { -1, -1 };
+ int status, prog_fd;
+ ssize_t n;
+ pid_t pid;
+
+ skel = uprobe_multi__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "uprobe_multi__open_and_load"))
+ return;
+ prog_fd = bpf_program__fd(skel->progs.uprobe_extra);
+
+ if (!ASSERT_OK_PTR(mkdtemp(dir), "mkdtemp"))
+ goto cleanup;
+ if (!ASSERT_OK(pipe(pipefd), "pipe"))
+ goto cleanup_dir;
+
+ pid = fork();
+ if (!ASSERT_GE(pid, 0, "fork"))
+ goto cleanup_pipe;
+ if (pid == 0) {
+ close(pipefd[0]);
+ ovl_no_data_child(prog_fd, dir, pipefd[1]);
+ }
+
+ close(pipefd[1]);
+ pipefd[1] = -1;
+ n = read(pipefd[0], &res, sizeof(res));
+ if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
+ goto cleanup_pipe;
+ if (!ASSERT_EQ(n, sizeof(res), "read result"))
+ goto cleanup_pipe;
+
+ if (res.stage == OVL_NO_DATA_MOUNT) {
+ printf("%s:SKIP: overlayfs with data-only layers not available (%d)\n",
+ __func__, res.err);
+ test__skip();
+ goto cleanup_pipe;
+ }
+ if (!ASSERT_EQ(res.stage, OVL_NO_DATA_ATTACH, "setup"))
+ goto cleanup_pipe;
+ ASSERT_EQ(res.err, -ENOENT, "link_create");
+
+cleanup_pipe:
+ if (pipefd[0] >= 0)
+ close(pipefd[0]);
+ if (pipefd[1] >= 0)
+ close(pipefd[1]);
+cleanup_dir:
+ rmdir(dir);
+cleanup:
+ uprobe_multi__destroy(skel);
+}
+
#ifdef __x86_64__
noinline void uprobe_multi_error_func(void)
{
@@ -1452,6 +1591,8 @@ void test_uprobe_multi_test(void)
test_bench_attach_usdt();
if (test__start_subtest("attach_api_fails"))
test_attach_api_fails();
+ if (test__start_subtest("attach_api_fails_ovl_no_data"))
+ test_attach_api_fails_ovl_no_data();
if (test__start_subtest("attach_uprobe_fails"))
test_attach_uprobe_fails();
if (test__start_subtest("consumers"))
--
2.53.0
^ permalink raw reply related [flat|nested] 32+ messages in thread