Skip to main content

fs_ext4/
hash.rs

1//! Directory-index (htree) name hashes.
2//!
3//! An indexed ext4 directory orders its entries by a 32-bit hash of each
4//! name (kernel.org ext4 documentation, "Hash Tree Directories"). The
5//! `dx_root` block records which of three algorithms the directory uses:
6//!
7//! * **legacy** — a small multiplicative/xor recurrence over the name's
8//!   bytes, producing only a major hash;
9//! * **half MD4** — the three rounds of MD4 (RFC 1320) applied to 32-byte
10//!   blocks of the name, run as a chained compression function whose
11//!   chaining value starts at the filesystem's hash seed;
12//! * **TEA** — the Tiny Encryption Algorithm (Wheeler & Needham, FSE 1994)
13//!   used the same way over 16-byte blocks, with the name block as the
14//!   cipher key and the chaining value as the plaintext.
15//!
16//! Each algorithm comes in a "signed" and an "unsigned" flavour, which
17//! differ only in whether a name byte >= 0x80 is widened to 32 bits with or
18//! without sign extension. For pure-ASCII names the two agree.
19//!
20//! The byte-level format facts below (padding word, packing order, block
21//! sizes, which chaining words become major/minor, the legacy constants)
22//! were taken from the BSD-licensed FreeBSD and lwext4 descriptions of this
23//! format and confirmed against `debugfs dx_hash` from e2fsprogs; see
24//! `tests/htree_hash_vectors.rs` and `tests/htree_hash_differential.rs`.
25
26/// Hash versions as recorded in `dx_root_info.hash_version` (kernel.org ext4
27/// documentation, "Hash Tree Directories").
28#[repr(u8)]
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum HashVersion {
31    Legacy = 0,
32    HalfMd4 = 1,
33    Tea = 2,
34    LegacyUnsigned = 3,
35    HalfMd4Unsigned = 4,
36    TeaUnsigned = 5,
37}
38
39impl HashVersion {
40    /// Map an on-disk hash version byte to a supported version.
41    pub fn from_u8(v: u8) -> Option<Self> {
42        Some(match v {
43            0 => Self::Legacy,
44            1 => Self::HalfMd4,
45            2 => Self::Tea,
46            3 => Self::LegacyUnsigned,
47            4 => Self::HalfMd4Unsigned,
48            5 => Self::TeaUnsigned,
49            _ => return None,
50        })
51    }
52
53    /// True for the three "unsigned" variants (codes 3, 4, 5).
54    pub fn is_unsigned(self) -> bool {
55        matches!(
56            self,
57            Self::LegacyUnsigned | Self::HalfMd4Unsigned | Self::TeaUnsigned
58        )
59    }
60
61    /// How this version widens a name byte into a 32-bit quantity.
62    fn byte_widening(self) -> ByteWidening {
63        if self.is_unsigned() {
64            ByteWidening::ZeroExtend
65        } else {
66            ByteWidening::SignExtend
67        }
68    }
69}
70
71/// Result of [`name_hash`]: `major` orders entries in the index, `minor`
72/// breaks ties between names whose major hashes collide.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct NameHash {
75    pub major: u32,
76    pub minor: u32,
77}
78
79/// Hash `name` for a directory index using `version` and the superblock's
80/// `s_hash_seed` (as four little-endian 32-bit words).
81pub fn name_hash(name: &[u8], version: HashVersion, seed: &[u32; 4]) -> NameHash {
82    let widening = version.byte_widening();
83    let (major, minor) = match version {
84        HashVersion::Legacy | HashVersion::LegacyUnsigned => {
85            (LegacyRecurrence::digest(name, widening), 0)
86        }
87        HashVersion::HalfMd4 | HashVersion::HalfMd4Unsigned => {
88            chain_blocks::<8, Md4HalfRounds>(name, widening, seed)
89        }
90        HashVersion::Tea | HashVersion::TeaUnsigned => {
91            chain_blocks::<4, TeaCipher>(name, widening, seed)
92        }
93    };
94    // The lowest bit of a major hash is never part of the value (every
95    // `debugfs dx_hash` major is even, for every version): index entries
96    // keep that bit free for their own bookkeeping.
97    let mut major = major & !1;
98    // 0xFFFFFFFE is reserved: the directory index uses it as its
99    // end-of-directory marker, so no name may hash to it. A name that
100    // would is given the next even value down, 0xFFFFFFFC, as both BSD
101    // references do; that is what directories written by other ext4
102    // implementations contain. (`debugfs dx_hash` prints the value before
103    // this remap, which `tests/htree_hash_differential.rs` accounts for.)
104    if major == END_OF_DIRECTORY_MAJOR {
105        major = END_OF_DIRECTORY_MAJOR - 2;
106    }
107    NameHash { major, minor }
108}
109
110/// The major hash value the directory index reserves to mean "end of
111/// directory". No name is ever given it (see [`name_hash`]).
112const END_OF_DIRECTORY_MAJOR: u32 = 0xFFFF_FFFE;
113
114/// The version to use for a directory whose `dx_root` records
115/// `root_version`, given the superblock's `EXT2_FLAGS_UNSIGNED_HASH` flag.
116/// `dx_root` only ever records the signed codes (0, 1, 2); when the flag is
117/// set, the matching unsigned variant (3, 4, 5) applies instead.
118pub fn effective_version(root_version: HashVersion, unsigned_hash: bool) -> HashVersion {
119    if !unsigned_hash {
120        return root_version;
121    }
122    match root_version {
123        HashVersion::Legacy => HashVersion::LegacyUnsigned,
124        HashVersion::HalfMd4 => HashVersion::HalfMd4Unsigned,
125        HashVersion::Tea => HashVersion::TeaUnsigned,
126        already_unsigned => already_unsigned,
127    }
128}
129
130// ---------------------------------------------------------------------------
131// Turning name bytes into 32-bit words
132// ---------------------------------------------------------------------------
133
134/// Whether a name byte is treated as a signed or an unsigned `char` when it
135/// is widened to 32 bits. This is the only difference between the signed
136/// and unsigned hash versions.
137#[derive(Debug, Clone, Copy)]
138enum ByteWidening {
139    SignExtend,
140    ZeroExtend,
141}
142
143impl ByteWidening {
144    fn widen(self, byte: u8) -> u32 {
145        match self {
146            // 0x80..=0xFF become 0xFFFFFF80..=0xFFFFFFFF.
147            ByteWidening::SignExtend => byte as i8 as i32 as u32,
148            ByteWidening::ZeroExtend => u32::from(byte),
149        }
150    }
151}
152
153/// Splits a name into fixed-size blocks of `N` words for the block-based
154/// algorithms (half MD4: 8 words, TEA: 4 words).
155///
156/// Each block is built from the bytes still unconsumed at that point:
157///
158/// * A "fill word" repeats the low byte of the number of bytes still
159///   unconsumed (the whole remaining tail, not just this block's share)
160///   in all four byte lanes.
161/// * Each word takes up to four consecutive name bytes. It starts as the
162///   fill word, and each byte in turn is appended at the bottom: the word
163///   shifts left by 8 and the widened byte is *added* (with wrapping). With
164///   four bytes the fill word is shifted out completely and the first byte
165///   ends up most significant; with fewer, the top lanes keep fill bytes.
166///   Under sign extension, adding a byte >= 0x80 borrows from the lanes
167///   above it, which is what makes the signed variants differ.
168/// * Words past the end of the name are the plain fill word.
169///
170/// A block consumes up to `4 * N` bytes. An empty name yields no blocks
171/// at all, so the hash is the seed state untouched (confirmed with
172/// `debugfs dx_hash` on the empty name).
173struct WordFeeder<'a, const N: usize> {
174    rest: &'a [u8],
175    widening: ByteWidening,
176}
177
178impl<'a, const N: usize> WordFeeder<'a, N> {
179    const BLOCK_BYTES: usize = 4 * N;
180
181    fn new(name: &'a [u8], widening: ByteWidening) -> Self {
182        Self {
183            rest: name,
184            widening,
185        }
186    }
187
188    fn fill_word(remaining: usize) -> u32 {
189        let r = remaining as u32;
190        r | (r << 8) | (r << 16) | (r << 24)
191    }
192
193    fn pack(&self, bytes: &[u8], fill: u32) -> u32 {
194        bytes.iter().fold(fill, |word, &b| {
195            (word << 8).wrapping_add(self.widening.widen(b))
196        })
197    }
198}
199
200impl<const N: usize> Iterator for WordFeeder<'_, N> {
201    type Item = [u32; N];
202
203    fn next(&mut self) -> Option<[u32; N]> {
204        if self.rest.is_empty() {
205            return None;
206        }
207        let fill = Self::fill_word(self.rest.len());
208        let take = self.rest.len().min(Self::BLOCK_BYTES);
209        let (this_block, later) = self.rest.split_at(take);
210        let mut block = [fill; N];
211        for (word, bytes) in block.iter_mut().zip(this_block.chunks(4)) {
212            *word = self.pack(bytes, fill);
213        }
214        self.rest = later;
215        Some(block)
216    }
217}
218
219// ---------------------------------------------------------------------------
220// Block mixers and the loop that drives them
221// ---------------------------------------------------------------------------
222
223/// A compression step that folds one block of name words into a 128-bit
224/// chaining state.
225trait BlockMixer<const N: usize> {
226    /// Fold `block` into `state`.
227    fn absorb(state: &mut [u32; 4], block: &[u32; N]);
228    /// Which chaining words are reported as (major, minor).
229    fn report(state: &[u32; 4]) -> (u32, u32);
230}
231
232/// The initial chaining values from RFC 1320 section 3.3 (words A, B, C, D).
233/// They are used whenever the superblock's hash seed is entirely zero —
234/// an unset seed — which `debugfs dx_hash -s 00000000-...` confirms.
235/// A seed with any non-zero word is used exactly as given.
236const RFC1320_INITIAL_STATE: [u32; 4] = [0x6745_2301, 0xEFCD_AB89, 0x98BA_DCFE, 0x1032_5476];
237
238fn starting_state(seed: &[u32; 4]) -> [u32; 4] {
239    if seed.iter().all(|&w| w == 0) {
240        RFC1320_INITIAL_STATE
241    } else {
242        *seed
243    }
244}
245
246/// Feed every `N`-word block of `name` through mixer `M`, starting from
247/// the seed, and report the mixer's (major, minor) words.
248fn chain_blocks<const N: usize, M: BlockMixer<N>>(
249    name: &[u8],
250    widening: ByteWidening,
251    seed: &[u32; 4],
252) -> (u32, u32) {
253    let mut state = starting_state(seed);
254    for block in WordFeeder::<N>::new(name, widening) {
255        M::absorb(&mut state, &block);
256    }
257    M::report(&state)
258}
259
260// --- Half MD4 --------------------------------------------------------------
261
262/// MD4's three rounds (RFC 1320 section 3.4) cut down to an 8-word block:
263/// each round makes two passes of four steps instead of four passes, and
264/// the result is added back into the chaining state (feed-forward), as in
265/// MD4. Major is chaining word B, minor is word C.
266struct Md4HalfRounds;
267
268/// RFC 1320 round 1 auxiliary function: bitwise "if X then Y else Z".
269fn rfc1320_f(x: u32, y: u32, z: u32) -> u32 {
270    (x & y) | (!x & z)
271}
272
273/// RFC 1320 round 2 auxiliary function: bitwise majority of X, Y, Z.
274fn rfc1320_g(x: u32, y: u32, z: u32) -> u32 {
275    (x & y) | (x & z) | (y & z)
276}
277
278/// RFC 1320 round 3 auxiliary function: bitwise parity.
279fn rfc1320_h(x: u32, y: u32, z: u32) -> u32 {
280    x ^ y ^ z
281}
282
283/// RFC 1320 additive constants for rounds 2 and 3 (round 1 adds none).
284const RFC1320_ROUND2_CONSTANT: u32 = 0x5A82_7999;
285const RFC1320_ROUND3_CONSTANT: u32 = 0x6ED9_EBA1;
286
287/// One MD4 round over an 8-word block: which word each of the 8 steps
288/// reads and how far it rotates.
289struct Md4Round {
290    mix: fn(u32, u32, u32) -> u32,
291    constant: u32,
292    words: [usize; 8],
293    rotations: [u32; 4],
294}
295
296const HALF_MD4_ROUNDS: [Md4Round; 3] = [
297    Md4Round {
298        mix: rfc1320_f,
299        constant: 0,
300        words: [0, 1, 2, 3, 4, 5, 6, 7],
301        rotations: [3, 7, 11, 19],
302    },
303    Md4Round {
304        mix: rfc1320_g,
305        constant: RFC1320_ROUND2_CONSTANT,
306        words: [1, 3, 5, 7, 0, 2, 4, 6],
307        rotations: [3, 5, 9, 13],
308    },
309    Md4Round {
310        mix: rfc1320_h,
311        constant: RFC1320_ROUND3_CONSTANT,
312        words: [3, 7, 2, 6, 1, 5, 0, 4],
313        rotations: [3, 9, 11, 15],
314    },
315];
316
317impl BlockMixer<8> for Md4HalfRounds {
318    fn absorb(state: &mut [u32; 4], block: &[u32; 8]) {
319        // Registers A, B, C, D live at indices 0..4. As in RFC 1320, the
320        // step targets cycle A, D, C, B, and each step's three inputs are
321        // the registers that follow its target in the order A B C D.
322        let mut reg = *state;
323        for round in &HALF_MD4_ROUNDS {
324            for (step, &word) in round.words.iter().enumerate() {
325                let target = (4 - step % 4) % 4;
326                let x = reg[(target + 1) % 4];
327                let y = reg[(target + 2) % 4];
328                let z = reg[(target + 3) % 4];
329                reg[target] = reg[target]
330                    .wrapping_add((round.mix)(x, y, z))
331                    .wrapping_add(block[word])
332                    .wrapping_add(round.constant)
333                    .rotate_left(round.rotations[step % 4]);
334            }
335        }
336        for (s, r) in state.iter_mut().zip(reg) {
337            *s = s.wrapping_add(r);
338        }
339    }
340
341    fn report(state: &[u32; 4]) -> (u32, u32) {
342        (state[1], state[2])
343    }
344}
345
346// --- TEA -------------------------------------------------------------------
347
348/// TEA (Wheeler & Needham) with the 4-word name block as the 128-bit key
349/// and the first two chaining words as the 64-bit plaintext. Only 16
350/// cycles are run rather than the paper's recommended 32. The ciphertext
351/// is added back into those two chaining words; the other two are never
352/// touched. Major is chaining word 0, minor is word 1.
353struct TeaCipher;
354
355/// The TEA key schedule constant, 2^32 / golden ratio (TEA paper).
356const TEA_DELTA: u32 = 0x9E37_79B9;
357const TEA_CYCLES: u32 = 16;
358
359/// The TEA encipher routine from the paper, for `cycles` cycles.
360fn tea_encipher(plain: [u32; 2], key: &[u32; 4], cycles: u32) -> [u32; 2] {
361    let [mut y, mut z] = plain;
362    let mut sum: u32 = 0;
363    for _ in 0..cycles {
364        sum = sum.wrapping_add(TEA_DELTA);
365        y = y.wrapping_add(
366            (z << 4).wrapping_add(key[0]) ^ z.wrapping_add(sum) ^ (z >> 5).wrapping_add(key[1]),
367        );
368        z = z.wrapping_add(
369            (y << 4).wrapping_add(key[2]) ^ y.wrapping_add(sum) ^ (y >> 5).wrapping_add(key[3]),
370        );
371    }
372    [y, z]
373}
374
375impl BlockMixer<4> for TeaCipher {
376    fn absorb(state: &mut [u32; 4], block: &[u32; 4]) {
377        let [y, z] = tea_encipher([state[0], state[1]], block, TEA_CYCLES);
378        state[0] = state[0].wrapping_add(y);
379        state[1] = state[1].wrapping_add(z);
380    }
381
382    fn report(state: &[u32; 4]) -> (u32, u32) {
383        (state[0], state[1])
384    }
385}
386
387// --- Legacy ----------------------------------------------------------------
388
389/// The original ext3 directory hash: a three-term recurrence. Each byte,
390/// widened and multiplied by a fixed odd constant, is xored into the most
391/// recent term and added to the term before it; a sum that lands in the
392/// upper half of the 32-bit range is pulled back down by 2^31 - 1. The
393/// result is the last term doubled. It ignores the hash seed and has no
394/// minor hash.
395struct LegacyRecurrence;
396
397impl LegacyRecurrence {
398    const MULTIPLIER: u32 = 0x006D_22F5;
399    const FIRST_TERMS: (u32, u32) = (0x37AB_E8F9, 0x12A3_FE2D);
400    const REDUCTION: u32 = 0x7FFF_FFFF;
401
402    fn digest(name: &[u8], widening: ByteWidening) -> u32 {
403        let (_older, newest) = name
404            .iter()
405            .fold(Self::FIRST_TERMS, |(older, newer), &byte| {
406                let scaled = widening.widen(byte).wrapping_mul(Self::MULTIPLIER);
407                let mut next = older.wrapping_add(newer ^ scaled);
408                if next & 0x8000_0000 != 0 {
409                    next = next.wrapping_sub(Self::REDUCTION);
410                }
411                (newer, next)
412            });
413        newest << 1
414    }
415}