Skip to content

Commit

Permalink
jbd2: fix use-after-free of transaction_t race
Browse files Browse the repository at this point in the history
commit cc16eec upstream.

jbd2_journal_wait_updates() is called with j_state_lock held. But if
there is a commit in progress, then this transaction might get committed
and freed via jbd2_journal_commit_transaction() ->
jbd2_journal_free_transaction(), when we release j_state_lock.
So check for journal->j_running_transaction everytime we release and
acquire j_state_lock to avoid use-after-free issue.

Link: https://lore.kernel.org/r/948c2fed518ae739db6a8f7f83f1d58b504f87d0.1644497105.git.ritesh.list@gmail.com
Fixes: 4f98186 ("jbd2: refactor wait logic for transaction updates into a common function")
Cc: stable@kernel.org
Reported-and-tested-by: syzbot+afa2ca5171d93e44b348@syzkaller.appspotmail.com
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Ritesh Harjani <riteshh@linux.ibm.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Ritesh Harjani authored and Greg Kroah-Hartman committed Mar 28, 2022
1 parent abc9ad3 commit bff94c5
Showing 1 changed file with 25 additions and 16 deletions.
41 changes: 25 additions & 16 deletions fs/jbd2/transaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -842,27 +842,38 @@ EXPORT_SYMBOL(jbd2_journal_restart);
*/
void jbd2_journal_wait_updates(journal_t *journal)
{
transaction_t *commit_transaction = journal->j_running_transaction;
DEFINE_WAIT(wait);

if (!commit_transaction)
return;
while (1) {
/*
* Note that the running transaction can get freed under us if
* this transaction is getting committed in
* jbd2_journal_commit_transaction() ->
* jbd2_journal_free_transaction(). This can only happen when we
* release j_state_lock -> schedule() -> acquire j_state_lock.
* Hence we should everytime retrieve new j_running_transaction
* value (after j_state_lock release acquire cycle), else it may
* lead to use-after-free of old freed transaction.
*/
transaction_t *transaction = journal->j_running_transaction;

spin_lock(&commit_transaction->t_handle_lock);
while (atomic_read(&commit_transaction->t_updates)) {
DEFINE_WAIT(wait);
if (!transaction)
break;

spin_lock(&transaction->t_handle_lock);
prepare_to_wait(&journal->j_wait_updates, &wait,
TASK_UNINTERRUPTIBLE);
if (atomic_read(&commit_transaction->t_updates)) {
spin_unlock(&commit_transaction->t_handle_lock);
write_unlock(&journal->j_state_lock);
schedule();
write_lock(&journal->j_state_lock);
spin_lock(&commit_transaction->t_handle_lock);
TASK_UNINTERRUPTIBLE);
if (!atomic_read(&transaction->t_updates)) {
spin_unlock(&transaction->t_handle_lock);
finish_wait(&journal->j_wait_updates, &wait);
break;
}
spin_unlock(&transaction->t_handle_lock);
write_unlock(&journal->j_state_lock);
schedule();
finish_wait(&journal->j_wait_updates, &wait);
write_lock(&journal->j_state_lock);
}
spin_unlock(&commit_transaction->t_handle_lock);
}

/**
Expand All @@ -877,8 +888,6 @@ void jbd2_journal_wait_updates(journal_t *journal)
*/
void jbd2_journal_lock_updates(journal_t *journal)
{
DEFINE_WAIT(wait);

jbd2_might_wait_for_commit(journal);

write_lock(&journal->j_state_lock);
Expand Down

0 comments on commit bff94c5

Please sign in to comment.