ext4: disable fast-commit of encrypted dir operations
authorEric Biggers <ebiggers@kernel.org>
Thu, 5 Jan 2023 07:13:55 +0000 (23:13 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 Jan 2023 10:59:04 +0000 (11:59 +0100)
From: Eric Biggers <ebiggers@google.com>

commit 0fbcb5251fc81b58969b272c4fb7374a7b922e3e upstream.

fast-commit of create, link, and unlink operations in encrypted
directories is completely broken because the unencrypted filenames are
being written to the fast-commit journal instead of the encrypted
filenames.  These operations can't be replayed, as encryption keys
aren't present at journal replay time.  It is also an information leak.

Until if/when we can get this working properly, make encrypted directory
operations ineligible for fast-commit.

Note that fast-commit operations on encrypted regular files continue to
be allowed, as they seem to work.

Fixes: aa75f4d3daae ("ext4: main fast-commit commit path")
Cc: <stable@vger.kernel.org> # v5.10+
Signed-off-by: Eric Biggers <ebiggers@google.com>
Link: https://lore.kernel.org/r/20221106224841.279231-2-ebiggers@kernel.org
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/ext4/fast_commit.c
fs/ext4/fast_commit.h
include/trace/events/ext4.h

index be59f87..33ce0e9 100644 (file)
@@ -399,25 +399,34 @@ static int __track_dentry_update(struct inode *inode, void *arg, bool update)
        struct __track_dentry_update_args *dentry_update =
                (struct __track_dentry_update_args *)arg;
        struct dentry *dentry = dentry_update->dentry;
-       struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
+       struct inode *dir = dentry->d_parent->d_inode;
+       struct super_block *sb = inode->i_sb;
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
 
        mutex_unlock(&ei->i_fc_lock);
+
+       if (IS_ENCRYPTED(dir)) {
+               ext4_fc_mark_ineligible(sb, EXT4_FC_REASON_ENCRYPTED_FILENAME,
+                                       NULL);
+               mutex_lock(&ei->i_fc_lock);
+               return -EOPNOTSUPP;
+       }
+
        node = kmem_cache_alloc(ext4_fc_dentry_cachep, GFP_NOFS);
        if (!node) {
-               ext4_fc_mark_ineligible(inode->i_sb, EXT4_FC_REASON_NOMEM, NULL);
+               ext4_fc_mark_ineligible(sb, EXT4_FC_REASON_NOMEM, NULL);
                mutex_lock(&ei->i_fc_lock);
                return -ENOMEM;
        }
 
        node->fcd_op = dentry_update->op;
-       node->fcd_parent = dentry->d_parent->d_inode->i_ino;
+       node->fcd_parent = dir->i_ino;
        node->fcd_ino = inode->i_ino;
        if (dentry->d_name.len > DNAME_INLINE_LEN) {
                node->fcd_name.name = kmalloc(dentry->d_name.len, GFP_NOFS);
                if (!node->fcd_name.name) {
                        kmem_cache_free(ext4_fc_dentry_cachep, node);
-                       ext4_fc_mark_ineligible(inode->i_sb,
-                               EXT4_FC_REASON_NOMEM, NULL);
+                       ext4_fc_mark_ineligible(sb, EXT4_FC_REASON_NOMEM, NULL);
                        mutex_lock(&ei->i_fc_lock);
                        return -ENOMEM;
                }
@@ -2179,17 +2188,17 @@ void ext4_fc_init(struct super_block *sb, journal_t *journal)
        journal->j_fc_cleanup_callback = ext4_fc_cleanup;
 }
 
-static const char *fc_ineligible_reasons[] = {
-       "Extended attributes changed",
-       "Cross rename",
-       "Journal flag changed",
-       "Insufficient memory",
-       "Swap boot",
-       "Resize",
-       "Dir renamed",
-       "Falloc range op",
-       "Data journalling",
-       "FC Commit Failed"
+static const char * const fc_ineligible_reasons[] = {
+       [EXT4_FC_REASON_XATTR] = "Extended attributes changed",
+       [EXT4_FC_REASON_CROSS_RENAME] = "Cross rename",
+       [EXT4_FC_REASON_JOURNAL_FLAG_CHANGE] = "Journal flag changed",
+       [EXT4_FC_REASON_NOMEM] = "Insufficient memory",
+       [EXT4_FC_REASON_SWAP_BOOT] = "Swap boot",
+       [EXT4_FC_REASON_RESIZE] = "Resize",
+       [EXT4_FC_REASON_RENAME_DIR] = "Dir renamed",
+       [EXT4_FC_REASON_FALLOC_RANGE] = "Falloc range op",
+       [EXT4_FC_REASON_INODE_JOURNAL_DATA] = "Data journalling",
+       [EXT4_FC_REASON_ENCRYPTED_FILENAME] = "Encrypted filename",
 };
 
 int ext4_fc_info_show(struct seq_file *seq, void *v)
index e580702..edbeb56 100644 (file)
@@ -96,6 +96,7 @@ enum {
        EXT4_FC_REASON_RENAME_DIR,
        EXT4_FC_REASON_FALLOC_RANGE,
        EXT4_FC_REASON_INODE_JOURNAL_DATA,
+       EXT4_FC_REASON_ENCRYPTED_FILENAME,
        EXT4_FC_REASON_MAX
 };
 
index 61a64d1..c649c7f 100644 (file)
@@ -104,6 +104,7 @@ TRACE_DEFINE_ENUM(EXT4_FC_REASON_RESIZE);
 TRACE_DEFINE_ENUM(EXT4_FC_REASON_RENAME_DIR);
 TRACE_DEFINE_ENUM(EXT4_FC_REASON_FALLOC_RANGE);
 TRACE_DEFINE_ENUM(EXT4_FC_REASON_INODE_JOURNAL_DATA);
+TRACE_DEFINE_ENUM(EXT4_FC_REASON_ENCRYPTED_FILENAME);
 TRACE_DEFINE_ENUM(EXT4_FC_REASON_MAX);
 
 #define show_fc_reason(reason)                                         \
@@ -116,7 +117,8 @@ TRACE_DEFINE_ENUM(EXT4_FC_REASON_MAX);
                { EXT4_FC_REASON_RESIZE,        "RESIZE"},              \
                { EXT4_FC_REASON_RENAME_DIR,    "RENAME_DIR"},          \
                { EXT4_FC_REASON_FALLOC_RANGE,  "FALLOC_RANGE"},        \
-               { EXT4_FC_REASON_INODE_JOURNAL_DATA,    "INODE_JOURNAL_DATA"})
+               { EXT4_FC_REASON_INODE_JOURNAL_DATA,    "INODE_JOURNAL_DATA"}, \
+               { EXT4_FC_REASON_ENCRYPTED_FILENAME,    "ENCRYPTED_FILENAME"})
 
 TRACE_EVENT(ext4_other_inode_update_time,
        TP_PROTO(struct inode *inode, ino_t orig_ino),
@@ -2764,7 +2766,7 @@ TRACE_EVENT(ext4_fc_stats,
        ),
 
        TP_printk("dev %d,%d fc ineligible reasons:\n"
-                 "%s:%u, %s:%u, %s:%u, %s:%u, %s:%u, %s:%u, %s:%u, %s:%u, %s:%u "
+                 "%s:%u, %s:%u, %s:%u, %s:%u, %s:%u, %s:%u, %s:%u, %s:%u, %s:%u, %s:%u"
                  "num_commits:%lu, ineligible: %lu, numblks: %lu",
                  MAJOR(__entry->dev), MINOR(__entry->dev),
                  FC_REASON_NAME_STAT(EXT4_FC_REASON_XATTR),
@@ -2776,6 +2778,7 @@ TRACE_EVENT(ext4_fc_stats,
                  FC_REASON_NAME_STAT(EXT4_FC_REASON_RENAME_DIR),
                  FC_REASON_NAME_STAT(EXT4_FC_REASON_FALLOC_RANGE),
                  FC_REASON_NAME_STAT(EXT4_FC_REASON_INODE_JOURNAL_DATA),
+                 FC_REASON_NAME_STAT(EXT4_FC_REASON_ENCRYPTED_FILENAME),
                  __entry->fc_commits, __entry->fc_ineligible_commits,
                  __entry->fc_numblks)
 );