Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

jbd2: gracefully abort on transaction state corruptions

Auditing the jbd2 codebase reveals several legacy J_ASSERT calls
that enforce internal state machine invariants (e.g., verifying
jh->b_transaction or jh->b_next_transaction pointers).

When these invariants are broken, the journal is in a corrupted
state. However, triggering a fatal panic brings down the entire
system for a localized filesystem error.

This patch targets a specific class of these asserts: those
residing inside functions that natively return integer error codes,
booleans, or error pointers. It replaces the hard J_ASSERTs with
WARN_ON_ONCE to capture the offending stack trace, safely drops
any held locks, gracefully aborts the journal, and returns -EINVAL.

This prevents a catastrophic kernel panic while ensuring the
corrupted journal state is safely contained and upstream callers
(like ext4 or ocfs2) can gracefully handle the aborted handle.

Functions modified in fs/jbd2/transaction.c:
- jbd2__journal_start()
- do_get_write_access()
- jbd2_journal_dirty_metadata()
- jbd2_journal_forget()
- jbd2_journal_try_to_free_buffers()
- jbd2_journal_file_inode()

Signed-off-by: Milos Nikic <nikic.milos@gmail.com>
Reviewed-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Andreas Dilger <adilger@dilger.ca>
Link: https://patch.msgid.link/20260304172016.23525-3-nikic.milos@gmail.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>

authored by

Milos Nikic and committed by
Theodore Ts'o
f7fc28b0 64924362

