[PATCH v2 0/3] ocfs2: deal with legacy signed name hash values
From: Joseph Qi
Date: Fri Oct 09 2026 - 04:34:39 EST
Commit 3bc753c06dd0 ("kbuild: treat char as always unsigned") turned on
-funsigned-char globally, which changed the result of two naked 'char'
loads in ocfs2 hashing code:
str2hashbuf() val = msg[i] + (val << 8);
ocfs2_xattr_name_hash() hash = (hash << 5) ^ (hash >> 27) ^ *name++;
A name byte >= 0x80 used to sign-extend and now zero-extends. Both
results are written to disk, the first into the dx leaf when a directory
entry is indexed and the second into xe_name_hash when an xattr is
stored, so anything written by a pre-6.2 kernel is looked up under a
different hash and is no longer found. readdir and listxattr still list
those names because neither of them hashes, which makes this look like
the filesystem losing entries rather than like a lookup bug. Anderson
Ferneda reported it that way for directories with non-ASCII names [1].
Patches 1 and 3 do what ext4 did in commit f3bbac32475b2 ("ext4: deal
with legacy signed xattr name hash values"): look up with the current
unsigned hash, retry with the legacy signed one on a miss, and always
store new entries under the unsigned hash. The retry is skipped when the
two hashes come out equal, so an ASCII name does not walk the index
twice. Both hash functions now spell out the signedness instead of
leaving it to -funsigned-char, as commit 854f0912f813 ("ext4: make xattr
char unsignedness in hash explicit") did, so a backport to a kernel
without the flag still computes both variants correctly.
Patch 2 is a separate fix, placed before patch 3 so that every commit in
the series is correct on its own. ocfs2_check_xattr_bucket_collision()
recomputes the hash of the name to decide whether splitting a full bucket
can make room for the entry being set. That is right for a new entry and
wrong for one that already exists, since an update leaves xe_name_hash
alone. Against a bucket holding legacy hashes the comparison does not
match, so the split goes ahead and cannot help: a bucket whose entries
all share one hash has no divide position. How that ends depends on
where the unsigned hash sorts. Below the legacy ones, the set fails with
-ENOSPC after growing the tree for nothing. Above them, it lands in the
empty bucket the split just appended and stores a second copy of the
entry there while the original stays behind, so listxattr reports the
name twice. Using the stored hash closes both.
Nothing retires a legacy hash at runtime. Deleting or updating an entry
does not rewrite it, and the only code that rehashes existing dirents is
ocfs2_expand_inline_dir() on the inline to extent conversion, so the
retry stays for as long as the legacy entries do. Rebuilding the index
offline is what removes it for good.
Changes since v1:
- add patch 2, so that ocfs2_check_xattr_bucket_collision() uses the
stored hash of an existing entry rather than recomputing it, to
address Sashiko's comments on v1 patch 2/2;
- v1 patch 2/2 is now patch 3, with no code change;
- patch 1 is unchanged and picks up Heming's Reviewed-by.
[1] https://lore.kernel.org/ocfs2-devel/CP5P284MB2780AC2C3C2AB2CF2B6CB7AFBE942@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/
Joseph Qi (3):
ocfs2: deal with legacy signed dir index name hash values
ocfs2: use the stored hash when checking xattr bucket collision
ocfs2: deal with legacy signed xattr name hash values
fs/ocfs2/dir.c | 76 +++++++++++++++++++++++++-----
fs/ocfs2/xattr.c | 120 ++++++++++++++++++++++++++++++++++++++---------
2 files changed, 164 insertions(+), 32 deletions(-)
--
2.39.3