zircon/system/ulib/fs/journal/test/journal_test.cc
// Tests that payload blocks which could be parsed as journal metadata are escaped.
//
// If the following metadata is written:
//  Operation:
//                   [1, 2, 3]
//  Journal:
//      [ _, _, _, H, 1, 2, 3, C, _, _ ]
//
// and continued operations occur, such that the header is overwritten, and the
// info block is updated:
//
//           New Operation
//           |
//      [ _, H, x, C, 1, 2, 3, C, _, _ ]
//
// Normally, the data would be invalid by the checksum, and ignored:
//
//      [ _, H, x, C, _, _, _, _, _, _ ]
//
// Resulting in replaying one operaton.
//
// However, if "[1, 2, 3]" actually sets block "1" to a valid header block, and
// block "3" to a valid commit block, the journal would look like the following:
//
//      [ _, H, x, C, H, 2, C, _, _, _ ]
//
// This would result in TWO operations being replayed, where the second could
// contain arbitrary data.
//
// To avoid this case, the journal converts payload blocks with "header entry magic"
// to a form that drops them on replay.
Tests that payload blocks which could be parsed as journal metadata are escaped.

and continued operations occur, such that the header is overwritten, and the
info block is updated.



Normally, the data would be invalid by the checksum, and ignored.



Resulting in replaying one operaton.





This would result in TWO operations being replayed, where the second could
contain arbitrary data.

