Commit 538bcaa6 authored by Theodore Ts'o's avatar Theodore Ts'o

jbd2: fix race when writing superblock

The jbd2 superblock is lockless now, so there is probably a race
condition between writing it so disk and modifing contents of it, which
may lead to checksum error. The following race is the one case that we
have captured.

jbd2                                fsstress
jbd2_journal_commit_transaction
 jbd2_journal_update_sb_log_tail
  jbd2_write_superblock
   jbd2_superblock_csum_set         jbd2_journal_revoke
                                     jbd2_journal_set_features(revork)
                                     modify superblock
   submit_bh(checksum incorrect)

Fix this by locking the buffer head before modifing it.  We always
write the jbd2 superblock after we modify it, so this just means
calling the lock_buffer() a little earlier.

This checksum corruption problem can be reproduced by xfstests
generic/475.
Reported-by: default avatarzhangyi (F) <yi.zhang@huawei.com>
Suggested-by: default avatarJan Kara <jack@suse.cz>
Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
parent f96c3ac8
...@@ -1356,6 +1356,10 @@ static int journal_reset(journal_t *journal) ...@@ -1356,6 +1356,10 @@ static int journal_reset(journal_t *journal)
return jbd2_journal_start_thread(journal); return jbd2_journal_start_thread(journal);
} }
/*
* This function expects that the caller will have locked the journal
* buffer head, and will return with it unlocked
*/
static int jbd2_write_superblock(journal_t *journal, int write_flags) static int jbd2_write_superblock(journal_t *journal, int write_flags)
{ {
struct buffer_head *bh = journal->j_sb_buffer; struct buffer_head *bh = journal->j_sb_buffer;
...@@ -1365,7 +1369,6 @@ static int jbd2_write_superblock(journal_t *journal, int write_flags) ...@@ -1365,7 +1369,6 @@ static int jbd2_write_superblock(journal_t *journal, int write_flags)
trace_jbd2_write_superblock(journal, write_flags); trace_jbd2_write_superblock(journal, write_flags);
if (!(journal->j_flags & JBD2_BARRIER)) if (!(journal->j_flags & JBD2_BARRIER))
write_flags &= ~(REQ_FUA | REQ_PREFLUSH); write_flags &= ~(REQ_FUA | REQ_PREFLUSH);
lock_buffer(bh);
if (buffer_write_io_error(bh)) { if (buffer_write_io_error(bh)) {
/* /*
* Oh, dear. A previous attempt to write the journal * Oh, dear. A previous attempt to write the journal
...@@ -1424,6 +1427,7 @@ int jbd2_journal_update_sb_log_tail(journal_t *journal, tid_t tail_tid, ...@@ -1424,6 +1427,7 @@ int jbd2_journal_update_sb_log_tail(journal_t *journal, tid_t tail_tid,
jbd_debug(1, "JBD2: updating superblock (start %lu, seq %u)\n", jbd_debug(1, "JBD2: updating superblock (start %lu, seq %u)\n",
tail_block, tail_tid); tail_block, tail_tid);
lock_buffer(journal->j_sb_buffer);
sb->s_sequence = cpu_to_be32(tail_tid); sb->s_sequence = cpu_to_be32(tail_tid);
sb->s_start = cpu_to_be32(tail_block); sb->s_start = cpu_to_be32(tail_block);
...@@ -1454,18 +1458,17 @@ static void jbd2_mark_journal_empty(journal_t *journal, int write_op) ...@@ -1454,18 +1458,17 @@ static void jbd2_mark_journal_empty(journal_t *journal, int write_op)
journal_superblock_t *sb = journal->j_superblock; journal_superblock_t *sb = journal->j_superblock;
BUG_ON(!mutex_is_locked(&journal->j_checkpoint_mutex)); BUG_ON(!mutex_is_locked(&journal->j_checkpoint_mutex));
read_lock(&journal->j_state_lock); lock_buffer(journal->j_sb_buffer);
/* Is it already empty? */ if (sb->s_start == 0) { /* Is it already empty? */
if (sb->s_start == 0) { unlock_buffer(journal->j_sb_buffer);
read_unlock(&journal->j_state_lock);
return; return;
} }
jbd_debug(1, "JBD2: Marking journal as empty (seq %d)\n", jbd_debug(1, "JBD2: Marking journal as empty (seq %d)\n",
journal->j_tail_sequence); journal->j_tail_sequence);
sb->s_sequence = cpu_to_be32(journal->j_tail_sequence); sb->s_sequence = cpu_to_be32(journal->j_tail_sequence);
sb->s_start = cpu_to_be32(0); sb->s_start = cpu_to_be32(0);
read_unlock(&journal->j_state_lock);
jbd2_write_superblock(journal, write_op); jbd2_write_superblock(journal, write_op);
...@@ -1488,9 +1491,8 @@ void jbd2_journal_update_sb_errno(journal_t *journal) ...@@ -1488,9 +1491,8 @@ void jbd2_journal_update_sb_errno(journal_t *journal)
journal_superblock_t *sb = journal->j_superblock; journal_superblock_t *sb = journal->j_superblock;
int errcode; int errcode;
read_lock(&journal->j_state_lock); lock_buffer(journal->j_sb_buffer);
errcode = journal->j_errno; errcode = journal->j_errno;
read_unlock(&journal->j_state_lock);
if (errcode == -ESHUTDOWN) if (errcode == -ESHUTDOWN)
errcode = 0; errcode = 0;
jbd_debug(1, "JBD2: updating superblock error (errno %d)\n", errcode); jbd_debug(1, "JBD2: updating superblock error (errno %d)\n", errcode);
...@@ -1894,28 +1896,27 @@ int jbd2_journal_set_features (journal_t *journal, unsigned long compat, ...@@ -1894,28 +1896,27 @@ int jbd2_journal_set_features (journal_t *journal, unsigned long compat,
sb = journal->j_superblock; sb = journal->j_superblock;
/* Load the checksum driver if necessary */
if ((journal->j_chksum_driver == NULL) &&
INCOMPAT_FEATURE_ON(JBD2_FEATURE_INCOMPAT_CSUM_V3)) {
journal->j_chksum_driver = crypto_alloc_shash("crc32c", 0, 0);
if (IS_ERR(journal->j_chksum_driver)) {
printk(KERN_ERR "JBD2: Cannot load crc32c driver.\n");
journal->j_chksum_driver = NULL;
return 0;
}
/* Precompute checksum seed for all metadata */
journal->j_csum_seed = jbd2_chksum(journal, ~0, sb->s_uuid,
sizeof(sb->s_uuid));
}
lock_buffer(journal->j_sb_buffer);
/* If enabling v3 checksums, update superblock */ /* If enabling v3 checksums, update superblock */
if (INCOMPAT_FEATURE_ON(JBD2_FEATURE_INCOMPAT_CSUM_V3)) { if (INCOMPAT_FEATURE_ON(JBD2_FEATURE_INCOMPAT_CSUM_V3)) {
sb->s_checksum_type = JBD2_CRC32C_CHKSUM; sb->s_checksum_type = JBD2_CRC32C_CHKSUM;
sb->s_feature_compat &= sb->s_feature_compat &=
~cpu_to_be32(JBD2_FEATURE_COMPAT_CHECKSUM); ~cpu_to_be32(JBD2_FEATURE_COMPAT_CHECKSUM);
/* Load the checksum driver */
if (journal->j_chksum_driver == NULL) {
journal->j_chksum_driver = crypto_alloc_shash("crc32c",
0, 0);
if (IS_ERR(journal->j_chksum_driver)) {
printk(KERN_ERR "JBD2: Cannot load crc32c "
"driver.\n");
journal->j_chksum_driver = NULL;
return 0;
}
/* Precompute checksum seed for all metadata */
journal->j_csum_seed = jbd2_chksum(journal, ~0,
sb->s_uuid,
sizeof(sb->s_uuid));
}
} }
/* If enabling v1 checksums, downgrade superblock */ /* If enabling v1 checksums, downgrade superblock */
...@@ -1927,6 +1928,7 @@ int jbd2_journal_set_features (journal_t *journal, unsigned long compat, ...@@ -1927,6 +1928,7 @@ int jbd2_journal_set_features (journal_t *journal, unsigned long compat,
sb->s_feature_compat |= cpu_to_be32(compat); sb->s_feature_compat |= cpu_to_be32(compat);
sb->s_feature_ro_compat |= cpu_to_be32(ro); sb->s_feature_ro_compat |= cpu_to_be32(ro);
sb->s_feature_incompat |= cpu_to_be32(incompat); sb->s_feature_incompat |= cpu_to_be32(incompat);
unlock_buffer(journal->j_sb_buffer);
return 1; return 1;
#undef COMPAT_FEATURE_ON #undef COMPAT_FEATURE_ON
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment