[PATCH v10 00/11] Data in direntry (dirdata) feature

Artem Blagodarenko <[email protected]> Fri, 31 Jul 2026 11:37:34 -0400
Newsgroups org.kernel.vger.linux-ext4
Message-ID <[email protected]>
EXT4 currently stores an 8-byte hash in the directory entry immediately
after the file name to support simultaneous fscrypt and casefold
functionality.

The dirdata feature provides a general mechanism to store multiple
metadata records in each directory entry after the NUL filename
terminator. The unused high 4 bits of 'file_type' indicate which
records are present; each record starts with a 1-byte length field,
providing forward compatibility.

The first user of this feature is LUFID (Locally Unique File ID), an
identifier that must survive rename and be accessible from directory
readdir without an inode lookup.

e2fsprogs support is provided in a separate patch series.

Thanks to Sashiko AI review for identifying several correctness and
safety issues in earlier versions of this series.

Changes in v10:

Reorder patches 4 and 5: "dirdata format definitions and access
helpers" now precedes "refactor dx_root to support variable dirent
sizes". The refactor patch uses ext4_dir_entry_len(), which in turn
depends on types and functions from the format definitions patch;
placing format definitions first makes the dependency explicit.

Patch 04 (dirdata format definitions): restore ext4_dirdata_next()
helper. v9 removed it as unused dead code; it is restored as a public
accessor for external consumers of the dirdata format (such as the
Lustre filesystem module) that traverse extension records without
going through ext4_dirent_get_data_len().

Patch 05 (refactor dx_root): fix OOB write in dx_get_dx_info().  The
bounds check ensured only sizeof(struct dx_root_info) (8 bytes) fits
after the '.' and '..' entries, but callers in make_indexed_dir()
immediately write the full sizeof(struct dx_entry) (8 bytes) that
follows.  A crafted image with '.' and '..' lengths summing to
(blocksize - 8) passes the old check, places dx_info at
(base + blocksize - 8), and causes all three dx_set_*() writes to land
past the end of the block buffer.  Fix by adding sizeof(struct dx_entry)
to the guard.

Reported-by: sashiko-bot <[email protected]>
Closes: https://sashiko.dev/#/patchset/[email protected]?part=4

Patch 09 (LUFID rename support): fix two duplicate
ext4_fc_mark_ineligible(EXT4_FC_REASON_DIRDATA) calls.

  - __ext4_link() called ext4_fc_mark_ineligible() before invoking
    __ext4_add_entry(), which calls it again when a LUFID payload is
    present.  Remove the redundant outer call; __ext4_add_entry() is
    the authoritative site.

  - ext4_rename() called ext4_fc_mark_ineligible() unconditionally at
    the top of the dirdata block for both the !new.bh path (which goes
    through __ext4_add_entry() and marks ineligible there) and the
    new.bh path (which calls ext4_setent() and needs the mark).  Move
    the call into the new.bh branch, after ext4_setent() succeeds.

Reported-by: sashiko-bot <[email protected]>
Closes: https://sashiko.dev/#/patchset/[email protected]?part=8

Patch 10 (dirdata set/get helpers): fix OOB read in
ext4_search_dir().  ext4_match() calls ext4_dirdata_get() for
casefolded+encrypted+dirdata directories, which decodes de->rec_len
and uses it as the upper bound for all pointer arithmetic.
ext4_search_dir() previously validated de_len only after ext4_match()
returned.  A crafted block with de->rec_len encoding a value larger
than the block size passes the loop guard and reaches ext4_match(),
allowing ext4_dirdata_get() to read past the end of the block buffer.
Fix by decoding and bounding de_len at the top of the loop before
ext4_match() is called.

Reported-by: sashiko-bot <[email protected]>
Closes: https://sashiko.dev/#/patchset/[email protected]?part=10

Patch 11 (SET_LUFID ioctl): three fixes to ext4_dirdata_set_lufid().

  - Enforce IS_APPEND(dir) on the parent directory.  The ioctl calls
    ext4_delete_entry() directly, bypassing may_delete(), which is the
    only VFS path that enforces append-only semantics for directories.
    inode_permission() blocks IS_IMMUTABLE but not IS_APPEND.  Add an
    explicit IS_APPEND(dir) check consistent with may_delete().

  - Restore the original LUFID on rollback.  If ext4_add_entry() fails
    after ext4_delete_entry() succeeds, the rollback path previously
    re-added the entry with no LUFID, silently orphaning any FID->path
    mapping the entry carried.  Fix by snapshotting the on-disk LUFID
    before deletion with ext4_lufid_snapshot() and attaching the
    snapshot to the rollback dentry's d_fsdata, so ext4_add_entry()
    restores the original LUFID.

  - Preserve on-disk filename case for re-add.  For non-encrypted
    casefolded directories, ext4_find_entry() matches
    case-insensitively, so de->name may differ from the caller-supplied
    filename.  Using the caller's form for the re-add silently renames
    the entry.  Fix by copying de->name into a stack buffer immediately
    after the phase-2 lookup and using those bytes for both the new
    entry and the rollback entry.  For encrypted directories (with or
    without casefold), de->name holds raw ciphertext; use the plaintext
    d_name instead and let ext4_fname_setup_filename() re-derive the
    ciphertext, avoiding double-encryption.

Reported-by: sashiko-bot <[email protected]>
Closes: https://sashiko.dev/#/patchset/[email protected]?part=11

Artem Blagodarenko (11):
  ext4: validate count against limit in ext4_dx_csum_verify/_set
  ext4: replace ext4_dir_entry with ext4_dir_entry_2
  ext4: add ext4_dir_entry_is_tail()
  ext4: add dirdata format definitions and access helpers
  ext4: refactor dx_root to support variable dirent sizes
  ext4: add ext4_dir_entry_len()
  ext4: rename ext4_dir_rec_len() and clarify dirdata usage
  ext4: dirdata feature
  ext4: add dirdata LUFID support for directory entry rename
  ext4: add dirdata set/get helpers
  ext4: Add EXT4_IOC_SET_LUFID ioctl for setting LUFID on directory
    entries

 fs/ext4/dir.c             |   49 +-
 fs/ext4/ext4.h            |  237 ++++++-
 fs/ext4/fast_commit.c     |    1 +
 fs/ext4/fast_commit.h     |    1 +
 fs/ext4/inline.c          |   67 +-
 fs/ext4/ioctl.c           |   85 +++
 fs/ext4/namei.c           | 1264 +++++++++++++++++++++++++++++++------
 fs/ext4/sysfs.c           |    2 +
 include/uapi/linux/ext4.h |   14 +
 9 files changed, 1470 insertions(+), 250 deletions(-)

--
2.43.7