projects/linux·blob master · refs

567 lines · 14.8 KiB

1// SPDX-License-Identifier: GPL-2.0
2/*
3 * KUnit tests for ext4 directory hash computation.
4 */
5
6#include <kunit/test.h>
7#include <kunit/resource.h>
8#include <linux/fs.h>
9#include <linux/stddef.h>
10#include <linux/string.h>
11#include <linux/unicode.h>
12#include "ext4.h"
13
14static void ext4_hash_init_fake_dir(struct inode *dir, struct super_block *sb)
15{
16 memset(sb, 0, sizeof(*sb));
17 memset(dir, 0, sizeof(*dir));
18 dir->i_sb = sb;
19 strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id));
20}
21
22static void ext4_hash_init_fake_dir_with_sbi(struct inode *dir,
23 struct super_block *sb,
24 struct ext4_sb_info *sbi)
25{
26 ext4_hash_init_fake_dir(dir, sb);
27 memset(sbi, 0, sizeof(*sbi));
28 sb->s_fs_info = sbi;
29 sbi->s_sb = sb;
30}
31
32#ifdef CONFIG_FS_ENCRYPTION
33static const struct fscrypt_operations ext4_hash_test_cryptops = {
34 .inode_info_offs =
35 (int)offsetof(struct ext4_inode_info, i_crypt_info) -
36 (int)offsetof(struct ext4_inode_info, vfs_inode),
37};
38#endif
39
40static void ext4_hash_init_fake_ext4_dir(struct ext4_inode_info *ei,
41 struct super_block *sb,
42 struct ext4_sb_info *sbi)
43{
44 struct inode *dir = &ei->vfs_inode;
45
46 memset(sb, 0, sizeof(*sb));
47 memset(ei, 0, sizeof(*ei));
48 memset(sbi, 0, sizeof(*sbi));
49
50 strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id));
51 sb->s_fs_info = sbi;
52 sbi->s_sb = sb;
53
54 dir->i_sb = sb;
55 dir->i_mode = S_IFDIR;
56
57#ifdef CONFIG_FS_ENCRYPTION
58 fscrypt_set_ops(sb, &ext4_hash_test_cryptops);
59#endif
60}
61
62struct ext4_dirhash_test_case {
63 const char *name;
64 u32 hash_version;
65 const char *input;
66 int len;
67 u32 seed[4];
68 bool use_seed;
69 u32 expected_hash;
70 u32 expected_minor_hash;
71};
72
73static const struct ext4_dirhash_test_case ext4_dirhash_test_cases[] = {
74 {
75 .name = "legacy_abc",
76 .hash_version = DX_HASH_LEGACY,
77 .input = "abc",
78 .len = 3,
79 .use_seed = false,
80 .expected_hash = 0x75afd992,
81 .expected_minor_hash = 0x00000000,
82 },
83 {
84 .name = "legacy_unsigned_abc",
85 .hash_version = DX_HASH_LEGACY_UNSIGNED,
86 .input = "abc",
87 .len = 3,
88 .use_seed = false,
89 .expected_hash = 0x75afd992,
90 .expected_minor_hash = 0x00000000,
91 },
92 {
93 .name = "half_md4_abc",
94 .hash_version = DX_HASH_HALF_MD4,
95 .input = "abc",
96 .len = 3,
97 .use_seed = false,
98 .expected_hash = 0xd196a868,
99 .expected_minor_hash = 0xc420eb28,
100 },
101 {
102 .name = "half_md4_unsigned_abc",
103 .hash_version = DX_HASH_HALF_MD4_UNSIGNED,
104 .input = "abc",
105 .len = 3,
106 .use_seed = false,
107 .expected_hash = 0xd196a868,
108 .expected_minor_hash = 0xc420eb28,
109 },
110 {
111 .name = "tea_abc",
112 .hash_version = DX_HASH_TEA,
113 .input = "abc",
114 .len = 3,
115 .use_seed = false,
116 .expected_hash = 0xb1435ec4,
117 .expected_minor_hash = 0x3f7eaa0e,
118 },
119 {
120 .name = "tea_unsigned_abc",
121 .hash_version = DX_HASH_TEA_UNSIGNED,
122 .input = "abc",
123 .len = 3,
124 .use_seed = false,
125 .expected_hash = 0xb1435ec4,
126 .expected_minor_hash = 0x3f7eaa0e,
127 },
128 {
129 .name = "empty_half_md4",
130 .hash_version = DX_HASH_HALF_MD4,
131 .input = "",
132 .len = 0,
133 .use_seed = false,
134 .expected_hash = 0xefcdab88,
135 .expected_minor_hash = 0x98badcfe,
136 },
137 {
138 .name = "half_md4_31bytes",
139 .hash_version = DX_HASH_HALF_MD4,
140 .input = "1234567890123456789012345678901",
141 .len = 31,
142 .use_seed = false,
143 .expected_hash = 0xc4db1f78,
144 .expected_minor_hash = 0xea23921b,
145 },
146 {
147 .name = "half_md4_32bytes",
148 .hash_version = DX_HASH_HALF_MD4,
149 .input = "12345678901234567890123456789012",
150 .len = 32,
151 .use_seed = false,
152 .expected_hash = 0xfa6cc63e,
153 .expected_minor_hash = 0x2f77bd1c,
154 },
155 {
156 .name = "half_md4_33bytes",
157 .hash_version = DX_HASH_HALF_MD4,
158 .input = "123456789012345678901234567890123",
159 .len = 33,
160 .use_seed = false,
161 .expected_hash = 0xdc0c2dec,
162 .expected_minor_hash = 0x5ca23365,
163 },
164 {
165 .name = "half_md4_unsigned_31bytes",
166 .hash_version = DX_HASH_HALF_MD4_UNSIGNED,
167 .input = "1234567890123456789012345678901",
168 .len = 31,
169 .use_seed = false,
170 .expected_hash = 0xc4db1f78,
171 .expected_minor_hash = 0xea23921b,
172 },
173 {
174 .name = "half_md4_unsigned_32bytes",
175 .hash_version = DX_HASH_HALF_MD4_UNSIGNED,
176 .input = "12345678901234567890123456789012",
177 .len = 32,
178 .use_seed = false,
179 .expected_hash = 0xfa6cc63e,
180 .expected_minor_hash = 0x2f77bd1c,
181 },
182 {
183 .name = "half_md4_unsigned_33bytes",
184 .hash_version = DX_HASH_HALF_MD4_UNSIGNED,
185 .input = "123456789012345678901234567890123",
186 .len = 33,
187 .use_seed = false,
188 .expected_hash = 0xdc0c2dec,
189 .expected_minor_hash = 0x5ca23365,
190 },
191 {
192 .name = "tea_15bytes",
193 .hash_version = DX_HASH_TEA,
194 .input = "123456789abcdef",
195 .len = 15,
196 .use_seed = false,
197 .expected_hash = 0xa562903a,
198 .expected_minor_hash = 0x6174a00f,
199 },
200 {
201 .name = "tea_16bytes",
202 .hash_version = DX_HASH_TEA,
203 .input = "1234567890abcdef",
204 .len = 16,
205 .use_seed = false,
206 .expected_hash = 0x8449f258,
207 .expected_minor_hash = 0x49a16d46,
208 },
209 {
210 .name = "tea_17bytes",
211 .hash_version = DX_HASH_TEA,
212 .input = "123456789abcdefgh",
213 .len = 17,
214 .use_seed = false,
215 .expected_hash = 0xf32ec10c,
216 .expected_minor_hash = 0x58ceae61,
217 },
218 {
219 .name = "half_md4_seeded",
220 .hash_version = DX_HASH_HALF_MD4,
221 .input = "same-name",
222 .len = 9,
223 .seed = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 },
224 .use_seed = true,
225 .expected_hash = 0x8aebf604,
226 .expected_minor_hash = 0x66ce48fe,
227 },
228 {
229 .name = "half_md4_non_ascii_signed",
230 .hash_version = DX_HASH_HALF_MD4,
231 .input = "\x80\x81\x82\x83\x84",
232 .len = 5,
233 .use_seed = false,
234 .expected_hash = 0x8bab0498,
235 .expected_minor_hash = 0xc326632d,
236 },
237 {
238 .name = "half_md4_non_ascii_unsigned",
239 .hash_version = DX_HASH_HALF_MD4_UNSIGNED,
240 .input = "\x80\x81\x82\x83\x84",
241 .len = 5,
242 .use_seed = false,
243 .expected_hash = 0xbc48596e,
244 .expected_minor_hash = 0xde0fad41,
245 },
246 {
247 .name = "tea_non_ascii_signed",
248 .hash_version = DX_HASH_TEA,
249 .input = "\x80\x81\x82\x83\x84",
250 .len = 5,
251 .use_seed = false,
252 .expected_hash = 0x21e3a154,
253 .expected_minor_hash = 0x90112c3d,
254 },
255 {
256 .name = "tea_non_ascii_unsigned",
257 .hash_version = DX_HASH_TEA_UNSIGNED,
258 .input = "\x80\x81\x82\x83\x84",
259 .len = 5,
260 .use_seed = false,
261 .expected_hash = 0x9b648616,
262 .expected_minor_hash = 0x011dd507,
263 },
264};
265
266static void test_ext4fs_dirhash_vectors(struct kunit *test)
267{
268 struct super_block *sb;
269 struct inode *dir;
270 int i;
271
272 sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
273 dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
274 KUNIT_ASSERT_NOT_NULL(test, sb);
275 KUNIT_ASSERT_NOT_NULL(test, dir);
276
277 ext4_hash_init_fake_dir(dir, sb);
278
279 for (i = 0; i < ARRAY_SIZE(ext4_dirhash_test_cases); i++) {
280 const struct ext4_dirhash_test_case *tc =
281 &ext4_dirhash_test_cases[i];
282 struct dx_hash_info hinfo;
283 int ret;
284
285 memset(&hinfo, 0, sizeof(hinfo));
286 hinfo.hash_version = tc->hash_version;
287 hinfo.seed = tc->use_seed ? (u32 *)tc->seed : NULL;
288
289 ret = ext4fs_dirhash(dir, tc->input, tc->len, &hinfo);
290
291 KUNIT_ASSERT_EQ_MSG(test, ret, 0, "case=%s", tc->name);
292 KUNIT_EXPECT_EQ_MSG(test, hinfo.hash, tc->expected_hash,
293 "case=%s", tc->name);
294 KUNIT_EXPECT_EQ_MSG(test, hinfo.minor_hash,
295 tc->expected_minor_hash,
296 "case=%s", tc->name);
297 }
298}
299
300static void test_ext4fs_dirhash_seed_changes_result(struct kunit *test)
301{
302 struct super_block *sb;
303 struct inode *dir;
304 u32 seed[4] = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 };
305 struct dx_hash_info plain = {
306 .hash_version = DX_HASH_HALF_MD4,
307 };
308 struct dx_hash_info seeded = {
309 .hash_version = DX_HASH_HALF_MD4,
310 .seed = seed,
311 };
312 int ret_plain, ret_seeded;
313
314 sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
315 dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
316 KUNIT_ASSERT_NOT_NULL(test, sb);
317 KUNIT_ASSERT_NOT_NULL(test, dir);
318
319 ext4_hash_init_fake_dir(dir, sb);
320
321 ret_plain = ext4fs_dirhash(dir, "same-name", 9, &plain);
322 ret_seeded = ext4fs_dirhash(dir, "same-name", 9, &seeded);
323
324 KUNIT_ASSERT_EQ(test, ret_plain, 0);
325 KUNIT_ASSERT_EQ(test, ret_seeded, 0);
326
327 KUNIT_EXPECT_TRUE(test,
328 plain.hash != seeded.hash ||
329 plain.minor_hash != seeded.minor_hash);
330}
331
332static void test_ext4fs_dirhash_invalid_version_returns_einval(struct kunit *test)
333{
334 struct super_block *sb;
335 struct inode *dir;
336 struct ext4_sb_info *sbi;
337 struct dx_hash_info hinfo = {
338 .hash = 0xdeadbeef,
339 .minor_hash = 0xcafebabe,
340 .hash_version = DX_HASH_LAST + 1,
341 };
342 int ret;
343
344 sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
345 dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
346 sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
347 KUNIT_ASSERT_NOT_NULL(test, sb);
348 KUNIT_ASSERT_NOT_NULL(test, dir);
349 KUNIT_ASSERT_NOT_NULL(test, sbi);
350
351 ext4_hash_init_fake_dir_with_sbi(dir, sb, sbi);
352
353 ret = ext4fs_dirhash(dir, "abc", 3, &hinfo);
354
355 KUNIT_EXPECT_EQ(test, ret, -EINVAL);
356 KUNIT_EXPECT_EQ(test, hinfo.hash, 0);
357 KUNIT_EXPECT_EQ(test, hinfo.minor_hash, 0);
358}
359
360static void test_ext4fs_dirhash_siphash_without_key_returns_einval(struct kunit *test)
361{
362 struct super_block *sb;
363 struct ext4_inode_info *ei;
364 struct inode *dir;
365 struct ext4_sb_info *sbi;
366 struct dx_hash_info hinfo = {
367 .hash_version = DX_HASH_SIPHASH,
368 };
369 int ret;
370
371 sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
372 ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
373 sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
374 KUNIT_ASSERT_NOT_NULL(test, sb);
375 KUNIT_ASSERT_NOT_NULL(test, ei);
376 KUNIT_ASSERT_NOT_NULL(test, sbi);
377
378 ext4_hash_init_fake_ext4_dir(ei, sb, sbi);
379 dir = &ei->vfs_inode;
380
381 ret = ext4fs_dirhash(dir, "name", strlen("name"), &hinfo);
382
383 KUNIT_EXPECT_EQ(test, ret, -EINVAL);
384}
385
386static void test_ext4fs_dirhash_signed_unsigned_differ_on_nonascii(struct kunit *test)
387{
388 struct super_block *sb;
389 struct inode *dir;
390 static const char input[] = "\x80\xff\x81\xfe\101bc";
391 struct dx_hash_info legacy_signed = {
392 .hash_version = DX_HASH_LEGACY,
393 };
394 struct dx_hash_info legacy_unsigned = {
395 .hash_version = DX_HASH_LEGACY_UNSIGNED,
396 };
397 struct dx_hash_info md4_signed = {
398 .hash_version = DX_HASH_HALF_MD4,
399 };
400 struct dx_hash_info md4_unsigned = {
401 .hash_version = DX_HASH_HALF_MD4_UNSIGNED,
402 };
403 struct dx_hash_info tea_signed = {
404 .hash_version = DX_HASH_TEA,
405 };
406 struct dx_hash_info tea_unsigned = {
407 .hash_version = DX_HASH_TEA_UNSIGNED,
408 };
409 int ret;
410
411 sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
412 dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
413 KUNIT_ASSERT_NOT_NULL(test, sb);
414 KUNIT_ASSERT_NOT_NULL(test, dir);
415
416 ext4_hash_init_fake_dir(dir, sb);
417
418 ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_signed);
419 KUNIT_ASSERT_EQ(test, ret, 0);
420 ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_unsigned);
421 KUNIT_ASSERT_EQ(test, ret, 0);
422 KUNIT_EXPECT_NE(test, legacy_signed.hash, legacy_unsigned.hash);
423
424 ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_signed);
425 KUNIT_ASSERT_EQ(test, ret, 0);
426 ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_unsigned);
427 KUNIT_ASSERT_EQ(test, ret, 0);
428 KUNIT_EXPECT_TRUE(test,
429 md4_signed.hash != md4_unsigned.hash ||
430 md4_signed.minor_hash != md4_unsigned.minor_hash);
431
432 ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_signed);
433 KUNIT_ASSERT_EQ(test, ret, 0);
434 ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_unsigned);
435 KUNIT_ASSERT_EQ(test, ret, 0);
436 KUNIT_EXPECT_TRUE(test,
437 tea_signed.hash != tea_unsigned.hash ||
438 tea_signed.minor_hash != tea_unsigned.minor_hash);
439}
440
441#if IS_ENABLED(CONFIG_UNICODE)
442KUNIT_DEFINE_ACTION_WRAPPER(utf8_unload_action, utf8_unload,
443 struct unicode_map *);
444static void test_ext4fs_dirhash_casefolded_names_hash_consistently(struct kunit *test)
445{
446 struct super_block *sb;
447 struct ext4_inode_info *ei;
448 struct ext4_sb_info *sbi;
449 struct unicode_map *um;
450 struct dx_hash_info h1 = {
451 .hash_version = DX_HASH_HALF_MD4,
452 };
453 struct dx_hash_info h2 = {
454 .hash_version = DX_HASH_HALF_MD4,
455 };
456 int ret, ret1, ret2;
457
458 sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
459 ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
460 sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
461 KUNIT_ASSERT_NOT_NULL(test, sb);
462 KUNIT_ASSERT_NOT_NULL(test, ei);
463 KUNIT_ASSERT_NOT_NULL(test, sbi);
464
465 um = utf8_load(UTF8_LATEST);
466 if (IS_ERR(um)) {
467 kunit_skip(test, "utf8_load(UTF8_LATEST) failed: %pe",
468 um);
469 return;
470 }
471
472 ret = kunit_add_action_or_reset(test, utf8_unload_action, um);
473 KUNIT_ASSERT_EQ(test, ret, 0);
474
475 ext4_hash_init_fake_ext4_dir(ei, sb, sbi);
476 sb->s_encoding = um;
477 ei->vfs_inode.i_flags |= S_CASEFOLD;
478
479 KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode));
480
481 ret1 = ext4fs_dirhash(&ei->vfs_inode, "Alpha", 5, &h1);
482 ret2 = ext4fs_dirhash(&ei->vfs_inode, "aLPHa", 5, &h2);
483
484 KUNIT_ASSERT_EQ(test, ret1, 0);
485 KUNIT_ASSERT_EQ(test, ret2, 0);
486 KUNIT_EXPECT_EQ(test, h1.hash, h2.hash);
487 KUNIT_EXPECT_EQ(test, h1.minor_hash, h2.minor_hash);
488}
489
490static void test_ext4fs_dirhash_casefold_fallback(struct kunit *test)
491{
492 struct super_block *sb_cf, *sb_plain;
493 struct ext4_inode_info *ei;
494 struct ext4_sb_info *sbi;
495 struct inode *plain_dir;
496 struct unicode_map *um;
497 static const char invalid_utf8[] = "\xc3\x28";
498 struct dx_hash_info folded_dir = {
499 .hash_version = DX_HASH_HALF_MD4,
500 };
501 struct dx_hash_info plain = {
502 .hash_version = DX_HASH_HALF_MD4,
503 };
504 int ret, ret_cf, ret_plain;
505
506 sb_cf = kunit_kzalloc(test, sizeof(*sb_cf), GFP_KERNEL);
507 sb_plain = kunit_kzalloc(test, sizeof(*sb_plain), GFP_KERNEL);
508 ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
509 sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
510 plain_dir = kunit_kzalloc(test, sizeof(*plain_dir), GFP_KERNEL);
511 KUNIT_ASSERT_NOT_NULL(test, sb_cf);
512 KUNIT_ASSERT_NOT_NULL(test, sb_plain);
513 KUNIT_ASSERT_NOT_NULL(test, ei);
514 KUNIT_ASSERT_NOT_NULL(test, sbi);
515 KUNIT_ASSERT_NOT_NULL(test, plain_dir);
516
517 um = utf8_load(UTF8_LATEST);
518 if (IS_ERR(um)) {
519 kunit_skip(test, "utf8_load(UTF8_LATEST) failed: %pe",
520 um);
521 return;
522 }
523
524 ret = kunit_add_action_or_reset(test, utf8_unload_action, um);
525 KUNIT_ASSERT_EQ(test, ret, 0);
526
527 ext4_hash_init_fake_ext4_dir(ei, sb_cf, sbi);
528 sb_cf->s_encoding = um;
529 ei->vfs_inode.i_flags |= S_CASEFOLD;
530
531 KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode));
532
533 ext4_hash_init_fake_dir(plain_dir, sb_plain);
534
535 ret_cf = ext4fs_dirhash(&ei->vfs_inode, invalid_utf8,
536 sizeof(invalid_utf8) - 1, &folded_dir);
537 ret_plain = ext4fs_dirhash(plain_dir, invalid_utf8,
538 sizeof(invalid_utf8) - 1, &plain);
539
540 KUNIT_ASSERT_EQ(test, ret_cf, 0);
541 KUNIT_ASSERT_EQ(test, ret_plain, 0);
542 KUNIT_EXPECT_EQ(test, folded_dir.hash, plain.hash);
543 KUNIT_EXPECT_EQ(test, folded_dir.minor_hash, plain.minor_hash);
544}
545#endif
546
547static struct kunit_case ext4_hash_test_cases[] = {
548 KUNIT_CASE(test_ext4fs_dirhash_vectors),
549 KUNIT_CASE(test_ext4fs_dirhash_seed_changes_result),
550 KUNIT_CASE(test_ext4fs_dirhash_invalid_version_returns_einval),
551 KUNIT_CASE(test_ext4fs_dirhash_siphash_without_key_returns_einval),
552 KUNIT_CASE(test_ext4fs_dirhash_signed_unsigned_differ_on_nonascii),
553#if IS_ENABLED(CONFIG_UNICODE)
554 KUNIT_CASE(test_ext4fs_dirhash_casefolded_names_hash_consistently),
555 KUNIT_CASE(test_ext4fs_dirhash_casefold_fallback),
556#endif
557 {}
558};
559
560static struct kunit_suite ext4_hash_test_suite = {
561 .name = "ext4_hash",
562 .test_cases = ext4_hash_test_cases,
563};
564
565kunit_test_suites(&ext4_hash_test_suite);
566
567MODULE_LICENSE("GPL");