[PATCH v7 00/11] Data in direntry (dirdata) feature
Artem Blagodarenko <[email protected]> Tue, 28 Jul 2026 19:42:11 -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 v7:
This version addresses all actionable items raised by the Sashiko AI
review of v6. Several of the bot's flags were false positives because
the bot reviews each patch in isolation without knowledge of subsequent
patches in the same series; those are documented below for the record.
ISSUES FIXED BY LATER PATCHES IN THE SERIES (false positives)
--------------------------------------------------------------
[v6 08/11] "ext4_insert_dentry_data() ignores void *data" [High]
[v6 09/11] "ext4_add_entry() path fails to write LUFID" [High]
Both fixed in patch 10/11 ("ext4: add dirdata set/get helpers"),
which replaces the inline hash-writing code with ext4_dirdata_set(),
wiring up the data parameter properly. The call is at namei.c:2347.
[v6 08/11] "unsafe_memcpy receives a C comment" [Low]
The block comment was intentional as an annotation; converted to a
string literal per the macro contract.
[v6 11/11] "ABBA deadlock between i_rwsem and JBD2 handle" [High]
False positive: the ioctl acquires inode_lock() and then calls
ext4_dirdata_set_lufid(), which opens a journal handle internally.
This follows the standard ext4 pattern used in ext4_setattr() and
others -- VFS lock is held over a short journal handle, not the
reverse.
[v6 11/11] "Error path loses directory entry" [High]
False positive: the delete-then-re-add sequence in the ioctl goes
through ext4_add_entry() which itself is journalled; a failure there
triggers the journal abort path, not silent corruption.
[v6 07/11] "Incomplete renames break build" [Low]
The rename of ext4_dir_rec_len() and the updated ext4_find_dest_de()
signature are complete within the series; Sashiko was reviewing the
intermediate state of patch 07 only.
[v6 04/11, 06/11] Critical issues around dx_get_dx_info() offset
calculation and dx_root_limit() callers passing integers as pointers
Both are false positives: the refactoring in patch 04 is internally
consistent and the callers were updated in the same patch.
[v6 05/11, 07/11] TOCTOU / READ_ONCE in ext4_dirent_get_data_len()
These fields are read from a locked buffer_head in a journalled
context; the "malicious block device" threat model does not apply
here.
GENUINE ISSUES FIXED IN V7
---------------------------
Patch 04 (dx_root refactor): add upper-bound rec_len checks in
dx_get_dx_info() for both '.' and '..' entries to prevent an
out-of-bounds pointer advance on a corrupted directory block. Add
a missing info_length validation in dx_root_limit() to avoid returning
a negative limit when the dx_root header is malformed.
Patch 05 (dirdata format definitions): convert ext4_dirdata_next() from
a macro to a static inline function to prevent double-evaluation of the
ddh argument. Restructure struct ext4_dentry_param to store the LUFID
payload as a raw __u8 flexible array (edp_dfid[]) rather than embedding
struct ext4_dirent_fid directly; embedding a struct with a flexible
array member inside another struct violates C99 ยง6.7.2.1p2 and breaks
CONFIG_FORTIFY_SOURCE because __builtin_object_size() cannot reason
about the embedded flexible-array tail.
Patch 08 (dirdata feature): guard the dirdata extension parsing in
ext4_htree_store_dirent() with ext4_has_feature_dirdata() to prevent
misinterpreting stale or corrupted upper file_type bits on filesystems
where the feature is not enabled. Document data1/data2 in
ext4_init_new_dir_data() as reserved for callers that need to embed
dirdata into the '.' and '..' entries of a new directory; the in-place
write path requires proper decoding via ext4_dentry_get_fid() and a
dotdot write path, neither of which is implemented here, so the
parameters are reserved with a comment rather than wired through
incorrectly.
Patch 09 (LUFID rename support): fix ext4_setent() to return -ENODATA
when the source inode carries a LUFID but the destination entry has no
LUFID slot, rather than silently dropping the LUFID and returning
success. Fix the size-mismatch branch to clear all dirdata extension
flags (via &= EXT4_FT_MASK) instead of only ~EXT4_DIRENT_LUFID:
extensions are encoded in flag order (LUFID precedes INO64 and CFHASH),
so clearing LUFID alone while leaving trailing flags set causes a parser
to misread the orphaned LUFID bytes as the start of the next extension.
struct ext4_dirent_fid holds a flexible array of struct ext4_fid, so
the mismatch path is reachable when entries carry different numbers of
FIDs.
Patch 10 (dirdata set/get helpers): change ext4_dirdata_set() from void
to int return and propagate the error through ext4_insert_dentry_data()
and add_dirent_to_buf(). Previously a bounds-check failure called
EXT4_ERROR_INODE() but returned void, so the caller continued and
committed a directory entry to disk with the LUFID silently absent
while reporting success to userspace.
Patch 11 (EXT4_IOC_SET_LUFID ioctl): add an explicit check_sticky()
call in ext4_dirdata_set_lufid() after resolving the target inode.
inode_permission(MAY_WRITE) alone does not enforce the sticky-bit
restriction: on a world-writable sticky directory an unprivileged user
passes the MAY_WRITE check without any capability, yet sticky prevents
modifying another user's entry. The new check mirrors what
may_delete_dentry() does in the standard VFS unlink/rename path.
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: refactor dx_root to support variable dirent sizes
ext4: add dirdata format definitions and access helpers
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 | 34 +-
fs/ext4/ext4.h | 239 ++++++++-
fs/ext4/fast_commit.c | 1 +
fs/ext4/fast_commit.h | 1 +
fs/ext4/inline.c | 51 +-
fs/ext4/ioctl.c | 84 +++
fs/ext4/namei.c | 1023 ++++++++++++++++++++++++++++++-------
fs/ext4/sysfs.c | 2 +
include/uapi/linux/ext4.h | 14 +
9 files changed, 1223 insertions(+), 226 deletions(-)
--
2.43.7