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/streams/file_stream.h>
30 
31 #include "update_engine/common/boot_control_interface.h"
32 #include "update_engine/common/utils.h"
33 #include "update_engine/payload_consumer/delta_performer.h"
34 #include "update_engine/payload_consumer/payload_constants.h"
35 
36 using std::string;
37 
38 namespace chromeos_update_engine {
39 
40 namespace {
41 const off_t kReadFileBufferSize = 128 * 1024;
42 }  // namespace
43 
FilesystemVerifierAction(const BootControlInterface * boot_control,VerifierMode verifier_mode)44 FilesystemVerifierAction::FilesystemVerifierAction(
45     const BootControlInterface* boot_control,
46     VerifierMode verifier_mode)
47     : verifier_mode_(verifier_mode),
48       boot_control_(boot_control) {}
49 
PerformAction()50 void FilesystemVerifierAction::PerformAction() {
51   // Will tell the ActionProcessor we've failed if we return.
52   ScopedActionCompleter abort_action_completer(processor_, this);
53 
54   if (!HasInputObject()) {
55     LOG(ERROR) << "FilesystemVerifierAction missing input object.";
56     return;
57   }
58   install_plan_ = GetInputObject();
59 
60   // For delta updates (major version 1) we need to populate the source
61   // partition hash if not pre-populated.
62   if (install_plan_.payload_type == InstallPayloadType::kDelta &&
63       install_plan_.partitions.empty() &&
64       verifier_mode_ == VerifierMode::kComputeSourceHash &&
65       DeltaPerformer::kSupportedMinorPayloadVersion <
66           kOpSrcHashMinorPayloadVersion) {
67     LOG(INFO) << "Using legacy partition names.";
68     InstallPlan::Partition part;
69     string part_path;
70 
71     part.name = kLegacyPartitionNameRoot;
72     if (!boot_control_->GetPartitionDevice(
73         part.name, install_plan_.source_slot, &part_path))
74       return;
75     int block_count = 0, block_size = 0;
76     if (utils::GetFilesystemSize(part_path, &block_count, &block_size)) {
77       part.source_size = static_cast<int64_t>(block_count) * block_size;
78       LOG(INFO) << "Partition " << part.name << " size: " << part.source_size
79                 << " bytes (" << block_count << "x" << block_size << ").";
80     }
81     install_plan_.partitions.push_back(part);
82 
83     part.name = kLegacyPartitionNameKernel;
84     if (!boot_control_->GetPartitionDevice(
85         part.name, install_plan_.source_slot, &part_path))
86       return;
87     off_t kernel_part_size = utils::FileSize(part_path);
88     if (kernel_part_size < 0)
89       return;
90     LOG(INFO) << "Partition " << part.name << " size: " << kernel_part_size
91               << " bytes.";
92     part.source_size = kernel_part_size;
93     install_plan_.partitions.push_back(part);
94   }
95 
96   if (install_plan_.partitions.empty()) {
97     LOG(INFO) << "No partitions to verify.";
98     if (HasOutputPipe())
99       SetOutputObject(install_plan_);
100     abort_action_completer.set_code(ErrorCode::kSuccess);
101     return;
102   }
103 
104   StartPartitionHashing();
105   abort_action_completer.set_should_complete(false);
106 }
107 
TerminateProcessing()108 void FilesystemVerifierAction::TerminateProcessing() {
109   cancelled_ = true;
110   Cleanup(ErrorCode::kSuccess);  // error code is ignored if canceled_ is true.
111 }
112 
IsCleanupPending() const113 bool FilesystemVerifierAction::IsCleanupPending() const {
114   return src_stream_ != nullptr;
115 }
116 
Cleanup(ErrorCode code)117 void FilesystemVerifierAction::Cleanup(ErrorCode code) {
118   src_stream_.reset();
119   // This memory is not used anymore.
120   buffer_.clear();
121 
122   if (cancelled_)
123     return;
124   if (code == ErrorCode::kSuccess && HasOutputPipe())
125     SetOutputObject(install_plan_);
126   processor_->ActionComplete(this, code);
127 }
128 
StartPartitionHashing()129 void FilesystemVerifierAction::StartPartitionHashing() {
130   if (partition_index_ == install_plan_.partitions.size()) {
131     // We never called this action with kVerifySourceHash directly, if we are in
132     // this mode, it means the target partition verification has failed, so we
133     // should set the error code to reflect the error in target.
134     if (verifier_mode_ == VerifierMode::kVerifySourceHash)
135       Cleanup(ErrorCode::kNewRootfsVerificationError);
136     else
137       Cleanup(ErrorCode::kSuccess);
138     return;
139   }
140   InstallPlan::Partition& partition =
141       install_plan_.partitions[partition_index_];
142 
143   string part_path;
144   switch (verifier_mode_) {
145     case VerifierMode::kComputeSourceHash:
146     case VerifierMode::kVerifySourceHash:
147       boot_control_->GetPartitionDevice(
148           partition.name, install_plan_.source_slot, &part_path);
149       remaining_size_ = partition.source_size;
150       break;
151     case VerifierMode::kVerifyTargetHash:
152       boot_control_->GetPartitionDevice(
153           partition.name, install_plan_.target_slot, &part_path);
154       remaining_size_ = partition.target_size;
155       break;
156   }
157   LOG(INFO) << "Hashing partition " << partition_index_ << " ("
158             << partition.name << ") on device " << part_path;
159   if (part_path.empty())
160     return Cleanup(ErrorCode::kFilesystemVerifierError);
161 
162   brillo::ErrorPtr error;
163   src_stream_ = brillo::FileStream::Open(
164       base::FilePath(part_path),
165       brillo::Stream::AccessMode::READ,
166       brillo::FileStream::Disposition::OPEN_EXISTING,
167       &error);
168 
169   if (!src_stream_) {
170     LOG(ERROR) << "Unable to open " << part_path << " for reading";
171     return Cleanup(ErrorCode::kFilesystemVerifierError);
172   }
173 
174   buffer_.resize(kReadFileBufferSize);
175   read_done_ = false;
176   hasher_.reset(new HashCalculator());
177 
178   // Start the first read.
179   ScheduleRead();
180 }
181 
ScheduleRead()182 void FilesystemVerifierAction::ScheduleRead() {
183   size_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()),
184                                   remaining_size_);
185   if (!bytes_to_read) {
186     OnReadDoneCallback(0);
187     return;
188   }
189 
190   bool read_async_ok = src_stream_->ReadAsync(
191     buffer_.data(),
192     bytes_to_read,
193     base::Bind(&FilesystemVerifierAction::OnReadDoneCallback,
194                base::Unretained(this)),
195     base::Bind(&FilesystemVerifierAction::OnReadErrorCallback,
196                base::Unretained(this)),
197     nullptr);
198 
199   if (!read_async_ok) {
200     LOG(ERROR) << "Unable to schedule an asynchronous read from the stream.";
201     Cleanup(ErrorCode::kError);
202   }
203 }
204 
OnReadDoneCallback(size_t bytes_read)205 void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) {
206   if (bytes_read == 0) {
207     read_done_ = true;
208   } else {
209     remaining_size_ -= bytes_read;
210     CHECK(!read_done_);
211     if (!hasher_->Update(buffer_.data(), bytes_read)) {
212       LOG(ERROR) << "Unable to update the hash.";
213       Cleanup(ErrorCode::kError);
214       return;
215     }
216   }
217 
218   // We either terminate the current partition or have more data to read.
219   if (cancelled_)
220     return Cleanup(ErrorCode::kError);
221 
222   if (read_done_ || remaining_size_ == 0) {
223     if (remaining_size_ != 0) {
224       LOG(ERROR) << "Failed to read the remaining " << remaining_size_
225                  << " bytes from partition "
226                  << install_plan_.partitions[partition_index_].name;
227       return Cleanup(ErrorCode::kFilesystemVerifierError);
228     }
229     return FinishPartitionHashing();
230   }
231   ScheduleRead();
232 }
233 
OnReadErrorCallback(const brillo::Error * error)234 void FilesystemVerifierAction::OnReadErrorCallback(
235       const brillo::Error* error) {
236   // TODO(deymo): Transform the read-error into an specific ErrorCode.
237   LOG(ERROR) << "Asynchronous read failed.";
238   Cleanup(ErrorCode::kError);
239 }
240 
FinishPartitionHashing()241 void FilesystemVerifierAction::FinishPartitionHashing() {
242   if (!hasher_->Finalize()) {
243     LOG(ERROR) << "Unable to finalize the hash.";
244     return Cleanup(ErrorCode::kError);
245   }
246   InstallPlan::Partition& partition =
247       install_plan_.partitions[partition_index_];
248   LOG(INFO) << "Hash of " << partition.name << ": " << hasher_->hash();
249 
250   switch (verifier_mode_) {
251     case VerifierMode::kComputeSourceHash:
252       partition.source_hash = hasher_->raw_hash();
253       partition_index_++;
254       break;
255     case VerifierMode::kVerifyTargetHash:
256       if (partition.target_hash != hasher_->raw_hash()) {
257         LOG(ERROR) << "New '" << partition.name
258                    << "' partition verification failed.";
259         if (DeltaPerformer::kSupportedMinorPayloadVersion <
260             kOpSrcHashMinorPayloadVersion)
261           return Cleanup(ErrorCode::kNewRootfsVerificationError);
262         // If we support per-operation source hash, then we skipped source
263         // filesystem verification, now that the target partition does not
264         // match, we need to switch to kVerifySourceHash mode to check if it's
265         // because the source partition does not match either.
266         verifier_mode_ = VerifierMode::kVerifySourceHash;
267         partition_index_ = 0;
268       } else {
269         partition_index_++;
270       }
271       break;
272     case VerifierMode::kVerifySourceHash:
273       if (partition.source_hash != hasher_->raw_hash()) {
274         LOG(ERROR) << "Old '" << partition.name
275                    << "' partition verification failed.";
276         return Cleanup(ErrorCode::kDownloadStateInitializationError);
277       }
278       partition_index_++;
279       break;
280   }
281   // Start hashing the next partition, if any.
282   hasher_.reset();
283   buffer_.clear();
284   src_stream_->CloseBlocking(nullptr);
285   StartPartitionHashing();
286 }
287 
288 }  // namespace chromeos_update_engine
289