1 //
2 // Copyright (C) 2021 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #include "lz4patch.h"
18 
19 #include <endian.h>
20 #include <unistd.h>
21 #include <fcntl.h>
22 
23 #include <algorithm>
24 #include <string_view>
25 
26 #include <bsdiff/bspatch.h>
27 #include <bsdiff/memory_file.h>
28 #include <bsdiff/file.h>
29 #include <puffin/memory_stream.h>
30 
31 #include "android-base/strings.h"
32 #include "lz4diff/lz4diff.h"
33 #include "lz4diff/lz4diff.pb.h"
34 #include "lz4diff_compress.h"
35 #include "lz4diff_format.h"
36 #include "puffin/puffpatch.h"
37 #include "update_engine/common/hash_calculator.h"
38 #include "update_engine/common/utils.h"
39 
40 namespace chromeos_update_engine {
41 
42 namespace {
43 
44 template <typename T>
BigEndianToHost(T & t)45 constexpr void BigEndianToHost(T& t) {
46   static_assert(std::is_integral_v<T>);
47   static_assert(sizeof(t) == 4 || sizeof(t) == 8 || sizeof(t) == 2);
48   if constexpr (sizeof(t) == 4) {
49     t = be32toh(t);
50   } else if constexpr (sizeof(t) == 8) {
51     t = be64toh(t);
52   } else if constexpr (sizeof(t) == 2) {
53     t = be16toh(t);
54   }
55 }
56 
57 // In memory representation of an LZ4Diff patch, it's not marked as packed
58 // because parsing isn't as simple as reinterpret_cast<> any way.
59 struct Lz4diffPatch {
60   char magic[kLz4diffMagic.size()];
61   uint32_t version;
62   uint32_t pb_header_size;  // size of protobuf message
63   Lz4diffHeader pb_header;
64   std::string_view inner_patch;
65 };
66 
67 // Utility class to interact with puffin API. C++ does not have standard
68 // Read/Write trait. So everybody invent their own file descriptor wrapper.
69 class StringViewStream : public puffin::StreamInterface {
70  public:
71   ~StringViewStream() override = default;
72 
GetSize(uint64_t * size) const73   bool GetSize(uint64_t* size) const override {
74     *size = read_memory_.size();
75     return true;
76   }
77 
GetOffset(uint64_t * offset) const78   bool GetOffset(uint64_t* offset) const override {
79     *offset = offset_;
80     return true;
81   }
82 
Seek(uint64_t offset)83   bool Seek(uint64_t offset) override {
84     TEST_AND_RETURN_FALSE(open_);
85     uint64_t size;
86     GetSize(&size);
87     TEST_AND_RETURN_FALSE(offset <= size);
88     offset_ = offset;
89     return true;
90   }
91 
Read(void * buffer,size_t length)92   bool Read(void* buffer, size_t length) override {
93     TEST_AND_RETURN_FALSE(open_);
94     TEST_AND_RETURN_FALSE(offset_ + length <= read_memory_.size());
95     memcpy(buffer, read_memory_.data() + offset_, length);
96     offset_ += length;
97     return true;
98   }
99 
Write(const void * buffer,size_t length)100   bool Write(const void* buffer, size_t length) override {
101     LOG(ERROR) << "Unsupported operation " << __FUNCTION__;
102     return false;
103   }
104 
Close()105   bool Close() override {
106     open_ = false;
107     return true;
108   }
109 
StringViewStream(std::string_view read_memory)110   constexpr StringViewStream(std::string_view read_memory)
111       : read_memory_(read_memory) {
112     CHECK(!read_memory.empty());
113   }
114 
115  private:
116   // The memory buffer for reading.
117   std::string_view read_memory_;
118 
119   // The current offset.
120   uint64_t offset_{};
121   bool open_{true};
122 };
123 
ParseLz4DifffPatch(std::string_view patch_data,Lz4diffPatch * output)124 bool ParseLz4DifffPatch(std::string_view patch_data, Lz4diffPatch* output) {
125   CHECK_NE(output, nullptr);
126   if (!android::base::StartsWith(patch_data, kLz4diffMagic)) {
127     LOG(ERROR) << "Invalid lz4diff magic: "
128                << HexEncode(patch_data.substr(0, kLz4diffMagic.size()))
129                << ", expected: " << HexEncode(kLz4diffMagic);
130     return false;
131   }
132   Lz4diffPatch& patch = *output;
133   std::memcpy(patch.magic, patch_data.data(), kLz4diffMagic.size());
134   std::memcpy(&patch.version,
135               patch_data.data() + kLz4diffMagic.size(),
136               sizeof(patch.version));
137   BigEndianToHost(patch.version);
138   if (patch.version != kLz4diffVersion) {
139     LOG(ERROR) << "Unsupported lz4diff version: " << patch.version
140                << ", supported version: " << kLz4diffVersion;
141     return false;
142   }
143   std::memcpy(&patch.pb_header_size,
144               patch_data.data() + kLz4diffMagic.size() + sizeof(patch.version),
145               sizeof(patch.pb_header_size));
146   BigEndianToHost(patch.pb_header_size);
147   TEST_AND_RETURN_FALSE(patch.pb_header.ParseFromArray(
148       patch_data.data() + kLz4diffHeaderSize, patch.pb_header_size));
149   patch.inner_patch =
150       patch_data.substr(kLz4diffHeaderSize + patch.pb_header_size);
151   return true;
152 }
153 
bspatch(std::string_view input_data,std::string_view patch_data,Blob * output)154 bool bspatch(std::string_view input_data,
155              std::string_view patch_data,
156              Blob* output) {
157   CHECK_NE(output, nullptr);
158   output->clear();
159   CHECK_GT(patch_data.size(), 0UL);
160   int err =
161       bsdiff::bspatch(reinterpret_cast<const uint8_t*>(input_data.data()),
162                       input_data.size(),
163                       reinterpret_cast<const uint8_t*>(patch_data.data()),
164                       patch_data.size(),
165                       [output](const uint8_t* data, size_t size) -> size_t {
166                         output->insert(output->end(), data, data + size);
167                         return size;
168                       });
169   return err == 0;
170 }
171 
puffpatch(std::string_view input_data,std::string_view patch_data,Blob * output)172 bool puffpatch(std::string_view input_data,
173                std::string_view patch_data,
174                Blob* output) {
175   // Cache size has a big impact on speed of puffpatch, use a default of 5MB to
176   // match update_engine behavior.
177   static constexpr size_t kPuffPatchCacheSize = 5 * 1024 * 1024;
178   return puffin::PuffPatch(std::make_unique<StringViewStream>(input_data),
179                            puffin::MemoryStream::CreateForWrite(output),
180                            reinterpret_cast<const uint8_t*>(patch_data.data()),
181                            patch_data.size(),
182                            kPuffPatchCacheSize);
183 }
184 
ToCompressedBlockVec(const google::protobuf::RepeatedPtrField<CompressedBlockInfo> & rpf)185 std::vector<CompressedBlock> ToCompressedBlockVec(
186     const google::protobuf::RepeatedPtrField<CompressedBlockInfo>& rpf) {
187   std::vector<CompressedBlock> ret;
188   ret.reserve(rpf.size());
189   for (const auto& block : rpf) {
190     auto& info = ret.emplace_back();
191     info.compressed_length = block.compressed_length();
192     info.uncompressed_length = block.uncompressed_length();
193     info.uncompressed_offset = block.uncompressed_offset();
194   }
195   return ret;
196 }
197 
HasPosfixPatches(const Lz4diffPatch & patch)198 bool HasPosfixPatches(const Lz4diffPatch& patch) {
199   for (const auto& info : patch.pb_header.dst_info().block_info()) {
200     if (!info.postfix_bspatch().empty()) {
201       return true;
202     }
203   }
204   return false;
205 }
206 
GetCompressedSize(const google::protobuf::RepeatedPtrField<CompressedBlockInfo> & info)207 size_t GetCompressedSize(
208     const google::protobuf::RepeatedPtrField<CompressedBlockInfo>& info) {
209   size_t compressed_size = 0;
210   for (const auto& block : info) {
211     compressed_size += block.compressed_length();
212   }
213   return compressed_size;
214 }
215 
GetDecompressedSize(const google::protobuf::RepeatedPtrField<CompressedBlockInfo> & info)216 size_t GetDecompressedSize(
217     const google::protobuf::RepeatedPtrField<CompressedBlockInfo>& info) {
218   size_t decompressed_size = 0;
219   for (const auto& block : info) {
220     decompressed_size += block.uncompressed_length();
221   }
222   return decompressed_size;
223 }
224 
ApplyInnerPatch(Blob decompressed_src,const Lz4diffPatch & patch,Blob * decompressed_dst)225 bool ApplyInnerPatch(Blob decompressed_src,
226                      const Lz4diffPatch& patch,
227                      Blob* decompressed_dst) {
228   switch (patch.pb_header.inner_type()) {
229     case InnerPatchType::BSDIFF:
230       TEST_AND_RETURN_FALSE(bspatch(
231           ToStringView(decompressed_src), patch.inner_patch, decompressed_dst));
232       break;
233     case InnerPatchType::PUFFDIFF:
234       TEST_AND_RETURN_FALSE(puffpatch(
235           ToStringView(decompressed_src), patch.inner_patch, decompressed_dst));
236       break;
237     default:
238       LOG(ERROR) << "Unsupported patch type: " << patch.pb_header.inner_type();
239       return false;
240   }
241   return true;
242 }
243 
244 // TODO(zhangkelvin) Rewrite this in C++ 20 coroutine once that's available.
245 // Hand coding CPS is not fun.
Lz4Patch(std::string_view src_data,const Lz4diffPatch & patch,const SinkFunc & sink)246 bool Lz4Patch(std::string_view src_data,
247               const Lz4diffPatch& patch,
248               const SinkFunc& sink) {
249   auto decompressed_src = TryDecompressBlob(
250       src_data,
251       ToCompressedBlockVec(patch.pb_header.src_info().block_info()),
252       patch.pb_header.src_info().zero_padding_enabled());
253   TEST_AND_RETURN_FALSE(!decompressed_src.empty());
254   Blob decompressed_dst;
255   const auto decompressed_dst_size =
256       GetDecompressedSize(patch.pb_header.dst_info().block_info());
257   decompressed_dst.reserve(decompressed_dst_size);
258 
259   ApplyInnerPatch(std::move(decompressed_src), patch, &decompressed_dst);
260 
261   if (!HasPosfixPatches(patch)) {
262     return TryCompressBlob(
263         ToStringView(decompressed_dst),
264         ToCompressedBlockVec(patch.pb_header.dst_info().block_info()),
265         patch.pb_header.dst_info().zero_padding_enabled(),
266         patch.pb_header.dst_info().algo(),
267         sink);
268   }
269   auto postfix_patcher =
270       [&sink,
271        block_idx = 0,
272        &dst_block_info = patch.pb_header.dst_info().block_info()](
273           const uint8_t* data, size_t size) mutable -> size_t {
274     if (block_idx >= dst_block_info.size()) {
275       return sink(data, size);
276     }
277     const auto& block_info = dst_block_info[block_idx];
278     TEST_EQ(size, block_info.compressed_length());
279     DEFER { block_idx++; };
280     if (block_info.postfix_bspatch().empty()) {
281       return sink(data, size);
282     }
283     if (!block_info.sha256_hash().empty()) {
284       Blob actual_hash;
285       TEST_AND_RETURN_FALSE(
286           HashCalculator::RawHashOfBytes(data, size, &actual_hash));
287       if (ToStringView(actual_hash) != block_info.sha256_hash()) {
288         LOG(ERROR) << "Block " << block_info
289                    << " is corrupted. This usually means the patch generator "
290                       "used a different version of LZ4, or an incompatible LZ4 "
291                       "patch generator was used, or LZ4 produces different "
292                       "output on different platforms. Expected hash: "
293                    << HexEncode(block_info.sha256_hash())
294                    << ", actual hash: " << HexEncode(actual_hash);
295         return 0;
296       }
297     }
298     Blob fixed_block;
299     TEST_AND_RETURN_FALSE(
300         bspatch(std::string_view(reinterpret_cast<const char*>(data), size),
301                 block_info.postfix_bspatch(),
302                 &fixed_block));
303     return sink(fixed_block.data(), fixed_block.size());
304   };
305 
306   return TryCompressBlob(
307       ToStringView(decompressed_dst),
308       ToCompressedBlockVec(patch.pb_header.dst_info().block_info()),
309       patch.pb_header.dst_info().zero_padding_enabled(),
310       patch.pb_header.dst_info().algo(),
311       postfix_patcher);
312 }
313 
Lz4Patch(std::string_view src_data,const Lz4diffPatch & patch,Blob * output)314 bool Lz4Patch(std::string_view src_data,
315               const Lz4diffPatch& patch,
316               Blob* output) {
317   Blob blob;
318   const auto output_size =
319       GetCompressedSize(patch.pb_header.dst_info().block_info());
320   blob.reserve(output_size);
321   TEST_AND_RETURN_FALSE(Lz4Patch(
322       src_data, patch, [&blob](const uint8_t* data, size_t size) -> size_t {
323         blob.insert(blob.end(), data, data + size);
324         return size;
325       }));
326   *output = std::move(blob);
327   return true;
328 }
329 
330 }  // namespace
331 
Lz4Patch(std::string_view src_data,std::string_view patch_data,Blob * output)332 bool Lz4Patch(std::string_view src_data,
333               std::string_view patch_data,
334               Blob* output) {
335   Lz4diffPatch patch;
336   TEST_AND_RETURN_FALSE(ParseLz4DifffPatch(patch_data, &patch));
337   return Lz4Patch(src_data, patch, output);
338 }
339 
Lz4Patch(std::string_view src_data,std::string_view patch_data,const SinkFunc & sink)340 bool Lz4Patch(std::string_view src_data,
341               std::string_view patch_data,
342               const SinkFunc& sink) {
343   Lz4diffPatch patch;
344   TEST_AND_RETURN_FALSE(ParseLz4DifffPatch(patch_data, &patch));
345   return Lz4Patch(src_data, patch, sink);
346 }
347 
Lz4Patch(const Blob & src_data,const Blob & patch_data,Blob * output)348 bool Lz4Patch(const Blob& src_data, const Blob& patch_data, Blob* output) {
349   return Lz4Patch(ToStringView(src_data), ToStringView(patch_data), output);
350 }
351 
operator <<(std::ostream & out,const CompressionAlgorithm & info)352 std::ostream& operator<<(std::ostream& out, const CompressionAlgorithm& info) {
353   out << "Algo {type: " << info.Type_Name(info.type());
354   if (info.level() != 0) {
355     out << ", level: " << info.level();
356   }
357   out << "}";
358 
359   return out;
360 }
361 
operator <<(std::ostream & out,const CompressionInfo & info)362 std::ostream& operator<<(std::ostream& out, const CompressionInfo& info) {
363   out << "CompressionInfo {block_info: " << info.block_info()
364       << ", algo: " << info.algo() << "}";
365   return out;
366 }
367 
operator <<(std::ostream & out,const Lz4diffHeader & header)368 std::ostream& operator<<(std::ostream& out, const Lz4diffHeader& header) {
369   out << "Lz4diffHeader {src_info: " << header.src_info()
370       << ", dst_info: " << header.dst_info() << "}";
371   return out;
372 }
373 
374 }  // namespace chromeos_update_engine
375