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 14 static 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 22 static 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 33 static 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 40 static 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 62 struct 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 73 static 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 266 static 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 300 static 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 332 static 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 360 static 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 386 static 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) 442 KUNIT_DEFINE_ACTION_WRAPPER(utf8_unload_action, utf8_unload, 443 struct unicode_map *); 444 static 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 490 static 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 547 static 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 560 static struct kunit_suite ext4_hash_test_suite = { 561 .name = "ext4_hash", 562 .test_cases = ext4_hash_test_cases, 563 }; 564 565 kunit_test_suites(&ext4_hash_test_suite); 566 567 MODULE_LICENSE("GPL"); 568