CVE-2022-28796 (C, published 2022-04-08T05:15Z)
Description: value: jbd2_journal_wait_updates in fs/jbd2/transaction.c in the Linux kernel before 5.17.1 has a use-after-free caused by a transaction_t race condition.
Severity: MEDIUM (CVSS v3 base score: 7)
Weakness: CWE-362 \u2014 Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
The product contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently.
Repository: https://github.com/torvalds/linux
Files changed:
fs/jbd2/transaction.c
Commit message: 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: 4f98186848707f53 ("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>


Vulnerable code:
	transaction_t *commit_transaction = journal->j_running_transaction;
	if (!commit_transaction)
		return;
	spin_lock(&commit_transaction->t_handle_lock);
	while (atomic_read(&commit_transaction->t_updates)) {
		DEFINE_WAIT(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);
	spin_unlock(&commit_transaction->t_handle_lock);
	DEFINE_WAIT(wait);


Fixed code:
	DEFINE_WAIT(wait);
	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;
		if (!transaction)
			break;
		spin_lock(&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();
		write_lock(&journal->j_state_lock);