Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568// SPDX-License-Identifier: GPL-2.0/* * KUnit tests for ext4 directory hash computation. */
#include <kunit/test.h>#include <kunit/resource.h>#include <linux/fs.h>#include <linux/stddef.h>#include <linux/string.h>#include <linux/unicode.h>#include "ext4.h"
static void ext4_hash_init_fake_dir(struct inode *dir, struct super_block *sb){ memset(sb, 0, sizeof(*sb)); memset(dir, 0, sizeof(*dir)); dir->i_sb = sb; strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id));}
static void ext4_hash_init_fake_dir_with_sbi(struct inode *dir, struct super_block *sb, struct ext4_sb_info *sbi){ ext4_hash_init_fake_dir(dir, sb); memset(sbi, 0, sizeof(*sbi)); sb->s_fs_info = sbi; sbi->s_sb = sb;}
#ifdef CONFIG_FS_ENCRYPTIONstatic const struct fscrypt_operations ext4_hash_test_cryptops = { .inode_info_offs = (int)offsetof(struct ext4_inode_info, i_crypt_info) - (int)offsetof(struct ext4_inode_info, vfs_inode),};#endif
static void ext4_hash_init_fake_ext4_dir(struct ext4_inode_info *ei, struct super_block *sb, struct ext4_sb_info *sbi){ struct inode *dir = &ei->vfs_inode;
memset(sb, 0, sizeof(*sb)); memset(ei, 0, sizeof(*ei)); memset(sbi, 0, sizeof(*sbi));
strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id)); sb->s_fs_info = sbi; sbi->s_sb = sb;
dir->i_sb = sb; dir->i_mode = S_IFDIR;
#ifdef CONFIG_FS_ENCRYPTION fscrypt_set_ops(sb, &ext4_hash_test_cryptops);#endif}
struct ext4_dirhash_test_case { const char *name; u32 hash_version; const char *input; int len; u32 seed[4]; bool use_seed; u32 expected_hash; u32 expected_minor_hash;};
static const struct ext4_dirhash_test_case ext4_dirhash_test_cases[] = { { .name = "legacy_abc", .hash_version = DX_HASH_LEGACY, .input = "abc", .len = 3, .use_seed = false, .expected_hash = 0x75afd992, .expected_minor_hash = 0x00000000, }, { .name = "legacy_unsigned_abc", .hash_version = DX_HASH_LEGACY_UNSIGNED, .input = "abc", .len = 3, .use_seed = false, .expected_hash = 0x75afd992, .expected_minor_hash = 0x00000000, }, { .name = "half_md4_abc", .hash_version = DX_HASH_HALF_MD4, .input = "abc", .len = 3, .use_seed = false, .expected_hash = 0xd196a868, .expected_minor_hash = 0xc420eb28, }, { .name = "half_md4_unsigned_abc", .hash_version = DX_HASH_HALF_MD4_UNSIGNED, .input = "abc", .len = 3, .use_seed = false, .expected_hash = 0xd196a868, .expected_minor_hash = 0xc420eb28, }, { .name = "tea_abc", .hash_version = DX_HASH_TEA, .input = "abc", .len = 3, .use_seed = false, .expected_hash = 0xb1435ec4, .expected_minor_hash = 0x3f7eaa0e, }, { .name = "tea_unsigned_abc", .hash_version = DX_HASH_TEA_UNSIGNED, .input = "abc", .len = 3, .use_seed = false, .expected_hash = 0xb1435ec4, .expected_minor_hash = 0x3f7eaa0e, }, { .name = "empty_half_md4", .hash_version = DX_HASH_HALF_MD4, .input = "", .len = 0, .use_seed = false, .expected_hash = 0xefcdab88, .expected_minor_hash = 0x98badcfe, }, { .name = "half_md4_31bytes", .hash_version = DX_HASH_HALF_MD4, .input = "1234567890123456789012345678901", .len = 31, .use_seed = false, .expected_hash = 0xc4db1f78, .expected_minor_hash = 0xea23921b, }, { .name = "half_md4_32bytes", .hash_version = DX_HASH_HALF_MD4, .input = "12345678901234567890123456789012", .len = 32, .use_seed = false, .expected_hash = 0xfa6cc63e, .expected_minor_hash = 0x2f77bd1c, }, { .name = "half_md4_33bytes", .hash_version = DX_HASH_HALF_MD4, .input = "123456789012345678901234567890123", .len = 33, .use_seed = false, .expected_hash = 0xdc0c2dec, .expected_minor_hash = 0x5ca23365, }, { .name = "half_md4_unsigned_31bytes", .hash_version = DX_HASH_HALF_MD4_UNSIGNED, .input = "1234567890123456789012345678901", .len = 31, .use_seed = false, .expected_hash = 0xc4db1f78, .expected_minor_hash = 0xea23921b, }, { .name = "half_md4_unsigned_32bytes", .hash_version = DX_HASH_HALF_MD4_UNSIGNED, .input = "12345678901234567890123456789012", .len = 32, .use_seed = false, .expected_hash = 0xfa6cc63e, .expected_minor_hash = 0x2f77bd1c, }, { .name = "half_md4_unsigned_33bytes", .hash_version = DX_HASH_HALF_MD4_UNSIGNED, .input = "123456789012345678901234567890123", .len = 33, .use_seed = false, .expected_hash = 0xdc0c2dec, .expected_minor_hash = 0x5ca23365, }, { .name = "tea_15bytes", .hash_version = DX_HASH_TEA, .input = "123456789abcdef", .len = 15, .use_seed = false, .expected_hash = 0xa562903a, .expected_minor_hash = 0x6174a00f, }, { .name = "tea_16bytes", .hash_version = DX_HASH_TEA, .input = "1234567890abcdef", .len = 16, .use_seed = false, .expected_hash = 0x8449f258, .expected_minor_hash = 0x49a16d46, }, { .name = "tea_17bytes", .hash_version = DX_HASH_TEA, .input = "123456789abcdefgh", .len = 17, .use_seed = false, .expected_hash = 0xf32ec10c, .expected_minor_hash = 0x58ceae61, }, { .name = "half_md4_seeded", .hash_version = DX_HASH_HALF_MD4, .input = "same-name", .len = 9, .seed = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 }, .use_seed = true, .expected_hash = 0x8aebf604, .expected_minor_hash = 0x66ce48fe, }, { .name = "half_md4_non_ascii_signed", .hash_version = DX_HASH_HALF_MD4, .input = "\x80\x81\x82\x83\x84", .len = 5, .use_seed = false, .expected_hash = 0x8bab0498, .expected_minor_hash = 0xc326632d, }, { .name = "half_md4_non_ascii_unsigned", .hash_version = DX_HASH_HALF_MD4_UNSIGNED, .input = "\x80\x81\x82\x83\x84", .len = 5, .use_seed = false, .expected_hash = 0xbc48596e, .expected_minor_hash = 0xde0fad41, }, { .name = "tea_non_ascii_signed", .hash_version = DX_HASH_TEA, .input = "\x80\x81\x82\x83\x84", .len = 5, .use_seed = false, .expected_hash = 0x21e3a154, .expected_minor_hash = 0x90112c3d, }, { .name = "tea_non_ascii_unsigned", .hash_version = DX_HASH_TEA_UNSIGNED, .input = "\x80\x81\x82\x83\x84", .len = 5, .use_seed = false, .expected_hash = 0x9b648616, .expected_minor_hash = 0x011dd507, },};
static void test_ext4fs_dirhash_vectors(struct kunit *test){ struct super_block *sb; struct inode *dir; int i;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sb); KUNIT_ASSERT_NOT_NULL(test, dir);
ext4_hash_init_fake_dir(dir, sb);
for (i = 0; i < ARRAY_SIZE(ext4_dirhash_test_cases); i++) { const struct ext4_dirhash_test_case *tc = &ext4_dirhash_test_cases[i]; struct dx_hash_info hinfo; int ret;
memset(&hinfo, 0, sizeof(hinfo)); hinfo.hash_version = tc->hash_version; hinfo.seed = tc->use_seed ? (u32 *)tc->seed : NULL;
ret = ext4fs_dirhash(dir, tc->input, tc->len, &hinfo);
KUNIT_ASSERT_EQ_MSG(test, ret, 0, "case=%s", tc->name); KUNIT_EXPECT_EQ_MSG(test, hinfo.hash, tc->expected_hash, "case=%s", tc->name); KUNIT_EXPECT_EQ_MSG(test, hinfo.minor_hash, tc->expected_minor_hash, "case=%s", tc->name); }}
static void test_ext4fs_dirhash_seed_changes_result(struct kunit *test){ struct super_block *sb; struct inode *dir; u32 seed[4] = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 }; struct dx_hash_info plain = { .hash_version = DX_HASH_HALF_MD4, }; struct dx_hash_info seeded = { .hash_version = DX_HASH_HALF_MD4, .seed = seed, }; int ret_plain, ret_seeded;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sb); KUNIT_ASSERT_NOT_NULL(test, dir);
ext4_hash_init_fake_dir(dir, sb);
ret_plain = ext4fs_dirhash(dir, "same-name", 9, &plain); ret_seeded = ext4fs_dirhash(dir, "same-name", 9, &seeded);
KUNIT_ASSERT_EQ(test, ret_plain, 0); KUNIT_ASSERT_EQ(test, ret_seeded, 0);
KUNIT_EXPECT_TRUE(test, plain.hash != seeded.hash || plain.minor_hash != seeded.minor_hash);}
static void test_ext4fs_dirhash_invalid_version_returns_einval(struct kunit *test){ struct super_block *sb; struct inode *dir; struct ext4_sb_info *sbi; struct dx_hash_info hinfo = { .hash = 0xdeadbeef, .minor_hash = 0xcafebabe, .hash_version = DX_HASH_LAST + 1, }; int ret;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL); sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sb); KUNIT_ASSERT_NOT_NULL(test, dir); KUNIT_ASSERT_NOT_NULL(test, sbi);
ext4_hash_init_fake_dir_with_sbi(dir, sb, sbi);
ret = ext4fs_dirhash(dir, "abc", 3, &hinfo);
KUNIT_EXPECT_EQ(test, ret, -EINVAL); KUNIT_EXPECT_EQ(test, hinfo.hash, 0); KUNIT_EXPECT_EQ(test, hinfo.minor_hash, 0);}
static void test_ext4fs_dirhash_siphash_without_key_returns_einval(struct kunit *test){ struct super_block *sb; struct ext4_inode_info *ei; struct inode *dir; struct ext4_sb_info *sbi; struct dx_hash_info hinfo = { .hash_version = DX_HASH_SIPHASH, }; int ret;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL); sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sb); KUNIT_ASSERT_NOT_NULL(test, ei); KUNIT_ASSERT_NOT_NULL(test, sbi);
ext4_hash_init_fake_ext4_dir(ei, sb, sbi); dir = &ei->vfs_inode;
ret = ext4fs_dirhash(dir, "name", strlen("name"), &hinfo);
KUNIT_EXPECT_EQ(test, ret, -EINVAL);}
static void test_ext4fs_dirhash_signed_unsigned_differ_on_nonascii(struct kunit *test){ struct super_block *sb; struct inode *dir; static const char input[] = "\x80\xff\x81\xfe\101bc"; struct dx_hash_info legacy_signed = { .hash_version = DX_HASH_LEGACY, }; struct dx_hash_info legacy_unsigned = { .hash_version = DX_HASH_LEGACY_UNSIGNED, }; struct dx_hash_info md4_signed = { .hash_version = DX_HASH_HALF_MD4, }; struct dx_hash_info md4_unsigned = { .hash_version = DX_HASH_HALF_MD4_UNSIGNED, }; struct dx_hash_info tea_signed = { .hash_version = DX_HASH_TEA, }; struct dx_hash_info tea_unsigned = { .hash_version = DX_HASH_TEA_UNSIGNED, }; int ret;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sb); KUNIT_ASSERT_NOT_NULL(test, dir);
ext4_hash_init_fake_dir(dir, sb);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_signed); KUNIT_ASSERT_EQ(test, ret, 0); ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_unsigned); KUNIT_ASSERT_EQ(test, ret, 0); KUNIT_EXPECT_NE(test, legacy_signed.hash, legacy_unsigned.hash);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_signed); KUNIT_ASSERT_EQ(test, ret, 0); ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_unsigned); KUNIT_ASSERT_EQ(test, ret, 0); KUNIT_EXPECT_TRUE(test, md4_signed.hash != md4_unsigned.hash || md4_signed.minor_hash != md4_unsigned.minor_hash);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_signed); KUNIT_ASSERT_EQ(test, ret, 0); ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_unsigned); KUNIT_ASSERT_EQ(test, ret, 0); KUNIT_EXPECT_TRUE(test, tea_signed.hash != tea_unsigned.hash || tea_signed.minor_hash != tea_unsigned.minor_hash);}
#if IS_ENABLED(CONFIG_UNICODE)KUNIT_DEFINE_ACTION_WRAPPER(utf8_unload_action, utf8_unload, struct unicode_map *);static void test_ext4fs_dirhash_casefolded_names_hash_consistently(struct kunit *test){ struct super_block *sb; struct ext4_inode_info *ei; struct ext4_sb_info *sbi; struct unicode_map *um; struct dx_hash_info h1 = { .hash_version = DX_HASH_HALF_MD4, }; struct dx_hash_info h2 = { .hash_version = DX_HASH_HALF_MD4, }; int ret, ret1, ret2;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL); sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sb); KUNIT_ASSERT_NOT_NULL(test, ei); KUNIT_ASSERT_NOT_NULL(test, sbi);
um = utf8_load(UTF8_LATEST); if (IS_ERR(um)) { kunit_skip(test, "utf8_load(UTF8_LATEST) failed: %pe", um); return; }
ret = kunit_add_action_or_reset(test, utf8_unload_action, um); KUNIT_ASSERT_EQ(test, ret, 0);
ext4_hash_init_fake_ext4_dir(ei, sb, sbi); sb->s_encoding = um; ei->vfs_inode.i_flags |= S_CASEFOLD;
KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode));
ret1 = ext4fs_dirhash(&ei->vfs_inode, "Alpha", 5, &h1); ret2 = ext4fs_dirhash(&ei->vfs_inode, "aLPHa", 5, &h2);
KUNIT_ASSERT_EQ(test, ret1, 0); KUNIT_ASSERT_EQ(test, ret2, 0); KUNIT_EXPECT_EQ(test, h1.hash, h2.hash); KUNIT_EXPECT_EQ(test, h1.minor_hash, h2.minor_hash);}
static void test_ext4fs_dirhash_casefold_fallback(struct kunit *test){ struct super_block *sb_cf, *sb_plain; struct ext4_inode_info *ei; struct ext4_sb_info *sbi; struct inode *plain_dir; struct unicode_map *um; static const char invalid_utf8[] = "\xc3\x28"; struct dx_hash_info folded_dir = { .hash_version = DX_HASH_HALF_MD4, }; struct dx_hash_info plain = { .hash_version = DX_HASH_HALF_MD4, }; int ret, ret_cf, ret_plain;
sb_cf = kunit_kzalloc(test, sizeof(*sb_cf), GFP_KERNEL); sb_plain = kunit_kzalloc(test, sizeof(*sb_plain), GFP_KERNEL); ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL); sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL); plain_dir = kunit_kzalloc(test, sizeof(*plain_dir), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sb_cf); KUNIT_ASSERT_NOT_NULL(test, sb_plain); KUNIT_ASSERT_NOT_NULL(test, ei); KUNIT_ASSERT_NOT_NULL(test, sbi); KUNIT_ASSERT_NOT_NULL(test, plain_dir);
um = utf8_load(UTF8_LATEST); if (IS_ERR(um)) { kunit_skip(test, "utf8_load(UTF8_LATEST) failed: %pe", um); return; }
ret = kunit_add_action_or_reset(test, utf8_unload_action, um); KUNIT_ASSERT_EQ(test, ret, 0);
ext4_hash_init_fake_ext4_dir(ei, sb_cf, sbi); sb_cf->s_encoding = um; ei->vfs_inode.i_flags |= S_CASEFOLD;
KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode));
ext4_hash_init_fake_dir(plain_dir, sb_plain);
ret_cf = ext4fs_dirhash(&ei->vfs_inode, invalid_utf8, sizeof(invalid_utf8) - 1, &folded_dir); ret_plain = ext4fs_dirhash(plain_dir, invalid_utf8, sizeof(invalid_utf8) - 1, &plain);
KUNIT_ASSERT_EQ(test, ret_cf, 0); KUNIT_ASSERT_EQ(test, ret_plain, 0); KUNIT_EXPECT_EQ(test, folded_dir.hash, plain.hash); KUNIT_EXPECT_EQ(test, folded_dir.minor_hash, plain.minor_hash);}#endif
static struct kunit_case ext4_hash_test_cases[] = { KUNIT_CASE(test_ext4fs_dirhash_vectors), KUNIT_CASE(test_ext4fs_dirhash_seed_changes_result), KUNIT_CASE(test_ext4fs_dirhash_invalid_version_returns_einval), KUNIT_CASE(test_ext4fs_dirhash_siphash_without_key_returns_einval), KUNIT_CASE(test_ext4fs_dirhash_signed_unsigned_differ_on_nonascii),#if IS_ENABLED(CONFIG_UNICODE) KUNIT_CASE(test_ext4fs_dirhash_casefolded_names_hash_consistently), KUNIT_CASE(test_ext4fs_dirhash_casefold_fallback),#endif {}};
static struct kunit_suite ext4_hash_test_suite = { .name = "ext4_hash", .test_cases = ext4_hash_test_cases,};
kunit_test_suites(&ext4_hash_test_suite);
MODULE_LICENSE("GPL");