blob: edd8a9a8827938fb7b915f9bc82b9d6dd06d7751 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <lib/fdio/spawn.h>
#include <lib/zx/process.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <zircon/device/block.h>
#include <algorithm>
#include <climits>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <iterator>
#include <memory>
#include <string>
#include <fbl/unique_fd.h>
#include "src/storage/blobfs/common.h"
#include "src/storage/blobfs/format.h"
#include "src/storage/blobfs/test/blob_utils.h"
#include "src/storage/blobfs/test/integration/blobfs_fixtures.h"
#include "src/storage/extractor/c/extractor.h"
#include "src/storage/extractor/cpp/extractor.h"
#include "src/storage/fs_test/blobfs_test.h"
#include "src/storage/fs_test/fs_test.h"
#include "src/storage/fs_test/fs_test_fixture.h"
namespace extractor {
namespace {
using BlobfsExtractionTest = fs_test::FilesystemTest;
constexpr uint64_t kExtractedImageBlockCount = 2;
constexpr uint64_t kExtractedImageStartOffset = 0;
constexpr uint64_t SuperblockOffset() {
return kExtractedImageStartOffset + kExtractedImageBlockCount * blobfs::kBlobfsBlockSize;
}
constexpr uint64_t BlockBitmapOffset(const blobfs::Superblock& info) {
uint64_t block_map_offset =
SuperblockOffset() + blobfs::kBlobfsSuperblockBlocks * blobfs::kBlobfsBlockSize;
if (info.flags & blobfs::kBlobFlagFVM) {
block_map_offset += (blobfs::kBlobfsSuperblockBlocks * blobfs::kBlobfsBlockSize);
}
return block_map_offset;
}
constexpr uint64_t NodemapOffset(const blobfs::Superblock& info) {
return BlockBitmapOffset(info) + blobfs::BlockMapBlocks(info) * blobfs::kBlobfsBlockSize;
}
constexpr uint64_t JournalOffset(const blobfs::Superblock& info) {
return NodemapOffset(info) + blobfs::NodeMapBlocks(info) * blobfs::kBlobfsBlockSize;
}
constexpr uint64_t DatablockOffset(const blobfs::Superblock& info) {
return JournalOffset(info) + blobfs::JournalBlocks(info) * blobfs::kBlobfsBlockSize;
}
void CreateInputAndOutputStream(fs_test::TestFilesystem& fs, fbl::unique_fd& input,
fbl::unique_fd& output, std::unique_ptr<blobfs::BlobInfo>* blob) {
std::unique_ptr<blobfs::BlobInfo> blob_info =
blobfs::GenerateBlob(blobfs::RandomFill, fs.mount_path(), 1 << 17);
fbl::unique_fd fd;
ASSERT_NO_FATAL_FAILURE(MakeBlob(*blob_info, &fd));
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(fs.Unmount().status_value(), ZX_OK);
input.reset(open(fs.DevicePath().value().c_str(), O_RDWR));
ASSERT_TRUE(input);
char out_path[] = "/tmp/blobfs-extraction.XXXXXX";
output.reset(mkostemp(out_path, O_RDWR | O_CREAT | O_EXCL));
ASSERT_TRUE(output);
if (blob != nullptr) {
*blob = std::move(blob_info);
}
}
void Extract(const fbl::unique_fd& input_fd, const fbl::unique_fd& output_fd,
bool corruptSuperblock) {
ExtractorOptions options = ExtractorOptions{.force_dump_pii = false,
.add_checksum = false,
.alignment = blobfs::kBlobfsBlockSize,
.compress = false};
auto extractor_status = Extractor::Create(input_fd.duplicate(), options, output_fd.duplicate());
ASSERT_EQ(extractor_status.status_value(), ZX_OK);
auto extractor = std::move(extractor_status.value());
auto status = BlobfsExtract(input_fd.duplicate(), *extractor);
if (!corruptSuperblock) {
ASSERT_TRUE(status.is_ok());
}
ASSERT_TRUE(extractor->Write().is_ok());
}
void VerifyInputSuperblock(blobfs::Superblock* info, const fbl::unique_fd& input_fd) {
ASSERT_EQ(ssize_t{blobfs::kBlobfsBlockSize},
pread(input_fd.get(), info, blobfs::kBlobfsBlockSize, blobfs::kSuperblockOffset));
ASSERT_EQ(info->magic0, blobfs::kBlobfsMagic0);
ASSERT_EQ(info->magic1, blobfs::kBlobfsMagic1);
}
void VerifyOutputSuperblock(blobfs::Superblock* info, const fbl::unique_fd& output_fd) {
ssize_t superblock_offset = SuperblockOffset();
char read_buffer[blobfs::kBlobfsBlockSize];
ASSERT_EQ(pread(output_fd.get(), read_buffer, sizeof(read_buffer), superblock_offset),
static_cast<ssize_t>(sizeof(read_buffer)));
ASSERT_EQ(memcmp(info, read_buffer, sizeof(read_buffer)), 0);
}
TEST_P(BlobfsExtractionTest, TestSuperblock) {
fbl::unique_fd input_fd;
fbl::unique_fd output_fd;
CreateInputAndOutputStream(fs(), input_fd, output_fd, nullptr);
Extract(input_fd, output_fd, false);
blobfs::Superblock info;
VerifyInputSuperblock(&info, input_fd);
struct stat stats;
ASSERT_EQ(fstat(output_fd.get(), &stats), 0);
VerifyOutputSuperblock(&info, output_fd);
}
TEST_P(BlobfsExtractionTest, TestCorruptedSuperblock) {
fbl::unique_fd input_fd;
fbl::unique_fd output_fd;
CreateInputAndOutputStream(fs(), input_fd, output_fd, nullptr);
std::unique_ptr<char[]> original_superblock(new char[blobfs::kBlobfsBlockSize]);
ASSERT_EQ(pread(input_fd.get(), original_superblock.get(), blobfs::kBlobfsBlockSize, 0),
(ssize_t)blobfs::kBlobfsBlockSize);
char corrupt_block[blobfs::kBlobfsBlockSize] = {'C'};
ssize_t r =
pwrite(input_fd.get(), corrupt_block, sizeof(corrupt_block), blobfs::kSuperblockOffset);
ASSERT_EQ(r, (ssize_t)sizeof(corrupt_block));
Extract(input_fd, output_fd, true);
std::unique_ptr<char[]> superblock(new char[blobfs::kBlobfsBlockSize]);
ASSERT_EQ(pread(input_fd.get(), superblock.get(), blobfs::kBlobfsBlockSize, 0),
(ssize_t)blobfs::kBlobfsBlockSize);
std::unique_ptr<char[]> read_buffer_sb(new char[blobfs::kBlobfsBlockSize]);
ASSERT_EQ(pread(output_fd.get(), read_buffer_sb.get(), blobfs::kBlobfsBlockSize,
kExtractedImageBlockCount * blobfs::kBlobfsBlockSize),
(ssize_t)blobfs::kBlobfsBlockSize);
ASSERT_EQ(memcmp(superblock.get(), corrupt_block, blobfs::kBlobfsBlockSize), 0);
ASSERT_EQ(memcmp(read_buffer_sb.get(), corrupt_block, blobfs::kBlobfsBlockSize), 0);
// Restore original superblock to pass Fsck test
ssize_t r1 = pwrite(input_fd.get(), original_superblock.get(), sizeof(corrupt_block),
blobfs::kSuperblockOffset);
ASSERT_GE(r1, 0) << "errno: " << strerror(errno) << std::endl;
}
TEST_P(BlobfsExtractionTest, TestNodeMap) {
fbl::unique_fd input_fd;
fbl::unique_fd output_fd;
CreateInputAndOutputStream(fs(), input_fd, output_fd, nullptr);
Extract(input_fd, output_fd, false);
blobfs::Superblock info;
VerifyInputSuperblock(&info, input_fd);
ASSERT_EQ(info.alloc_inode_count, 1ul);
VerifyOutputSuperblock(&info, output_fd);
std::unique_ptr<blobfs::Inode[]> inode_table;
inode_table =
std::make_unique<blobfs::Inode[]>(NodeMapBlocks(info) * blobfs::kBlobfsInodesPerBlock);
auto size = static_cast<ssize_t>(blobfs::NodeMapBlocks(info) * blobfs::kBlobfsBlockSize);
ASSERT_EQ(pread(input_fd.get(), inode_table.get(), size,
blobfs::kBlobfsBlockSize * blobfs::NodeMapStartBlock(info)),
size);
char read_buffer_nodemap[size];
ASSERT_EQ(pread(output_fd.get(), read_buffer_nodemap, size, NodemapOffset(info)), size);
ASSERT_EQ(memcmp(inode_table.get(), read_buffer_nodemap, size), 0);
}
TEST_P(BlobfsExtractionTest, TestBlockMap) {
fbl::unique_fd input_fd;
fbl::unique_fd output_fd;
CreateInputAndOutputStream(fs(), input_fd, output_fd, nullptr);
Extract(input_fd, output_fd, false);
blobfs::Superblock info;
VerifyInputSuperblock(&info, input_fd);
ASSERT_EQ(info.alloc_inode_count, 1ul);
VerifyOutputSuperblock(&info, output_fd);
auto size = static_cast<ssize_t>(blobfs::BlockMapBlocks(info) * blobfs::kBlobfsBlockSize);
char block_bitmap[size];
ASSERT_EQ(pread(input_fd.get(), block_bitmap, size,
blobfs::kBlobfsBlockSize * blobfs::BlockMapStartBlock(info)),
size);
char read_buffer_blockmap[size];
ASSERT_EQ(pread(output_fd.get(), read_buffer_blockmap, size, BlockBitmapOffset(info)), size);
ASSERT_EQ(memcmp(block_bitmap, read_buffer_blockmap, size), 0);
}
TEST_P(BlobfsExtractionTest, TestJournal) {
fbl::unique_fd input_fd;
fbl::unique_fd output_fd;
CreateInputAndOutputStream(fs(), input_fd, output_fd, nullptr);
Extract(input_fd, output_fd, false);
blobfs::Superblock info;
VerifyInputSuperblock(&info, input_fd);
ASSERT_EQ(info.alloc_inode_count, 1ul);
VerifyOutputSuperblock(&info, output_fd);
auto size = static_cast<ssize_t>(blobfs::JournalBlocks(info) * blobfs::kBlobfsBlockSize);
std::unique_ptr<char[]> journal(new char[size]);
ASSERT_EQ(pread(input_fd.get(), journal.get(), size,
blobfs::kBlobfsBlockSize * blobfs::JournalStartBlock(info)),
size);
std::unique_ptr<char[]> read_buffer_blockmap(new char[size]);
ASSERT_EQ(pread(output_fd.get(), read_buffer_blockmap.get(), size, JournalOffset(info)), size);
ASSERT_EQ(memcmp(journal.get(), read_buffer_blockmap.get(), size), 0);
}
TEST_P(BlobfsExtractionTest, TestCorruptBlob) {
fbl::unique_fd input_fd;
fbl::unique_fd output_fd;
std::unique_ptr<blobfs::BlobInfo> blob_info;
CreateInputAndOutputStream(fs(), input_fd, output_fd, &blob_info);
blobfs::Superblock info;
VerifyInputSuperblock(&info, input_fd);
ASSERT_EQ(int(info.alloc_inode_count), 1);
std::unique_ptr<blobfs::Inode[]> inode_table;
inode_table =
std::make_unique<blobfs::Inode[]>(NodeMapBlocks(info) * blobfs::kBlobfsInodesPerBlock);
auto nodemap_size = static_cast<ssize_t>(blobfs::NodeMapBlocks(info) * blobfs::kBlobfsBlockSize);
ASSERT_EQ(nodemap_size, pread(input_fd.get(), inode_table.get(), nodemap_size,
blobfs::kBlobfsBlockSize * blobfs::NodeMapStartBlock(info)));
uint64_t input_datablock_offset = 0;
bool found_allocated_inode = false;
uint64_t size_of_data;
for (unsigned n = 0; n < info.inode_count; n++) {
blobfs::Inode ino = inode_table[n];
blobfs::NodePrelude header = ino.header;
if (header.IsAllocated() && header.IsInode()) {
blobfs::Extent extent = ino.extents[0];
input_datablock_offset = extent.Start();
size_of_data = extent.Length() * blobfs::kBlobfsBlockSize;
found_allocated_inode = true;
break;
}
}
ASSERT_EQ(found_allocated_inode, true);
char corrupt_block[blobfs::kBlobfsBlockSize] = {'C'};
// Assuming here that merkle tree is at the beginning and only takes up one block woo
auto offset = static_cast<off_t>((blobfs::DataStartBlock(info) + input_datablock_offset + 1)) *
static_cast<off_t>(blobfs::kBlobfsBlockSize);
ssize_t r = pwrite(input_fd.get(), corrupt_block, sizeof(corrupt_block), offset);
ASSERT_EQ(r, (ssize_t)sizeof(corrupt_block));
Extract(input_fd, output_fd, false);
VerifyOutputSuperblock(&info, output_fd);
struct stat stats;
ASSERT_EQ(fstat(output_fd.get(), &stats), 0);
char read_buffer_datablocks[size_of_data];
ASSERT_EQ(pread(output_fd.get(), read_buffer_datablocks, size_of_data, DatablockOffset(info)),
ssize_t(size_of_data));
// Assuming that the merkle tree takes up the first block of read_buffer_datablocks
ASSERT_NE(memcmp(blob_info->data.get(), read_buffer_datablocks + blobfs::kBlobfsBlockSize,
size_of_data - blobfs::kBlobfsBlockSize),
0);
ASSERT_EQ(memcmp(blob_info->data.get() + blobfs::kBlobfsBlockSize,
read_buffer_datablocks + (2 * blobfs::kBlobfsBlockSize),
size_of_data - (2 * blobfs::kBlobfsBlockSize)),
0);
offset = static_cast<off_t>((blobfs::DataStartBlock(info) + input_datablock_offset + 1)) *
static_cast<off_t>(blobfs::kBlobfsBlockSize);
ssize_t r1 = pwrite(input_fd.get(), blob_info->data.get(), sizeof(corrupt_block), offset);
ASSERT_GE(r1, 0) << "errno: " << strerror(errno) << std::endl;
}
// This test depends on the "padded" blobfs format. It will need updating to test compact.
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, BlobfsExtractionTest,
testing::Values(blobfs::BlobfsWithPaddedLayoutTestParam()),
testing::PrintToStringParamName());
} // namespace
} // namespace extractor