Vulnerability: 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.
Vulnerability ID: CVE-2022-28796
CWE: CWE-416
Severity score: 10
Project: https://github.com/torvalds/linux
Published: 2022-04-08
File: 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>
Commit author: Ritesh Harjani

Vulnerable version (before fix):
 EXPORT_SYMBOL(jbd2_journal_restart);  */ void jbd2_journal_wait_updates(journal_t *journal) { 	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);  		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); 		} 		finish_wait(&journal->j_wait_updates, &wait); 	} 	spin_unlock(&commit_transaction->t_handle_lock); }  /** 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);

Fixed version (after fix):
 EXPORT_SYMBOL(jbd2_journal_restart);  */ void jbd2_journal_wait_updates(journal_t *journal) { 	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); 		prepare_to_wait(&journal->j_wait_updates, &wait, 				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); 	} }  /** void jbd2_journal_wait_updates(journal_t *journal)  */ void jbd2_journal_lock_updates(journal_t *journal) { 	jbd2_might_wait_for_commit(journal);  	write_lock(&journal->j_state_lock);

Patch:
patch: @@ -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);
path: fs/jbd2/transaction.c