]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
jbd2: avoid infinite transaction commit loop
authorJan Kara <jack@suse.cz>
Mon, 24 Jun 2024 17:01:19 +0000 (19:01 +0200)
committerTheodore Ts'o <tytso@mit.edu>
Tue, 9 Jul 2024 03:59:37 +0000 (23:59 -0400)
Commit 9f356e5a4f12 ("jbd2: Account descriptor blocks into
t_outstanding_credits") started to account descriptor blocks into
transactions outstanding credits. However it didn't appropriately
decrease the maximum amount of credits available to userspace. Thus if
the filesystem requests a transaction smaller than
j_max_transaction_buffers but large enough that when descriptor blocks
are added the size exceeds j_max_transaction_buffers, we confuse
add_transaction_credits() into thinking previous handles have grown the
transaction too much and enter infinite journal commit loop in
start_this_handle() -> add_transaction_credits() trying to create
transaction with enough credits available.

Fix the problem by properly accounting for transaction space reserved
for descriptor blocks when verifying requested transaction handle size.

CC: stable@vger.kernel.org
Fixes: 9f356e5a4f12 ("jbd2: Account descriptor blocks into t_outstanding_credits")
Reported-by: Alexander Coffin <alex.coffin@maticrobots.com>
Link: https://lore.kernel.org/all/CA+hUFcuGs04JHZ_WzA1zGN57+ehL2qmHOt5a7RMpo+rv6Vyxtw@mail.gmail.com
Signed-off-by: Jan Kara <jack@suse.cz>
Reviewed-by: Zhang Yi <yi.zhang@huawei.com>
Link: https://patch.msgid.link/20240624170127.3253-3-jack@suse.cz
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/jbd2/transaction.c

index a095f1a3114bb6c35d7b129423d19a078fd7ab72..66513c18ca294b5418aeda2a7d598d5bb524d044 100644 (file)
@@ -191,6 +191,13 @@ static void sub_reserved_credits(journal_t *journal, int blocks)
        wake_up(&journal->j_wait_reserved);
 }
 
+/* Maximum number of blocks for user transaction payload */
+static int jbd2_max_user_trans_buffers(journal_t *journal)
+{
+       return journal->j_max_transaction_buffers -
+                               journal->j_transaction_overhead_buffers;
+}
+
 /*
  * Wait until we can add credits for handle to the running transaction.  Called
  * with j_state_lock held for reading. Returns 0 if handle joined the running
@@ -240,12 +247,12 @@ __must_hold(&journal->j_state_lock)
                 * big to fit this handle? Wait until reserved credits are freed.
                 */
                if (atomic_read(&journal->j_reserved_credits) + total >
-                   journal->j_max_transaction_buffers) {
+                   jbd2_max_user_trans_buffers(journal)) {
                        read_unlock(&journal->j_state_lock);
                        jbd2_might_wait_for_commit(journal);
                        wait_event(journal->j_wait_reserved,
                                   atomic_read(&journal->j_reserved_credits) + total <=
-                                  journal->j_max_transaction_buffers);
+                                  jbd2_max_user_trans_buffers(journal));
                        __acquire(&journal->j_state_lock); /* fake out sparse */
                        return 1;
                }
@@ -285,14 +292,14 @@ __must_hold(&journal->j_state_lock)
 
        needed = atomic_add_return(rsv_blocks, &journal->j_reserved_credits);
        /* We allow at most half of a transaction to be reserved */
-       if (needed > journal->j_max_transaction_buffers / 2) {
+       if (needed > jbd2_max_user_trans_buffers(journal) / 2) {
                sub_reserved_credits(journal, rsv_blocks);
                atomic_sub(total, &t->t_outstanding_credits);
                read_unlock(&journal->j_state_lock);
                jbd2_might_wait_for_commit(journal);
                wait_event(journal->j_wait_reserved,
                         atomic_read(&journal->j_reserved_credits) + rsv_blocks
-                        <= journal->j_max_transaction_buffers / 2);
+                        <= jbd2_max_user_trans_buffers(journal) / 2);
                __acquire(&journal->j_state_lock); /* fake out sparse */
                return 1;
        }
@@ -322,12 +329,12 @@ static int start_this_handle(journal_t *journal, handle_t *handle,
         * size and limit the number of total credits to not exceed maximum
         * transaction size per operation.
         */
-       if ((rsv_blocks > journal->j_max_transaction_buffers / 2) ||
-           (rsv_blocks + blocks > journal->j_max_transaction_buffers)) {
+       if (rsv_blocks > jbd2_max_user_trans_buffers(journal) / 2 ||
+           rsv_blocks + blocks > jbd2_max_user_trans_buffers(journal)) {
                printk(KERN_ERR "JBD2: %s wants too many credits "
                       "credits:%d rsv_credits:%d max:%d\n",
                       current->comm, blocks, rsv_blocks,
-                      journal->j_max_transaction_buffers);
+                      jbd2_max_user_trans_buffers(journal));
                WARN_ON(1);
                return -ENOSPC;
        }