To avoid this case, the journal converts payload blocks with "header entry magic"
to a form that drops them on replay.
Tests that payload blocks which could be parsed as journal metadata are escaped . and continued operations occur such that the header is overwritten and the info block is updated . Normally the data would be invalid by the checksum and ignored . Resulting in replaying one operaton . This would result in TWO operations being replayed where the second could contain arbitrary data . To avoid this case the journal converts payload blocks with " header entry magic " to a form that drops them on replay .
TEST_F(JournalTest, PayloadBlocksWithJournalMagicAreEscaped) {
  storage::VmoBuffer metadata = registry()->InitializeBuffer(1);
  *reinterpret_cast<uint64_t*>(metadata.Data(0)) = kJournalEntryMagic;
  const storage::UnbufferedOperation operation = {
      zx::unowned_vmo(metadata.vmo().get()),
      {
          storage::OperationType::kWrite,
          .vmo_offset = 0,
          .dev_offset = 20,
          .length = 1,
      },
  };
  constexpr uint64_t kJournalStartBlock = 55;
  JournalRequestVerifier verifier(registry()->info(), registry()->journal(),
                                  registry()->writeback(), kJournalStartBlock);
  MockTransactionHandler::TransactionCallback callbacks[] = {
      [&](const std::vector<storage::BufferedOperation>& requests) {
        verifier.VerifyJournalWrite(operation, requests);
        std::array<char, kBlockSize> buffer = {};
        uint64_t offset = (verifier.JournalOffset() + kJournalEntryHeaderBlocks) * kBlockSize;
        uint64_t length = kBlockSize;
        EXPECT_OK(registry()->journal().read(buffer.data(), offset, length));
        EXPECT_BYTES_NE(metadata.Data(0), buffer.data(), kBlockSize,
                        "metadata should have been escaped (modified)");
        uint64_t sequence_number = 1;
        registry()->VerifyReplay({operation}, sequence_number);
        return ZX_OK;
      },
      [&](const std::vector<storage::BufferedOperation>& requests) {
        verifier.VerifyMetadataWrite(operation, requests);
        std::array<char, kBlockSize> buffer = {};
        uint64_t offset = (verifier.JournalOffset() + kJournalEntryHeaderBlocks) * kBlockSize;
        uint64_t length = kBlockSize;
        EXPECT_OK(registry()->journal().read(buffer.data(), offset, length));
        EXPECT_BYTES_EQ(metadata.Data(0), buffer.data(), kBlockSize,
                        "Metadata should only be escaped in the journal");
        verifier.ExtendJournalOffset(operation.op.length + kEntryMetadataBlocks);
        return ZX_OK;
      },
      [&](const std::vector<storage::BufferedOperation>& requests) {
        uint64_t sequence_number = 1;
        verifier.VerifyInfoBlockWrite(sequence_number, requests);
        registry()->VerifyReplay({}, sequence_number);
        return ZX_OK;
      }};
  MockTransactionHandler handler(registry(), callbacks, std::size(callbacks));
  {
    Journal journal(&handler, take_info(), take_journal_buffer(), take_data_buffer(),
                    kJournalStartBlock, Journal::Options());
    journal.schedule_task(journal.WriteMetadata({operation}));
  }
}
TEST_F ( JournalTest , PayloadBlocksWithJournalMagicAreEscaped ) { storage :: VmoBuffer metadata = registry ( ) -> InitializeBuffer ( 1 ) ; * reinterpret_cast < uint64_t * > ( metadata . Data ( 0 ) ) = kJournalEntryMagic ; const storage :: UnbufferedOperation operation = { zx :: unowned_vmo ( metadata . vmo ( ) . get ( ) ) , { storage :: OperationType :: kWrite , . vmo_offset = 0 , . dev_offset = 20 , . length = 1 , } , } ; constexpr uint64_t kJournalStartBlock = 55 ; JournalRequestVerifier verifier ( registry ( ) -> info ( ) , registry ( ) -> journal ( ) , registry ( ) -> writeback ( ) , kJournalStartBlock ) ; MockTransactionHandler :: TransactionCallback callbacks [ ] = { [ & ] ( const std :: vector < storage :: BufferedOperation > & requests ) { verifier . VerifyJournalWrite ( operation , requests ) ; std :: array < char , kBlockSize > buffer = { } ; uint64_t offset = ( verifier . JournalOffset ( ) + kJournalEntryHeaderBlocks ) * kBlockSize ; uint64_t length = kBlockSize ; EXPECT_OK ( registry ( ) -> journal ( ) . read ( buffer . data ( ) , offset , length ) ) ; EXPECT_BYTES_NE ( metadata . Data ( 0 ) , buffer . data ( ) , kBlockSize , " " ) ; uint64_t sequence_number = 1 ; registry ( ) -> VerifyReplay ( { operation } , sequence_number ) ; return ZX_OK ; } , [ & ] ( const std :: vector < storage :: BufferedOperation > & requests ) { verifier . VerifyMetadataWrite ( operation , requests ) ; std :: array < char , kBlockSize > buffer = { } ; uint64_t offset = ( verifier . JournalOffset ( ) + kJournalEntryHeaderBlocks ) * kBlockSize ; uint64_t length = kBlockSize ; EXPECT_OK ( registry ( ) -> journal ( ) . read ( buffer . data ( ) , offset , length ) ) ; EXPECT_BYTES_EQ ( metadata . Data ( 0 ) , buffer . data ( ) , kBlockSize , " " ) ; verifier . ExtendJournalOffset ( operation . op . length + kEntryMetadataBlocks ) ; return ZX_OK ; } , [ & ] ( const std :: vector < storage :: BufferedOperation > & requests ) { uint64_t sequence_number = 1 ; verifier . VerifyInfoBlockWrite ( sequence_number , requests ) ; registry ( ) -> VerifyReplay ( { } , sequence_number ) ; return ZX_OK ; } } ; MockTransactionHandler handler ( registry ( ) , callbacks , std :: size ( callbacks ) ) ; { Journal journal ( & handler , take_info ( ) , take_journal_buffer ( ) , take_data_buffer ( ) , kJournalStartBlock , Journal :: Options ( ) ) ; journal . schedule_task ( journal . WriteMetadata ( { operation } ) ) ; } }
Tests that payload blocks which could be parsed as journal metadata are escaped.
Tests that payload blocks which could be parsed as journal metadata are escaped .
// Create an operation which will become escaped when written by the journal. // Verify the operation is first issued to the on-disk journal. // Verify that the payload is escaped in the journal. // Verify that if we were to reboot now the operation would be replayed. // Verify that the payload is NOT escaped when writing to the final location.