diff options
author | Etienne Pierre-doray <etiennep@chromium.org> | 2021-10-28 21:16:04 +0000 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2021-10-28 14:31:23 -0700 |
commit | b90a947429fdce96b1d684b9a7af9683cb4a13c1 (patch) | |
tree | 333e034b74f5ec4cfb1be1600c4d1a061afbb3bc | |
parent | aff408603b3db5b7974c522db2ad8c5ce2a0f3c1 (diff) | |
download | zucchini-b90a947429fdce96b1d684b9a7af9683cb4a13c1.tar.gz |
[Zucchini]: Add patch version.
This is a breaking change to zucchini patch format:
Zucchini 1.0, see changelog.
Add major/minor patch-wide version, and element version.
Also add VerifyPatch() API and command line option to verify
patch compatibility.
Design: go/zucchini-versions
Bug: 1231882
Change-Id: I19f1fbe2ee866c23f0814ffe6a912fb72812edbc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3140224
Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org>
Reviewed-by: Samuel Huang <huangs@chromium.org>
Reviewed-by: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/main@{#936096}
NOKEYCHECK=True
GitOrigin-RevId: 559d77a9741428a48add017d389d104e431e6de7
-rw-r--r-- | README.md | 18 | ||||
-rw-r--r-- | disassembler_dex.h | 1 | ||||
-rw-r--r-- | disassembler_elf.h | 3 | ||||
-rw-r--r-- | disassembler_no_op.h | 2 | ||||
-rw-r--r-- | disassembler_win32.h | 3 | ||||
-rw-r--r-- | disassembler_ztf.h | 2 | ||||
-rw-r--r-- | element_detection.cc | 35 | ||||
-rw-r--r-- | element_detection.h | 3 | ||||
-rw-r--r-- | fuzzers/testdata/patch_fuzzer/empty.zuc | bin | 76 -> 82 bytes | |||
-rw-r--r-- | main_utils.cc | 4 | ||||
-rw-r--r-- | patch_read_write_unittest.cc | 80 | ||||
-rw-r--r-- | patch_reader.cc | 12 | ||||
-rw-r--r-- | patch_utils.h | 22 | ||||
-rw-r--r-- | patch_writer.cc | 6 | ||||
-rw-r--r-- | zucchini_commands.cc | 5 | ||||
-rw-r--r-- | zucchini_commands.h | 3 | ||||
-rw-r--r-- | zucchini_integration.cc | 27 | ||||
-rw-r--r-- | zucchini_integration.h | 10 |
18 files changed, 222 insertions, 14 deletions
@@ -204,12 +204,14 @@ Position of elements in new file is ascending. Name | Format | Description --- | --- | --- magic | uint32 = kMagic | Magic value. +major_version | uint16 | Major version number indicating breaking changes. +minor_version | uint16 | Minor version number indicating possibly breaking changes. old_size | uint32 | Size of old file in bytes. old_crc | uint32 | CRC32 of old file. new_size | uint32 | Size of new file in bytes. new_crc | uint32 | CRC32 of new file. -**kMagic** == `'Z' | ('u' << 8) | ('c' << 16)` +**kMagic** == `'Z' | ('u' << 8) | ('c' << 16) | ('c' << 24)` **PatchElement** Contains all the information required to produce a single element in new file. @@ -235,6 +237,7 @@ old_length | uint32 | Length of the element in old file. new_offset | uint32 | Starting offset of the element in new file. new_length | uint32 | Length of the element in new file. exe_type | uint32 | Executable type for this unit, see `enum ExecutableType`. +version | uint16 | Version specific to the executable type for this unit. **EquivalenceList** Encodes a list of equivalences, where dst offsets (in new image) are ascending. @@ -278,3 +281,16 @@ Name | Format | Description --- | --- | --- size |uint32 | Size of content in bytes. content |T[] | List of integers. + +# Format Changelog +All breaking changes to zucchini patch format will be documented in this +section. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [Unreleased] + +## [1.0] - 2021-10-27 +### Added +Major/Minor version is encoded in PatchHeader +Disassembler version associated with an element version is encoded in PatchElementHeader. diff --git a/disassembler_dex.h b/disassembler_dex.h index 8e739d0..d9d93b2 100644 --- a/disassembler_dex.h +++ b/disassembler_dex.h @@ -24,6 +24,7 @@ namespace zucchini { class DisassemblerDex : public Disassembler { public: + static constexpr uint16_t kVersion = 1; // Pools follow canonical order. enum ReferencePool : uint8_t { kStringId, diff --git a/disassembler_elf.h b/disassembler_elf.h index b29e89f..353c444 100644 --- a/disassembler_elf.h +++ b/disassembler_elf.h @@ -63,6 +63,7 @@ struct AArch64ReferenceType { }; struct Elf32Traits { + static constexpr uint16_t kVersion = 1; static constexpr Bitness kBitness = kBit32; static constexpr elf::FileClass kIdentificationClass = elf::ELFCLASS32; using Elf_Shdr = elf::Elf32_Shdr; @@ -94,6 +95,7 @@ struct ElfAArch32Traits : public Elf32Traits { }; struct Elf64Traits { + static constexpr uint16_t kVersion = 1; static constexpr Bitness kBitness = kBit64; static constexpr elf::FileClass kIdentificationClass = elf::ELFCLASS64; using Elf_Shdr = elf::Elf64_Shdr; @@ -151,6 +153,7 @@ template <class TRAITS> class DisassemblerElf : public Disassembler { public: using Traits = TRAITS; + static constexpr uint16_t kVersion = Traits::kVersion; // Applies quick checks to determine whether |image| *may* point to the start // of an executable. Returns true iff the check passes. static bool QuickDetect(ConstBufferView image); diff --git a/disassembler_no_op.h b/disassembler_no_op.h index ef10651..1d436dd 100644 --- a/disassembler_no_op.h +++ b/disassembler_no_op.h @@ -18,6 +18,8 @@ namespace zucchini { // This disassembler works on any file and does not look for reference. class DisassemblerNoOp : public Disassembler { public: + static constexpr uint16_t kVersion = 1; + DisassemblerNoOp(); DisassemblerNoOp(const DisassemblerNoOp&) = delete; const DisassemblerNoOp& operator=(const DisassemblerNoOp&) = delete; diff --git a/disassembler_win32.h b/disassembler_win32.h index 77b65ac..dfb2533 100644 --- a/disassembler_win32.h +++ b/disassembler_win32.h @@ -26,6 +26,7 @@ class Rel32FinderX86; class Rel32FinderX64; struct Win32X86Traits { + static constexpr uint16_t kVersion = 1; static constexpr Bitness kBitness = kBit32; static constexpr ExecutableType kExeType = kExeTypeWin32X86; enum : uint16_t { kMagic = 0x10B }; @@ -39,6 +40,7 @@ struct Win32X86Traits { }; struct Win32X64Traits { + static constexpr uint16_t kVersion = 1; static constexpr Bitness kBitness = kBit64; static constexpr ExecutableType kExeType = kExeTypeWin32X64; enum : uint16_t { kMagic = 0x20B }; @@ -55,6 +57,7 @@ template <class TRAITS> class DisassemblerWin32 : public Disassembler { public: using Traits = TRAITS; + static constexpr uint16_t kVersion = Traits::kVersion; enum ReferenceType : uint8_t { kReloc, kAbs32, kRel32, kTypeCount }; // Applies quick checks to determine whether |image| *may* point to the start diff --git a/disassembler_ztf.h b/disassembler_ztf.h index 0e73c2a..9b4a94b 100644 --- a/disassembler_ztf.h +++ b/disassembler_ztf.h @@ -123,6 +123,8 @@ class ZtfTranslator { // Disassembler for Zucchini Text Format (ZTF). class DisassemblerZtf : public Disassembler { public: + static constexpr uint16_t kVersion = 1; + // Target Pools enum ReferencePool : uint8_t { kAngles, // <> diff --git a/element_detection.cc b/element_detection.cc index 356c0d7..5548610 100644 --- a/element_detection.cc +++ b/element_detection.cc @@ -9,6 +9,7 @@ #include "components/zucchini/buildflags.h" #include "components/zucchini/disassembler.h" #include "components/zucchini/disassembler_no_op.h" +#include "components/zucchini/patch_utils.h" #if BUILDFLAG(ENABLE_DEX) #include "components/zucchini/disassembler_dex.h" @@ -134,6 +135,40 @@ std::unique_ptr<Disassembler> MakeDisassemblerOfType(ConstBufferView image, } } +uint16_t DisassemblerVersionOfType(ExecutableType exe_type) { + switch (exe_type) { +#if BUILDFLAG(ENABLE_WIN) + case kExeTypeWin32X86: + return DisassemblerWin32X86::kVersion; + case kExeTypeWin32X64: + return DisassemblerWin32X64::kVersion; +#endif // BUILDFLAG(ENABLE_WIN) +#if BUILDFLAG(ENABLE_ELF) + case kExeTypeElfX86: + return DisassemblerElfX86::kVersion; + case kExeTypeElfX64: + return DisassemblerElfX64::kVersion; + case kExeTypeElfAArch32: + return DisassemblerElfAArch32::kVersion; + case kExeTypeElfAArch64: + return DisassemblerElfAArch64::kVersion; +#endif // BUILDFLAG(ENABLE_ELF) +#if BUILDFLAG(ENABLE_DEX) + case kExeTypeDex: + return DisassemblerDex::kVersion; +#endif // BUILDFLAG(ENABLE_DEX) +#if BUILDFLAG(ENABLE_ZTF) + case kExeTypeZtf: + return DisassemblerZtf::kVersion; +#endif // BUILDFLAG(ENABLE_ZTF) + case kExeTypeNoOp: + return DisassemblerNoOp::kVersion; + default: + // If an architecture is disabled then null is handled gracefully. + return kInvalidVersion; + } +} + absl::optional<Element> DetectElementFromDisassembler(ConstBufferView image) { std::unique_ptr<Disassembler> disasm = MakeDisassemblerWithoutFallback(image); if (disasm) diff --git a/element_detection.h b/element_detection.h index 856ec27..febedc5 100644 --- a/element_detection.h +++ b/element_detection.h @@ -28,6 +28,9 @@ std::unique_ptr<Disassembler> MakeDisassemblerWithoutFallback( std::unique_ptr<Disassembler> MakeDisassemblerOfType(ConstBufferView image, ExecutableType exe_type); +// Returns the version associated with disassembler of type |exe_type|. +uint16_t DisassemblerVersionOfType(ExecutableType exe_type); + // Attempts to detect an element associated with |image| and returns it, or // returns nullopt if no element is detected. using ElementDetector = diff --git a/fuzzers/testdata/patch_fuzzer/empty.zuc b/fuzzers/testdata/patch_fuzzer/empty.zuc Binary files differindex 64eacf5..1bda1e9 100644 --- a/fuzzers/testdata/patch_fuzzer/empty.zuc +++ b/fuzzers/testdata/patch_fuzzer/empty.zuc diff --git a/main_utils.cc b/main_utils.cc index 8c47c91..b499817 100644 --- a/main_utils.cc +++ b/main_utils.cc @@ -22,6 +22,7 @@ #include "base/time/time.h" #include "build/build_config.h" #include "components/zucchini/io_utils.h" +#include "components/zucchini/patch_utils.h" #include "components/zucchini/zucchini_commands.h" #if defined(OS_WIN) @@ -69,6 +70,7 @@ constexpr Command kCommands[] = { 3, &MainGen}, {"apply", "-apply <old_file> <patch_file> <new_file> [-keep]", 3, &MainApply}, + {"verify", "-verify <patch_file>", 1, &MainVerify}, {"read", "-read <exe> [-dump]", 1, &MainRead}, {"detect", "-detect <archive_file>", 1, &MainDetect}, {"match", "-match <old_file> <new_file> [-impose=#+#=#+#,#+#=#+#,...]", 2, @@ -206,6 +208,8 @@ bool CheckAndGetFilePathParams(const base::CommandLine& command_line, // Prints main Zucchini usage text. void PrintUsage(std::ostream& err) { + err << "Version: " << zucchini::kMajorVersion << "." + << zucchini::kMinorVersion << std::endl; err << "Usage:" << std::endl; for (const Command& command : kCommands) err << " zucchini " << command.usage << std::endl; diff --git a/patch_read_write_unittest.cc b/patch_read_write_unittest.cc index 627513c..25e1fb0 100644 --- a/patch_read_write_unittest.cc +++ b/patch_read_write_unittest.cc @@ -63,6 +63,7 @@ ByteVector CreatePatchElement() { 0x03, 0, 0, 0, // new_offset 0x13, 0, 0, 0, // new_length 'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X86 + 0x01, 0x00, // element version // EquivalenceSource 1, 0, 0, 0, // src_skip size 0x10, // src_skip content @@ -95,11 +96,12 @@ ByteVector CreatePatchElement() { ByteVector CreateElementMatch() { return { // PatchElementHeader - 0x01, 0, 0, 0, // old_offset - 0x02, 0, 0, 0, // old_length - 0x03, 0, 0, 0, // new_offset - 0x04, 0, 0, 0, // new_length - 'D', 'E', 'X', ' ', // exe_type = kExeTypeDex + 0x01, 0, 0, 0, // old_offset + 0x02, 0, 0, 0, // old_length + 0x03, 0, 0, 0, // new_offset + 0x04, 0, 0, 0, // new_length + 'D', 'E', 'X', ' ', // exe_type = kExeTypeDex + 0x01, 0x00, // element version }; } @@ -586,10 +588,26 @@ TEST(PatchElementTest, WrongExtraData) { } } +TEST(PatchElementTest, WrongVersion) { + // Bump element version to 2. + { + ByteVector data = CreatePatchElement(); + ModifyByte(offsetof(PatchElementHeader, version), 0x01, 0x02, &data); + TestInvalidInitialize<PatchElementReader>(&data); + } + // Bump element version to 0. + { + ByteVector data = CreatePatchElement(); + ModifyByte(offsetof(PatchElementHeader, version), 0x01, 0x00, &data); + TestInvalidInitialize<PatchElementReader>(&data); + } +} + TEST(EnsemblePatchTest, RawPatch) { ByteVector data = { // PatchHeader - 0x5A, 0x75, 0x63, 0x00, // magic + 0x5A, 0x75, 0x63, 0x63, // magic + 0x01, 0x00, 0x00, 0x00, // major/minor version 0x10, 0x32, 0x54, 0x76, // old_size 0x00, 0x11, 0x22, 0x33, // old_crc 0x01, 0, 0, 0, // new_size @@ -602,7 +620,8 @@ TEST(EnsemblePatchTest, RawPatch) { 0x02, 0, 0, 0, // old_length 0x00, 0, 0, 0, // new_offset 0x01, 0, 0, 0, // new_length - 'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X86 + 'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X8 + 0x01, 0x00, // element version // EquivalenceSource 0, 0, 0, 0, // src_skip size 0, 0, 0, 0, // dst_skip size @@ -624,6 +643,8 @@ TEST(EnsemblePatchTest, RawPatch) { PatchHeader header = ensemble_patch_reader.header(); EXPECT_EQ(PatchHeader::kMagic, header.magic); + EXPECT_EQ(kMajorVersion, header.major_version); + EXPECT_EQ(kMinorVersion, header.minor_version); EXPECT_EQ(0x76543210U, header.old_size); EXPECT_EQ(0x33221100U, header.old_crc); EXPECT_EQ(0x01U, header.new_size); @@ -647,7 +668,8 @@ TEST(EnsemblePatchTest, RawPatch) { TEST(EnsemblePatchTest, CheckFile) { ByteVector data = { // PatchHeader - 0x5A, 0x75, 0x63, 0x00, // magic + 0x5A, 0x75, 0x63, 0x63, // magic + 0x01, 0x00, 0x00, 0x00, // major/minor version 0x05, 0x00, 0x00, 0x00, // old_size 0xDF, 0x13, 0xE4, 0x10, // old_crc 0x03, 0x00, 0x00, 0x00, // new_size @@ -661,6 +683,7 @@ TEST(EnsemblePatchTest, CheckFile) { 0x00, 0, 0, 0, // new_offset 0x03, 0, 0, 0, // new_length 'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X86 + 0x01, 0x00, // element version // EquivalenceSource 0, 0, 0, 0, // src_skip size 0, 0, 0, 0, // dst_skip size @@ -695,7 +718,45 @@ TEST(EnsemblePatchTest, CheckFile) { TEST(EnsemblePatchTest, InvalidMagic) { ByteVector data = { // PatchHeader - 0x42, 0x42, 0x42, 0x00, // magic + 0x42, 0x42, 0x42, 0x42, // magic + 0x01, 0x00, 0x00, 0x00, // major/minor version + 0x10, 0x32, 0x54, 0x76, // old_size + 0x00, 0x11, 0x22, 0x33, // old_crc + 0x03, 0x00, 0x00, 0x00, // new_size + 0x44, 0x55, 0x66, 0x77, // new_crc + + 1, 0, 0, 0, // number of element + + // PatchElementHeader + 0x01, 0, 0, 0, // old_offset + 0x02, 0, 0, 0, // old_length + 0x00, 0, 0, 0, // new_offset + 0x03, 0, 0, 0, // new_length + 'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X86 + 0x01, 0x00, // element version + // EquivalenceSource + 0, 0, 0, 0, // src_skip size + 0, 0, 0, 0, // dst_skip size + 0, 0, 0, 0, // copy_count size + // ExtraDataSource + 0, 0, 0, 0, // extra_data size + // RawDeltaSource + 0, 0, 0, 0, // raw_delta_skip size + 0, 0, 0, 0, // raw_delta_diff size + // ReferenceDeltaSource + 0, 0, 0, 0, // reference_delta size + // PatchElementReader + 0, 0, 0, 0, // pool count + }; + + TestInvalidInitialize<EnsemblePatchReader>(&data); +} + +TEST(EnsemblePatchTest, InvalidVersion) { + ByteVector data = { + // PatchHeader + 0x5A, 0x75, 0x63, 0x63, // magic + 0x02, 0x01, 0x00, 0x00, // major/minor version 0x10, 0x32, 0x54, 0x76, // old_size 0x00, 0x11, 0x22, 0x33, // old_crc 0x03, 0x00, 0x00, 0x00, // new_size @@ -709,6 +770,7 @@ TEST(EnsemblePatchTest, InvalidMagic) { 0x00, 0, 0, 0, // new_offset 0x03, 0, 0, 0, // new_length 'P', 'x', '8', '6', // exe_type = EXE_TYPE_WIN32_X86 + 0x01, 0x00, // element version // EquivalenceSource 0, 0, 0, 0, // src_skip size 0, 0, 0, 0, // dst_skip size diff --git a/patch_reader.cc b/patch_reader.cc index 99951da..8fd9b57 100644 --- a/patch_reader.cc +++ b/patch_reader.cc @@ -10,6 +10,7 @@ #include "base/numerics/safe_conversions.h" #include "components/zucchini/algorithm.h" #include "components/zucchini/crc32.h" +#include "components/zucchini/element_detection.h" namespace zucchini { @@ -27,6 +28,12 @@ bool ParseElementMatch(BufferSource* source, ElementMatch* element_match) { LOG(ERROR) << "Invalid ExecutableType found."; return false; } + uint16_t element_version = DisassemblerVersionOfType(exe_type); + if (element_version != unsafe_element_header.version) { + LOG(ERROR) << "Element version doesn't match. Expected: " << element_version + << ", Actual:" << unsafe_element_header.version; + return false; + } if (!unsafe_element_header.old_length || !unsafe_element_header.new_length) { LOG(ERROR) << "Empty patch element found."; return false; @@ -334,6 +341,11 @@ bool EnsemblePatchReader::Initialize(BufferSource* source) { LOG(ERROR) << "Patch contains invalid magic."; return false; } + if (header_.major_version != kMajorVersion) { + LOG(ERROR) << "Patch major version doesn't match. Expected: " + << kMajorVersion << ", Actual:" << header_.major_version; + return false; + } // |header_| is assumed to be safe from this point forward. uint32_t element_count = 0; diff --git a/patch_utils.h b/patch_utils.h index 5f49195..822fedc 100644 --- a/patch_utils.h +++ b/patch_utils.h @@ -14,6 +14,17 @@ namespace zucchini { +// A change in major version indicates breaking changes such that a patch +// definitely cannot be applied by a zucchini binary whose major version doesn't +// match. +enum : uint16_t { kMajorVersion = 1 }; +// A change in minor version indicates possibly breaking changes at the element +// level, such that it may not be possible to apply a patch whose minor version +// doesn't match this version. To determine if a given patch may be applied with +// this version, VerifyPatch() should be called. +enum : uint16_t { kMinorVersion = 0 }; +enum : uint16_t { kInvalidVersion = 0xffff }; + // A Zucchini 'ensemble' patch is the concatenation of a patch header with a // list of patch 'elements', each containing data for patching individual // elements. @@ -24,9 +35,11 @@ namespace zucchini { // Header for a Zucchini patch, found at the beginning of an ensemble patch. struct PatchHeader { // Magic signature at the beginning of a Zucchini patch file. - enum : uint32_t { kMagic = 'Z' | ('u' << 8) | ('c' << 16) }; + enum : uint32_t { kMagic = 'Z' | ('u' << 8) | ('c' << 16) | ('c' << 24) }; uint32_t magic = 0; + uint16_t major_version = kInvalidVersion; + uint16_t minor_version = kInvalidVersion; uint32_t old_size = 0; uint32_t old_crc = 0; uint32_t new_size = 0; @@ -34,7 +47,7 @@ struct PatchHeader { }; // Sanity check. -static_assert(sizeof(PatchHeader) == 20, "PatchHeader must be 20 bytes"); +static_assert(sizeof(PatchHeader) == 24, "PatchHeader must be 24 bytes"); // Header for a patch element, found at the beginning of every patch element. struct PatchElementHeader { @@ -43,11 +56,12 @@ struct PatchElementHeader { uint32_t new_offset; uint32_t new_length; uint32_t exe_type; // ExecutableType. + uint16_t version = kInvalidVersion; }; // Sanity check. -static_assert(sizeof(PatchElementHeader) == 20, - "PatchElementHeader must be 20 bytes"); +static_assert(sizeof(PatchElementHeader) == 22, + "PatchElementHeader must be 22 bytes"); #pragma pack(pop) diff --git a/patch_writer.cc b/patch_writer.cc index 1206208..04f3244 100644 --- a/patch_writer.cc +++ b/patch_writer.cc @@ -10,6 +10,7 @@ #include "base/numerics/checked_math.h" #include "base/numerics/safe_conversions.h" #include "components/zucchini/crc32.h" +#include "components/zucchini/element_detection.h" namespace zucchini { @@ -30,6 +31,7 @@ bool SerializeElementMatch(const ElementMatch& element_match, element_header.new_length = base::checked_cast<uint32_t>(element_match.new_element.size); element_header.exe_type = element_match.exe_type(); + element_header.version = DisassemblerVersionOfType(element_match.exe_type()); return sink->PutValue<PatchElementHeader>(element_header); } @@ -248,11 +250,15 @@ EnsemblePatchWriter::~EnsemblePatchWriter() = default; EnsemblePatchWriter::EnsemblePatchWriter(const PatchHeader& header) : header_(header) { DCHECK_EQ(header_.magic, PatchHeader::kMagic); + DCHECK_EQ(header_.major_version, kMajorVersion); + DCHECK_EQ(header_.minor_version, kMinorVersion); } EnsemblePatchWriter::EnsemblePatchWriter(ConstBufferView old_image, ConstBufferView new_image) { header_.magic = PatchHeader::kMagic; + header_.major_version = kMajorVersion; + header_.minor_version = kMinorVersion; header_.old_size = base::checked_cast<uint32_t>(old_image.size()); header_.old_crc = CalculateCrc32(old_image.begin(), old_image.end()); header_.new_size = base::checked_cast<uint32_t>(new_image.size()); diff --git a/zucchini_commands.cc b/zucchini_commands.cc index 93929bd..0699cbe 100644 --- a/zucchini_commands.cc +++ b/zucchini_commands.cc @@ -51,6 +51,11 @@ zucchini::status::Code MainApply(MainParams params) { params.command_line.HasSwitch(kSwitchKeep)); } +zucchini::status::Code MainVerify(MainParams params) { + CHECK_EQ(1U, params.file_paths.size()); + return zucchini::VerifyPatch(params.file_paths[0]); +} + zucchini::status::Code MainRead(MainParams params) { CHECK_EQ(1U, params.file_paths.size()); base::File input_file(params.file_paths[0], diff --git a/zucchini_commands.h b/zucchini_commands.h index cef18dc..91c2ef8 100644 --- a/zucchini_commands.h +++ b/zucchini_commands.h @@ -36,6 +36,9 @@ zucchini::status::Code MainGen(MainParams params); // Command Function: Patch application. zucchini::status::Code MainApply(MainParams params); +// Command Function: Verify patch format and compatibility. +zucchini::status::Code MainVerify(MainParams params); + // Command Function: Read and dump references from an executable. zucchini::status::Code MainRead(MainParams params); diff --git a/zucchini_integration.cc b/zucchini_integration.cc index ff7e792..bf28b3c 100644 --- a/zucchini_integration.cc +++ b/zucchini_integration.cc @@ -146,6 +146,22 @@ status::Code ApplyCommon(base::File old_file, return status::kStatusSuccess; } +status::Code VerifyPatchCommon(base::File patch_file, + base::FilePath patch_name) { + MappedFileReader mapped_patch(std::move(patch_file)); + if (mapped_patch.HasError()) { + LOG(ERROR) << "Error with file " << patch_name.value() << ": " + << mapped_patch.error(); + return status::kStatusFileReadError; + } + auto patch_reader = EnsemblePatchReader::Create(mapped_patch.region()); + if (!patch_reader.has_value()) { + LOG(ERROR) << "Error reading patch header."; + return status::kStatusPatchReadError; + } + return status::kStatusSuccess; +} + } // namespace status::Code Generate(base::File old_file, @@ -206,4 +222,15 @@ status::Code Apply(const base::FilePath& old_path, std::move(new_file), file_names, force_keep); } +status::Code VerifyPatch(base::File patch_file) { + return VerifyPatchCommon(std::move(patch_file), base::FilePath()); +} + +status::Code VerifyPatch(const base::FilePath& patch_path) { + using base::File; + File patch_file(patch_path, File::FLAG_OPEN | File::FLAG_READ | + base::File::FLAG_SHARE_DELETE); + return VerifyPatchCommon(std::move(patch_file), patch_path); +} + } // namespace zucchini diff --git a/zucchini_integration.h b/zucchini_integration.h index 2ae6091..2b6287b 100644 --- a/zucchini_integration.h +++ b/zucchini_integration.h @@ -63,6 +63,16 @@ status::Code Apply(const base::FilePath& old_path, const base::FilePath& new_path, bool force_keep = false); +// Verifies the patch format in |patch_file| and returns +// Code::kStatusPatchReadError if the patch is malformed or version is +// unsupported. Since this uses memory mapped files, crashes are expected in +// case of I/O errors. +status::Code VerifyPatch(base::File patch_file); + +// Alternative VerifyPatch() interface that takes base::FilePath as arguments. +// Performs proper cleanup in Windows and UNIX if failure occurs. +status::Code VerifyPatch(const base::FilePath& patch_path); + } // namespace zucchini #endif // COMPONENTS_ZUCCHINI_ZUCCHINI_INTEGRATION_H_ |