1 //
2 // Copyright (C) 2012 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 "update_engine/payload_consumer/filesystem_verifier_action.h"
18 
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 
24 #include <algorithm>
25 #include <cstdlib>
26 #include <string>
27 
28 #include <base/bind.h>
29 #include <brillo/data_encoding.h>
30 #include <brillo/streams/file_stream.h>
31 
32 #include "update_engine/common/utils.h"
33 
34 using brillo::data_encoding::Base64Encode;
35 using std::string;
36 
37 namespace chromeos_update_engine {
38 
39 namespace {
40 const off_t kReadFileBufferSize = 128 * 1024;
41 }  // namespace
42 
43 void FilesystemVerifierAction::PerformAction() {
44   // Will tell the ActionProcessor we've failed if we return.
45   ScopedActionCompleter abort_action_completer(processor_, this);
46 
47   if (!HasInputObject()) {
48     LOG(ERROR) << "FilesystemVerifierAction missing input object.";
49     return;
50   }
51   install_plan_ = GetInputObject();
52 
53   if (install_plan_.partitions.empty()) {
54     LOG(INFO) << "No partitions to verify.";
55     if (HasOutputPipe())
56       SetOutputObject(install_plan_);
57     abort_action_completer.set_code(ErrorCode::kSuccess);
58     return;
59   }
60 
61   StartPartitionHashing();
62   abort_action_completer.set_should_complete(false);
63 }
64 
65 void FilesystemVerifierAction::TerminateProcessing() {
66   cancelled_ = true;
67   Cleanup(ErrorCode::kSuccess);  // error code is ignored if canceled_ is true.
68 }
69 
70 void FilesystemVerifierAction::Cleanup(ErrorCode code) {
71   src_stream_.reset();
72   // This memory is not used anymore.
73   buffer_.clear();
74 
75   if (cancelled_)
76     return;
77   if (code == ErrorCode::kSuccess && HasOutputPipe())
78     SetOutputObject(install_plan_);
79   processor_->ActionComplete(this, code);
80 }
81 
82 void FilesystemVerifierAction::StartPartitionHashing() {
83   if (partition_index_ == install_plan_.partitions.size()) {
84     Cleanup(ErrorCode::kSuccess);
85     return;
86   }
87   const InstallPlan::Partition& partition =
88       install_plan_.partitions[partition_index_];
89 
90   string part_path;
91   switch (verifier_step_) {
92     case VerifierStep::kVerifySourceHash:
93       part_path = partition.source_path;
94       partition_size_ = partition.source_size;
95       break;
96     case VerifierStep::kVerifyTargetHash:
97       part_path = partition.target_path;
98       partition_size_ = partition.target_size;
99       break;
100   }
101 
102   if (part_path.empty()) {
103     if (partition_size_ == 0) {
104       LOG(INFO) << "Skip hashing partition " << partition_index_ << " ("
105                 << partition.name << ") because size is 0.";
106       partition_index_++;
107       StartPartitionHashing();
108       return;
109     }
110     LOG(ERROR) << "Cannot hash partition " << partition_index_ << " ("
111                << partition.name
112                << ") because its device path cannot be determined.";
113     Cleanup(ErrorCode::kFilesystemVerifierError);
114     return;
115   }
116 
117   LOG(INFO) << "Hashing partition " << partition_index_ << " ("
118             << partition.name << ") on device " << part_path;
119 
120   brillo::ErrorPtr error;
121   src_stream_ =
122       brillo::FileStream::Open(base::FilePath(part_path),
123                                brillo::Stream::AccessMode::READ,
124                                brillo::FileStream::Disposition::OPEN_EXISTING,
125                                &error);
126 
127   if (!src_stream_) {
128     LOG(ERROR) << "Unable to open " << part_path << " for reading";
129     Cleanup(ErrorCode::kFilesystemVerifierError);
130     return;
131   }
132 
133   buffer_.resize(kReadFileBufferSize);
134   hasher_ = std::make_unique<HashCalculator>();
135 
136   offset_ = 0;
137   if (verifier_step_ == VerifierStep::kVerifyTargetHash &&
138       install_plan_.write_verity) {
139     if (!verity_writer_->Init(partition)) {
140       Cleanup(ErrorCode::kVerityCalculationError);
141       return;
142     }
143   }
144 
145   // Start the first read.
146   ScheduleRead();
147 }
148 
149 void FilesystemVerifierAction::ScheduleRead() {
150   const InstallPlan::Partition& partition =
151       install_plan_.partitions[partition_index_];
152 
153   // We can only start reading anything past |hash_tree_offset| after we have
154   // already read all the data blocks that the hash tree covers. The same
155   // applies to FEC.
156   uint64_t read_end = partition_size_;
157   if (partition.hash_tree_size != 0 &&
158       offset_ < partition.hash_tree_data_offset + partition.hash_tree_data_size)
159     read_end = std::min(read_end, partition.hash_tree_offset);
160   if (partition.fec_size != 0 &&
161       offset_ < partition.fec_data_offset + partition.fec_data_size)
162     read_end = std::min(read_end, partition.fec_offset);
163   size_t bytes_to_read =
164       std::min(static_cast<uint64_t>(buffer_.size()), read_end - offset_);
165   if (!bytes_to_read) {
166     FinishPartitionHashing();
167     return;
168   }
169 
170   bool read_async_ok = src_stream_->ReadAsync(
171       buffer_.data(),
172       bytes_to_read,
173       base::Bind(&FilesystemVerifierAction::OnReadDoneCallback,
174                  base::Unretained(this)),
175       base::Bind(&FilesystemVerifierAction::OnReadErrorCallback,
176                  base::Unretained(this)),
177       nullptr);
178 
179   if (!read_async_ok) {
180     LOG(ERROR) << "Unable to schedule an asynchronous read from the stream.";
181     Cleanup(ErrorCode::kError);
182   }
183 }
184 
185 void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) {
186   if (cancelled_) {
187     Cleanup(ErrorCode::kError);
188     return;
189   }
190 
191   if (bytes_read == 0) {
192     LOG(ERROR) << "Failed to read the remaining " << partition_size_ - offset_
193                << " bytes from partition "
194                << install_plan_.partitions[partition_index_].name;
195     Cleanup(ErrorCode::kFilesystemVerifierError);
196     return;
197   }
198 
199   if (!hasher_->Update(buffer_.data(), bytes_read)) {
200     LOG(ERROR) << "Unable to update the hash.";
201     Cleanup(ErrorCode::kError);
202     return;
203   }
204 
205   if (verifier_step_ == VerifierStep::kVerifyTargetHash &&
206       install_plan_.write_verity) {
207     if (!verity_writer_->Update(offset_, buffer_.data(), bytes_read)) {
208       Cleanup(ErrorCode::kVerityCalculationError);
209       return;
210     }
211   }
212 
213   offset_ += bytes_read;
214 
215   if (offset_ == partition_size_) {
216     FinishPartitionHashing();
217     return;
218   }
219 
220   ScheduleRead();
221 }
222 
223 void FilesystemVerifierAction::OnReadErrorCallback(const brillo::Error* error) {
224   // TODO(deymo): Transform the read-error into an specific ErrorCode.
225   LOG(ERROR) << "Asynchronous read failed.";
226   Cleanup(ErrorCode::kError);
227 }
228 
229 void FilesystemVerifierAction::FinishPartitionHashing() {
230   if (!hasher_->Finalize()) {
231     LOG(ERROR) << "Unable to finalize the hash.";
232     Cleanup(ErrorCode::kError);
233     return;
234   }
235   InstallPlan::Partition& partition =
236       install_plan_.partitions[partition_index_];
237   LOG(INFO) << "Hash of " << partition.name << ": "
238             << Base64Encode(hasher_->raw_hash());
239 
240   switch (verifier_step_) {
241     case VerifierStep::kVerifyTargetHash:
242       if (partition.target_hash != hasher_->raw_hash()) {
243         LOG(ERROR) << "New '" << partition.name
244                    << "' partition verification failed.";
245         if (partition.source_hash.empty()) {
246           // No need to verify source if it is a full payload.
247           Cleanup(ErrorCode::kNewRootfsVerificationError);
248           return;
249         }
250         // If we have not verified source partition yet, now that the target
251         // partition does not match, and it's not a full payload, we need to
252         // switch to kVerifySourceHash step to check if it's because the source
253         // partition does not match either.
254         verifier_step_ = VerifierStep::kVerifySourceHash;
255       } else {
256         partition_index_++;
257       }
258       break;
259     case VerifierStep::kVerifySourceHash:
260       if (partition.source_hash != hasher_->raw_hash()) {
261         LOG(ERROR) << "Old '" << partition.name
262                    << "' partition verification failed.";
263         LOG(ERROR) << "This is a server-side error due to mismatched delta"
264                    << " update image!";
265         LOG(ERROR) << "The delta I've been given contains a " << partition.name
266                    << " delta update that must be applied over a "
267                    << partition.name << " with a specific checksum, but the "
268                    << partition.name
269                    << " we're starting with doesn't have that checksum! This"
270                       " means that the delta I've been given doesn't match my"
271                       " existing system. The "
272                    << partition.name << " partition I have has hash: "
273                    << Base64Encode(hasher_->raw_hash())
274                    << " but the update expected me to have "
275                    << Base64Encode(partition.source_hash) << " .";
276         LOG(INFO) << "To get the checksum of the " << partition.name
277                   << " partition run this command: dd if="
278                   << partition.source_path
279                   << " bs=1M count=" << partition.source_size
280                   << " iflag=count_bytes 2>/dev/null | openssl dgst -sha256 "
281                      "-binary | openssl base64";
282         LOG(INFO) << "To get the checksum of partitions in a bin file, "
283                   << "run: .../src/scripts/sha256_partitions.sh .../file.bin";
284         Cleanup(ErrorCode::kDownloadStateInitializationError);
285         return;
286       }
287       // The action will skip kVerifySourceHash step if target partition hash
288       // matches, if we are in this step, it means target hash does not match,
289       // and now that the source partition hash matches, we should set the error
290       // code to reflect the error in target partition.
291       // We only need to verify the source partition which the target hash does
292       // not match, the rest of the partitions don't matter.
293       Cleanup(ErrorCode::kNewRootfsVerificationError);
294       return;
295   }
296   // Start hashing the next partition, if any.
297   hasher_.reset();
298   buffer_.clear();
299   src_stream_->CloseBlocking(nullptr);
300   StartPartitionHashing();
301 }
302 
303 }  // namespace chromeos_update_engine
304