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