Re: [PATCH] overlayfs: mask d_type high bits before whiteout check
From: Amir Goldstein
Date: Wed Jan 07 2026 - 17:39:00 EST
On Wed, Jan 7, 2026 at 4:46 AM Chunsheng Luo <luochunsheng@xxxxxxxx> wrote:
>
> Commit c31f91c6af96 ("fuse: don't allow signals to interrupt getdents
> copying") introduced the use of high bits in d_type as flags. However,
> overlayfs was not adapted to handle this change.
>
> In ovl_cache_entry_new(), the code checks if d_type == DT_CHR to
> determine if an entry might be a whiteout. When fuse is used as the
> lower layer and sets high bits in d_type, this comparison fails,
> causing whiteout files to not be recognized properly and resulting in
> incorrect overlayfs behavior.
>
> Fix this by masking out the high bits with S_DT_MASK before checking.
>
> Fixes: c31f91c6af96 ("fuse: don't allow signals to interrupt getdents copying")
> Link: https://github.com/containerd/stargz-snapshotter/issues/2214
> Signed-off-by: Chunsheng Luo <luochunsheng@xxxxxxxx>
Hi Chunsheng,
Thanks for the report and the suggested fix.
This time overlayfs was surprised by unexpected d_type flags and next
time it could be another user.
I prefer to fix this in a more profound way -
Instead of making overlafys aware of d_type flags, require the users that
use the d_type flags to opt-in for them.
Please test/review the attached patch.
Thanks,
Amir.
> ---
> fs/overlayfs/readdir.c | 15 +++++++++++++++
> 1 file changed, 15 insertions(+)
>
> diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
> index 160960bb0ad0..a2ac47458bf9 100644
> --- a/fs/overlayfs/readdir.c
> +++ b/fs/overlayfs/readdir.c
> @@ -246,6 +246,9 @@ static int ovl_fill_lowest(struct ovl_readdir_data *rdd,
> {
> struct ovl_cache_entry *p;
>
> + /* Mask out high bits that may be used (e.g., fuse) */
> + d_type &= S_DT_MASK;
> +
> p = ovl_cache_entry_find(rdd->root, c_name, c_len);
> if (p) {
> list_move_tail(&p->l_node, &rdd->middle);
> @@ -316,6 +319,9 @@ static bool ovl_fill_merge(struct dir_context *ctx, const char *name,
> char *cf_name = NULL;
> int c_len = 0, ret;
>
> + /* Mask out high bits that may be used (e.g., fuse) */
> + d_type &= S_DT_MASK;
> +
> if (ofs->casefold)
> c_len = ovl_casefold(rdd, name, namelen, &cf_name);
>
> @@ -632,6 +638,9 @@ static bool ovl_fill_plain(struct dir_context *ctx, const char *name,
> struct ovl_readdir_data *rdd =
> container_of(ctx, struct ovl_readdir_data, ctx);
>
> + /* Mask out high bits that may be used (e.g., fuse) */
> + d_type &= S_DT_MASK;
> +
> rdd->count++;
> p = ovl_cache_entry_new(rdd, name, namelen, NULL, 0, ino, d_type);
> if (p == NULL) {
> @@ -755,6 +764,9 @@ static bool ovl_fill_real(struct dir_context *ctx, const char *name,
> struct dir_context *orig_ctx = rdt->orig_ctx;
> bool res;
>
> + /* Mask out high bits that may be used (e.g., fuse) */
> + d_type &= S_DT_MASK;
> +
> if (rdt->parent_ino && strcmp(name, "..") == 0) {
> ino = rdt->parent_ino;
> } else if (rdt->cache) {
> @@ -1144,6 +1156,9 @@ static bool ovl_check_d_type(struct dir_context *ctx, const char *name,
> struct ovl_readdir_data *rdd =
> container_of(ctx, struct ovl_readdir_data, ctx);
>
> + /* Mask out high bits that may be used (e.g., fuse) */
> + d_type &= S_DT_MASK;
> +
> /* Even if d_type is not supported, DT_DIR is returned for . and .. */
> if (!strncmp(name, ".", namelen) || !strncmp(name, "..", namelen))
> return true;
> --
> 2.43.0
>
From 283faa964b2fb2821ea64eb451407e8a5095fe0b Mon Sep 17 00:00:00 2001
From: Amir Goldstein <amir73il@xxxxxxxxx>
Date: Wed, 7 Jan 2026 10:00:57 +0100
Subject: [PATCH] readdir: require opt-in for d_type flags
Commit c31f91c6af96 ("fuse: don't allow signals to interrupt getdents
copying") introduced the use of high bits in d_type as flags. However,
overlayfs was not adapted to handle this change.
In ovl_cache_entry_new(), the code checks if d_type == DT_CHR to
determine if an entry might be a whiteout. When fuse is used as the
lower layer and sets high bits in d_type, this comparison fails,
causing whiteout files to not be recognized properly and resulting in
incorrect overlayfs behavior.
Fix this by requiring callers of iterate_dir() to opt-in for getting
flag bits in d_type outside of S_DT_MASK.
Fixes: c31f91c6af96 ("fuse: don't allow signals to interrupt getdents copying")
Link: https://lore.kernel.org/all/20260107034551.439-1-luochunsheng@xxxxxxxx/
Link: https://github.com/containerd/stargz-snapshotter/issues/2214
Reported-by: Chunsheng Luo <luochunsheng@xxxxxxxx>
Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx>
---
fs/readdir.c | 3 +++
include/linux/fs.h | 6 +++++-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/fs/readdir.c b/fs/readdir.c
index 7764b86389788..77e4aa772315d 100644
--- a/fs/readdir.c
+++ b/fs/readdir.c
@@ -316,6 +316,7 @@ SYSCALL_DEFINE3(getdents, unsigned int, fd,
struct getdents_callback buf = {
.ctx.actor = filldir,
.ctx.count = count,
+ .ctx.dt_flag_mask = FILLDIR_FLAG_NOINTR,
.current_dir = dirent
};
int error;
@@ -400,6 +401,7 @@ SYSCALL_DEFINE3(getdents64, unsigned int, fd,
struct getdents_callback64 buf = {
.ctx.actor = filldir64,
.ctx.count = count,
+ .ctx.dt_flag_mask = FILLDIR_FLAG_NOINTR,
.current_dir = dirent
};
int error;
@@ -569,6 +571,7 @@ COMPAT_SYSCALL_DEFINE3(getdents, unsigned int, fd,
struct compat_getdents_callback buf = {
.ctx.actor = compat_filldir,
.ctx.count = count,
+ .ctx.dt_flag_mask = FILLDIR_FLAG_NOINTR,
.current_dir = dirent,
};
int error;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f5c9cf28c4dcf..c36992832fb2b 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1855,6 +1855,8 @@ struct dir_context {
* INT_MAX unlimited
*/
int count;
+ /* @actor supports these flags in d_type high bits */
+ unsigned int dt_flag_mask;
};
/* If OR-ed with d_type, pending signals are not checked */
@@ -3524,7 +3526,9 @@ static inline bool dir_emit(struct dir_context *ctx,
const char *name, int namelen,
u64 ino, unsigned type)
{
- return ctx->actor(ctx, name, namelen, ctx->pos, ino, type);
+ unsigned int dt_mask = S_DT_MASK | ctx->dt_flag_mask;
+
+ return ctx->actor(ctx, name, namelen, ctx->pos, ino, type & dt_mask);
}
static inline bool dir_emit_dot(struct file *file, struct dir_context *ctx)
{
--
2.52.0