| From 27ba5b67312a944576addc4df44ac3b709aabede Mon Sep 17 00:00:00 2001 |
| From: Jan Kara <jack@suse.cz> |
| Date: Mon, 24 Jun 2024 19:01:19 +0200 |
| Subject: jbd2: avoid infinite transaction commit loop |
| |
| From: Jan Kara <jack@suse.cz> |
| |
| commit 27ba5b67312a944576addc4df44ac3b709aabede upstream. |
| |
| 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> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| fs/jbd2/transaction.c | 21 ++++++++++++++------- |
| 1 file changed, 14 insertions(+), 7 deletions(-) |
| |
| --- a/fs/jbd2/transaction.c |
| +++ b/fs/jbd2/transaction.c |
| @@ -191,6 +191,13 @@ static void sub_reserved_credits(journal |
| 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 * |
| * 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; |
| } |