forked from Minki/linux
jbd2: fix use-after-free of transaction_t race
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: 4f98186848
("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>
This commit is contained in:
parent
e3952fcce1
commit
cc16eecae6
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user