btrfs: don't print stack trace when transaction is aborted due to ENOMEM
authorDavid Sterba <dsterba@suse.com>
Thu, 3 Nov 2022 13:39:01 +0000 (14:39 +0100)
committerDavid Sterba <dsterba@suse.com>
Mon, 7 Nov 2022 13:34:57 +0000 (14:34 +0100)
Add ENOMEM among the error codes that don't print stack trace on
transaction abort. We've got several reports from syzbot that detects
stacks as errors but caused by limiting memory. As this is an artificial
condition we don't need to know where exactly the error happens, the
abort and error cleanup will continue like e.g. for EIO.

As the transaction aborts code needs to be inline in a lot of code, the
implementation cases about minimal bloat. The error codes are in a
separate function and the WARN uses the condition directly. This
increases the code size by 571 bytes on release build.

Alternatives considered: add -ENOMEM among the errors, this increases
size by 2340 bytes, various attempts to combine the WARN and helper
calls, increase by 700 or more bytes.

Example syzbot reports (error -12):

- https://syzkaller.appspot.com/bug?extid=5244d35be7f589cf093e
- https://syzkaller.appspot.com/bug?extid=9c37714c07194d816417

Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/ctree.c
fs/btrfs/ctree.h

index b39b339..a9543f0 100644 (file)
@@ -114,6 +114,22 @@ noinline void btrfs_release_path(struct btrfs_path *p)
 }
 
 /*
+ * We want the transaction abort to print stack trace only for errors where the
+ * cause could be a bug, eg. due to ENOSPC, and not for common errors that are
+ * caused by external factors.
+ */
+bool __cold abort_should_print_stack(int errno)
+{
+       switch (errno) {
+       case -EIO:
+       case -EROFS:
+       case -ENOMEM:
+               return false;
+       }
+       return true;
+}
+
+/*
  * safely gets a reference on the root node of a tree.  A lock
  * is not taken, so a concurrent writer may put a different node
  * at the root of the tree.  See btrfs_lock_root_node for the
index f677b49..9e6d48f 100644 (file)
@@ -3796,9 +3796,11 @@ void __btrfs_abort_transaction(struct btrfs_trans_handle *trans,
                               const char *function,
                               unsigned int line, int errno, bool first_hit);
 
+bool __cold abort_should_print_stack(int errno);
+
 /*
  * Call btrfs_abort_transaction as early as possible when an error condition is
- * detected, that way the exact line number is reported.
+ * detected, that way the exact stack trace is reported for some errors.
  */
 #define btrfs_abort_transaction(trans, errno)          \
 do {                                                           \
@@ -3807,10 +3809,11 @@ do {                                                            \
        if (!test_and_set_bit(BTRFS_FS_STATE_TRANS_ABORTED,     \
                        &((trans)->fs_info->fs_state))) {       \
                first = true;                                   \
-               if ((errno) != -EIO && (errno) != -EROFS) {             \
-                       WARN(1, KERN_DEBUG                              \
+               if (WARN(abort_should_print_stack(errno),       \
+                       KERN_DEBUG                              \
                        "BTRFS: Transaction aborted (error %d)\n",      \
-                       (errno));                                       \
+                       (errno))) {                                     \
+                       /* Stack trace printed. */                      \
                } else {                                                \
                        btrfs_debug((trans)->fs_info,                   \
                                    "Transaction aborted (error %d)", \