+86 -28
+86 -28
fs/jbd2/transaction.c
··· 474 474 return ERR_PTR(-EROFS); 475 475 476 476 if (handle) { 477 - J_ASSERT(handle->h_transaction->t_journal == journal); 477 + if (WARN_ON_ONCE(handle->h_transaction->t_journal != journal)) 478 + return ERR_PTR(-EINVAL); 478 479 handle->h_ref++; 479 480 return handle; 480 481 } ··· 1037 1036 */ 1038 1037 if (!jh->b_transaction) { 1039 1038 JBUFFER_TRACE(jh, "no transaction"); 1040 - J_ASSERT_JH(jh, !jh->b_next_transaction); 1039 + if (WARN_ON_ONCE(jh->b_next_transaction)) { 1040 + spin_unlock(&jh->b_state_lock); 1041 + unlock_buffer(bh); 1042 + error = -EINVAL; 1043 + jbd2_journal_abort(journal, error); 1044 + goto out; 1045 + } 1041 1046 JBUFFER_TRACE(jh, "file as BJ_Reserved"); 1042 1047 /* 1043 1048 * Make sure all stores to jh (b_modified, b_frozen_data) are ··· 1076 1069 */ 1077 1070 if (jh->b_frozen_data) { 1078 1071 JBUFFER_TRACE(jh, "has frozen data"); 1079 - J_ASSERT_JH(jh, jh->b_next_transaction == NULL); 1072 + if (WARN_ON_ONCE(jh->b_next_transaction)) { 1073 + spin_unlock(&jh->b_state_lock); 1074 + error = -EINVAL; 1075 + jbd2_journal_abort(journal, error); 1076 + goto out; 1077 + } 1080 1078 goto attach_next; 1081 1079 } 1082 1080 1083 1081 JBUFFER_TRACE(jh, "owned by older transaction"); 1084 - J_ASSERT_JH(jh, jh->b_next_transaction == NULL); 1085 - J_ASSERT_JH(jh, jh->b_transaction == journal->j_committing_transaction); 1082 + if (WARN_ON_ONCE(jh->b_next_transaction || 1083 + jh->b_transaction != 1084 + journal->j_committing_transaction)) { 1085 + pr_err("JBD2: %s: assertion failure: b_next_transaction=%p b_transaction=%p j_committing_transaction=%p\n", 1086 + journal->j_devname, jh->b_next_transaction, 1087 + jh->b_transaction, journal->j_committing_transaction); 1088 + spin_unlock(&jh->b_state_lock); 1089 + error = -EINVAL; 1090 + jbd2_journal_abort(journal, error); 1091 + goto out; 1092 + } 1086 1093 1087 1094 /* 1088 1095 * There is one case we have to be very careful about. If the ··· 1517 1496 int jbd2_journal_dirty_metadata(handle_t *handle, struct buffer_head *bh) 1518 1497 { 1519 1498 transaction_t *transaction = handle->h_transaction; 1520 - journal_t *journal; 1499 + journal_t *journal = transaction->t_journal; 1521 1500 struct journal_head *jh; 1522 1501 int ret = 0; 1523 1502 ··· 1541 1520 if (data_race(jh->b_transaction != transaction && 1542 1521 jh->b_next_transaction != transaction)) { 1543 1522 spin_lock(&jh->b_state_lock); 1544 - J_ASSERT_JH(jh, jh->b_transaction == transaction || 1545 - jh->b_next_transaction == transaction); 1523 + if (WARN_ON_ONCE(jh->b_transaction != transaction && 1524 + jh->b_next_transaction != transaction)) { 1525 + pr_err("JBD2: %s: assertion failure: b_transaction=%p transaction=%p b_next_transaction=%p\n", 1526 + journal->j_devname, jh->b_transaction, 1527 + transaction, jh->b_next_transaction); 1528 + ret = -EINVAL; 1529 + goto out_unlock_bh; 1530 + } 1546 1531 spin_unlock(&jh->b_state_lock); 1547 1532 } 1548 1533 if (data_race(jh->b_modified == 1)) { ··· 1556 1529 if (data_race(jh->b_transaction == transaction && 1557 1530 jh->b_jlist != BJ_Metadata)) { 1558 1531 spin_lock(&jh->b_state_lock); 1559 - if (jh->b_transaction == transaction && 1560 - jh->b_jlist != BJ_Metadata) 1561 - pr_err("JBD2: assertion failure: h_type=%u " 1562 - "h_line_no=%u block_no=%llu jlist=%u\n", 1532 + if (WARN_ON_ONCE(jh->b_transaction == transaction && 1533 + jh->b_jlist != BJ_Metadata)) { 1534 + pr_err("JBD2: assertion failure: h_type=%u h_line_no=%u block_no=%llu jlist=%u\n", 1563 1535 handle->h_type, handle->h_line_no, 1564 1536 (unsigned long long) bh->b_blocknr, 1565 1537 jh->b_jlist); 1566 - J_ASSERT_JH(jh, jh->b_transaction != transaction || 1567 - jh->b_jlist == BJ_Metadata); 1538 + ret = -EINVAL; 1539 + goto out_unlock_bh; 1540 + } 1568 1541 spin_unlock(&jh->b_state_lock); 1569 1542 } 1570 1543 goto out; ··· 1583 1556 ret = -EROFS; 1584 1557 goto out_unlock_bh; 1585 1558 } 1586 - 1587 - journal = transaction->t_journal; 1588 1559 1589 1560 if (jh->b_modified == 0) { 1590 1561 /* ··· 1661 1636 } 1662 1637 1663 1638 /* That test should have eliminated the following case: */ 1664 - J_ASSERT_JH(jh, jh->b_frozen_data == NULL); 1639 + if (WARN_ON_ONCE(jh->b_frozen_data)) { 1640 + ret = -EINVAL; 1641 + goto out_unlock_bh; 1642 + } 1665 1643 1666 1644 JBUFFER_TRACE(jh, "file as BJ_Metadata"); 1667 1645 spin_lock(&journal->j_list_lock); ··· 1703 1675 int err = 0; 1704 1676 int was_modified = 0; 1705 1677 int wait_for_writeback = 0; 1678 + int abort_journal = 0; 1706 1679 1707 1680 if (is_handle_aborted(handle)) 1708 1681 return -EROFS; ··· 1737 1708 jh->b_modified = 0; 1738 1709 1739 1710 if (jh->b_transaction == transaction) { 1740 - J_ASSERT_JH(jh, !jh->b_frozen_data); 1711 + if (WARN_ON_ONCE(jh->b_frozen_data)) { 1712 + err = -EINVAL; 1713 + abort_journal = 1; 1714 + goto drop; 1715 + } 1741 1716 1742 1717 /* If we are forgetting a buffer which is already part 1743 1718 * of this transaction, then we can just drop it from ··· 1780 1747 } 1781 1748 spin_unlock(&journal->j_list_lock); 1782 1749 } else if (jh->b_transaction) { 1783 - J_ASSERT_JH(jh, (jh->b_transaction == 1784 - journal->j_committing_transaction)); 1750 + if (WARN_ON_ONCE(jh->b_transaction != journal->j_committing_transaction)) { 1751 + err = -EINVAL; 1752 + abort_journal = 1; 1753 + goto drop; 1754 + } 1785 1755 /* However, if the buffer is still owned by a prior 1786 1756 * (committing) transaction, we can't drop it yet... */ 1787 1757 JBUFFER_TRACE(jh, "belongs to older transaction"); ··· 1802 1766 jh->b_next_transaction = transaction; 1803 1767 spin_unlock(&journal->j_list_lock); 1804 1768 } else { 1805 - J_ASSERT(jh->b_next_transaction == transaction); 1769 + if (WARN_ON_ONCE(jh->b_next_transaction != transaction)) { 1770 + err = -EINVAL; 1771 + abort_journal = 1; 1772 + goto drop; 1773 + } 1806 1774 1807 1775 /* 1808 1776 * only drop a reference if this transaction modified ··· 1852 1812 drop: 1853 1813 __brelse(bh); 1854 1814 spin_unlock(&jh->b_state_lock); 1815 + if (abort_journal) 1816 + jbd2_journal_abort(journal, err); 1855 1817 if (wait_for_writeback) 1856 1818 wait_on_buffer(bh); 1857 1819 jbd2_journal_put_journal_head(jh); ··· 2178 2136 struct buffer_head *bh; 2179 2137 bool ret = false; 2180 2138 2181 - J_ASSERT(folio_test_locked(folio)); 2139 + if (WARN_ON_ONCE(!folio_test_locked(folio))) 2140 + return false; 2182 2141 2183 2142 head = folio_buffers(folio); 2184 2143 bh = head; ··· 2694 2651 { 2695 2652 transaction_t *transaction = handle->h_transaction; 2696 2653 journal_t *journal; 2654 + int err = 0; 2655 + int abort_transaction = 0; 2697 2656 2698 2657 if (is_handle_aborted(handle)) 2699 2658 return -EROFS; ··· 2730 2685 /* On some different transaction's list - should be 2731 2686 * the committing one */ 2732 2687 if (jinode->i_transaction) { 2733 - J_ASSERT(jinode->i_next_transaction == NULL); 2734 - J_ASSERT(jinode->i_transaction == 2735 - journal->j_committing_transaction); 2688 + if (WARN_ON_ONCE(jinode->i_next_transaction || 2689 + jinode->i_transaction != 2690 + journal->j_committing_transaction)) { 2691 + pr_err("JBD2: %s: assertion failure: i_next_transaction=%p i_transaction=%p j_committing_transaction=%p\n", 2692 + journal->j_devname, jinode->i_next_transaction, 2693 + jinode->i_transaction, 2694 + journal->j_committing_transaction); 2695 + err = -EINVAL; 2696 + abort_transaction = 1; 2697 + goto done; 2698 + } 2736 2699 jinode->i_next_transaction = transaction; 2737 2700 goto done; 2738 2701 } 2739 2702 /* Not on any transaction list... */ 2740 - J_ASSERT(!jinode->i_next_transaction); 2703 + if (WARN_ON_ONCE(jinode->i_next_transaction)) { 2704 + err = -EINVAL; 2705 + abort_transaction = 1; 2706 + goto done; 2707 + } 2741 2708 jinode->i_transaction = transaction; 2742 2709 list_add(&jinode->i_list, &transaction->t_inode_list); 2743 2710 done: 2744 2711 spin_unlock(&journal->j_list_lock); 2745 - 2746 - return 0; 2712 + if (abort_transaction) 2713 + jbd2_journal_abort(journal, err); 2714 + return err; 2747 2715 } 2748 2716 2749 2717 int jbd2_journal_inode_ranged_write(handle_t *handle,