EXT4_IOC_SET_TUNE_SB_PARAM allows setting the casefold feature (along with s_encoding and s_encoding_flags) on a mounted file system, but it only updates the on-disk superblock. The in-memory state needed by casefolding is set up exclusively at mount time: - sb->s_encoding is loaded by ext4_encoding_init(), so it stays NULL; - the casefolding dentry operations are installed before the root dentry is created, and can't be retrofitted onto a live dcache; - nothing checks for CONFIG_UNICODE, so a kernel without it can set a feature that then prevents the file system from being mounted. Since ext4_has_feature_casefold() becomes true as soon as the primary superblock is updated, userspace can then set EXT4_CASEFOLD_FL on an empty directory, and the next lookup or create in it passes the NULL sb->s_encoding to utf8_casefold(), crashing the kernel. Casefolding has to be enabled while the file system is unmounted, so stop advertising and accepting the casefold feature and the EXT4_TUNE_FL_ENCODING{,_FLAGS} operations, and remove the code that handled them. Fixes: 04a91570ac67 ("ext4: implemet new ioctls to set and get superblock parameters") Signed-off-by: Theodore Ts'o --- fs/ext4/ioctl-test.c | 35 ++++++++++++++++++++------------- fs/ext4/ioctl.c | 47 +++++++------------------------------------- 2 files changed, 28 insertions(+), 54 deletions(-) diff --git a/fs/ext4/ioctl-test.c b/fs/ext4/ioctl-test.c index c3eee774bcfa..fab2b9f8ff35 100644 --- a/fs/ext4/ioctl-test.c +++ b/fs/ext4/ioctl-test.c @@ -522,8 +522,6 @@ static void test_sb_callbacks_and_setparams(struct kunit *test) EXT4_TUNE_FL_DEF_HASH_ALG | EXT4_TUNE_FL_RAID_STRIDE | EXT4_TUNE_FL_RAID_STRIPE_WIDTH | - EXT4_TUNE_FL_ENCODING | - EXT4_TUNE_FL_ENCODING_FLAGS | EXT4_TUNE_FL_MOUNT_OPTS | EXT4_TUNE_FL_EDIT_FEATURES | EXT4_TUNE_FL_FORCE_FSCK; @@ -539,8 +537,6 @@ static void test_sb_callbacks_and_setparams(struct kunit *test) params.def_hash_alg = DX_HASH_HALF_MD4; params.raid_stride = 16; params.raid_stripe_width = 128; - params.encoding = EXT4_ENC_UTF8_12_1; - params.encoding_flags = SB_ENC_STRICT_MODE_FL; strscpy(params.mount_opts, "nodelalloc,noatime", sizeof(params.mount_opts)); params.set_feature_compat_mask = EXT4_FEATURE_COMPAT_DIR_INDEX; @@ -563,9 +559,6 @@ static void test_sb_callbacks_and_setparams(struct kunit *test) KUNIT_EXPECT_EQ(test, es->s_def_hash_version, DX_HASH_TEA); KUNIT_EXPECT_EQ(test, le16_to_cpu(es->s_raid_stride), 16); KUNIT_EXPECT_EQ(test, le32_to_cpu(es->s_raid_stripe_width), 128U); - KUNIT_EXPECT_EQ(test, le16_to_cpu(es->s_encoding), EXT4_ENC_UTF8_12_1); - KUNIT_EXPECT_EQ(test, le16_to_cpu(es->s_encoding_flags), - SB_ENC_STRICT_MODE_FL); KUNIT_EXPECT_STREQ(test, (char *)es->s_mount_opts, "nodelalloc,noatime"); KUNIT_EXPECT_NE(test, @@ -583,8 +576,9 @@ static void test_sb_callbacks_and_setparams(struct kunit *test) /* * Verify ext4_validate_tune_sb_params rejects malformed mount_opts, invalid - * flags, out-of-range parameters, unsupported feature edits, and standalone - * encoding updates, while normalizing DIR_INDEX and CASEFOLD enablement. + * flags, out-of-range parameters, unsupported feature edits, encoding + * updates and online casefold enablement, while normalizing DIR_INDEX + * enablement. */ static void test_validate_tune_sb_params(struct kunit *test) { @@ -675,20 +669,33 @@ static void test_validate_tune_sb_params(struct kunit *test) KUNIT_EXPECT_FALSE(test, uuid_is_null(&uu)); KUNIT_EXPECT_EQ(test, sbi->s_def_hash_version, DX_HASH_HALF_MD4); - /* Encoding without CASEFOLD -> -EINVAL; with CASEFOLD -> UTF8_12_1 */ + /* Encoding changes and online casefold enablement -> -EOPNOTSUPP */ memset(¶ms, 0, sizeof(params)); params.set_flags = EXT4_TUNE_FL_ENCODING; params.encoding = EXT4_ENC_UTF8_12_1; KUNIT_EXPECT_EQ(test, ext4_validate_tune_sb_params_test(sb, ¶ms), - -EINVAL); + -EOPNOTSUPP); + + memset(¶ms, 0, sizeof(params)); + params.set_flags = EXT4_TUNE_FL_ENCODING_FLAGS; + KUNIT_EXPECT_EQ(test, ext4_validate_tune_sb_params_test(sb, ¶ms), + -EOPNOTSUPP); memset(¶ms, 0, sizeof(params)); params.set_flags = EXT4_TUNE_FL_EDIT_FEATURES; params.set_feature_incompat_mask = EXT4_FEATURE_INCOMPAT_CASEFOLD; KUNIT_EXPECT_EQ(test, ext4_validate_tune_sb_params_test(sb, ¶ms), - 0); - KUNIT_EXPECT_EQ(test, params.encoding, EXT4_ENC_UTF8_12_1); - KUNIT_EXPECT_EQ(test, params.encoding_flags, 0); + -EOPNOTSUPP); + + memset(¶ms, 0, sizeof(params)); + params.set_flags = EXT4_TUNE_FL_FEATURES; + params.feature_compat = le32_to_cpu(es->s_feature_compat); + params.feature_incompat = le32_to_cpu(es->s_feature_incompat) | + EXT4_FEATURE_INCOMPAT_CASEFOLD; + params.feature_ro_compat = le32_to_cpu(es->s_feature_ro_compat); + KUNIT_EXPECT_EQ(test, ext4_validate_tune_sb_params_test(sb, ¶ms), + -EOPNOTSUPP); + KUNIT_EXPECT_FALSE(test, ext4_has_feature_casefold(sb)); } /* diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index 37ad549ad693..8537d5766732 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -1269,9 +1269,13 @@ static int ext4_ioctl_setuuid(struct file *filp, EXT4_TUNE_FL_DEF_HASH_ALG | EXT4_TUNE_FL_RAID_STRIDE | \ EXT4_TUNE_FL_RAID_STRIPE_WIDTH | EXT4_TUNE_FL_MOUNT_OPTS | \ EXT4_TUNE_FL_FEATURES | EXT4_TUNE_FL_EDIT_FEATURES | \ - EXT4_TUNE_FL_FORCE_FSCK | EXT4_TUNE_FL_ENCODING | \ - EXT4_TUNE_FL_ENCODING_FLAGS) + EXT4_TUNE_FL_FORCE_FSCK) +/* + * Casefolding can't be enabled on a mounted file system: sb->s_encoding + * and the casefolding dentry operations are only set up at mount time, + * before any dentries exist. + */ #define EXT4_TUNE_SET_COMPAT_SUPP \ (EXT4_FEATURE_COMPAT_DIR_INDEX | \ EXT4_FEATURE_COMPAT_STABLE_INODES) @@ -1280,8 +1284,7 @@ static int ext4_ioctl_setuuid(struct file *filp, EXT4_FEATURE_INCOMPAT_EA_INODE | \ EXT4_FEATURE_INCOMPAT_ENCRYPT | \ EXT4_FEATURE_INCOMPAT_CSUM_SEED | \ - EXT4_FEATURE_INCOMPAT_LARGEDIR | \ - EXT4_FEATURE_INCOMPAT_CASEFOLD) + EXT4_FEATURE_INCOMPAT_LARGEDIR) #define EXT4_TUNE_SET_RO_COMPAT_SUPP \ (EXT4_FEATURE_RO_COMPAT_LARGE_FILE | \ EXT4_FEATURE_RO_COMPAT_DIR_NLINK | \ @@ -1293,9 +1296,6 @@ static int ext4_ioctl_setuuid(struct file *filp, #define EXT4_TUNE_CLEAR_INCOMPAT_SUPP (0) #define EXT4_TUNE_CLEAR_RO_COMPAT_SUPP (0) -#define SB_ENC_SUPP_MASK (SB_ENC_STRICT_MODE_FL | \ - SB_ENC_NO_COMPAT_FALLBACK_FL) - static int ext4_ioctl_get_tune_sb(struct ext4_sb_info *sbi, struct ext4_tune_sb_params __user *params) { @@ -1376,10 +1376,6 @@ static void ext4_sb_setparams(struct ext4_sb_info *sbi, if (params->set_flags & EXT4_TUNE_FL_RAID_STRIPE_WIDTH) es->s_raid_stripe_width = cpu_to_le32(params->raid_stripe_width); - if (params->set_flags & EXT4_TUNE_FL_ENCODING) - es->s_encoding = cpu_to_le16(params->encoding); - if (params->set_flags & EXT4_TUNE_FL_ENCODING_FLAGS) - es->s_encoding_flags = cpu_to_le16(params->encoding_flags); if (params->set_flags & EXT4_TUNE_FL_MOUNT_OPTS) strscpy_pad(es->s_mount_opts, params->mount_opts); if (params->set_flags & EXT4_TUNE_FL_EDIT_FEATURES) { @@ -1411,7 +1407,6 @@ static int ext4_validate_tune_sb_params(struct super_block *sb, { struct ext4_sb_info *sbi = EXT4_SB(sb); struct ext4_super_block *es = sbi->s_es; - int enabling_casefold = 0; if ((params->set_flags & EXT4_TUNE_FL_MOUNT_OPTS) && strnlen(params->mount_opts, sizeof(params->mount_opts)) == @@ -1482,20 +1477,6 @@ static int ext4_validate_tune_sb_params(struct super_block *sb, ~le32_to_cpu(es->s_feature_incompat); params->set_feature_ro_compat_mask &= ~le32_to_cpu(es->s_feature_ro_compat); - if (params->set_feature_incompat_mask & - EXT4_FEATURE_INCOMPAT_CASEFOLD) { - enabling_casefold = 1; - if (!(params->set_flags & EXT4_TUNE_FL_ENCODING)) { - params->encoding = EXT4_ENC_UTF8_12_1; - params->set_flags |= EXT4_TUNE_FL_ENCODING; - } - if (!(params->set_flags & - EXT4_TUNE_FL_ENCODING_FLAGS)) { - params->encoding_flags = 0; - params->set_flags |= - EXT4_TUNE_FL_ENCODING_FLAGS; - } - } if (params->set_feature_compat_mask & EXT4_FEATURE_COMPAT_DIR_INDEX) { uuid_t uu; @@ -1519,20 +1500,6 @@ static int ext4_validate_tune_sb_params(struct super_block *sb, } } } - if (params->set_flags & EXT4_TUNE_FL_ENCODING) { - if (!enabling_casefold) - return -EINVAL; - if (params->encoding == 0) - params->encoding = EXT4_ENC_UTF8_12_1; - else if (params->encoding != EXT4_ENC_UTF8_12_1) - return -EINVAL; - } - if (params->set_flags & EXT4_TUNE_FL_ENCODING_FLAGS) { - if (!enabling_casefold) - return -EINVAL; - if (params->encoding_flags & ~SB_ENC_SUPP_MASK) - return -EINVAL; - } return 0; } -- 2.56.0.542.g3ef89b7020-goog