aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEtienne Pierre-doray <etiennep@chromium.org>2021-10-28 21:16:04 +0000
committerCopybara-Service <copybara-worker@google.com>2021-10-28 14:31:23 -0700
commitb90a947429fdce96b1d684b9a7af9683cb4a13c1 (patch)
tree333e034b74f5ec4cfb1be1600c4d1a061afbb3bc
parentaff408603b3db5b7974c522db2ad8c5ce2a0f3c1 (diff)
downloadzucchini-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.md18
-rw-r--r--disassembler_dex.h1
-rw-r--r--disassembler_elf.h3
-rw-r--r--disassembler_no_op.h2
-rw-r--r--disassembler_win32.h3
-rw-r--r--disassembler_ztf.h2
-rw-r--r--element_detection.cc35
-rw-r--r--element_detection.h3
-rw-r--r--fuzzers/testdata/patch_fuzzer/empty.zucbin76 -> 82 bytes
-rw-r--r--main_utils.cc4
-rw-r--r--patch_read_write_unittest.cc80
-rw-r--r--patch_reader.cc12
-rw-r--r--patch_utils.h22
-rw-r--r--patch_writer.cc6
-rw-r--r--zucchini_commands.cc5
-rw-r--r--zucchini_commands.h3
-rw-r--r--zucchini_integration.cc27
-rw-r--r--zucchini_integration.h10
18 files changed, 222 insertions, 14 deletions
diff --git a/README.md b/README.md
index d3fd0a1..2d885b6 100644
--- a/README.md
+++ b/README.md
@@ -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
index 64eacf5..1bda1e9 100644
--- a/fuzzers/testdata/patch_fuzzer/empty.zuc
+++ b/fuzzers/testdata/patch_fuzzer/empty.zuc
Binary files differ
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_