xref: /aosp_15_r20/system/update_engine/payload_consumer/verified_source_fd.cc (revision 5a9231315b4521097b8dc3750bc806fcafe0c72f)
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 // limi
15 
16 #include "update_engine/payload_consumer/verified_source_fd.h"
17 
18 #include <fcntl.h>
19 #include <sys/stat.h>
20 
21 #include <memory>
22 #include <vector>
23 
24 #include <base/strings/string_number_conversions.h>
25 #include <android-base/stringprintf.h>
26 
27 #include "update_engine/common/error_code.h"
28 #include "update_engine/common/hash_calculator.h"
29 #include "update_engine/common/utils.h"
30 #include "update_engine/payload_consumer/extent_writer.h"
31 #include "update_engine/payload_consumer/file_descriptor.h"
32 #include "update_engine/payload_consumer/file_descriptor_utils.h"
33 #include "update_engine/payload_consumer/partition_writer.h"
34 #include "update_engine/update_metadata.pb.h"
35 #if USE_FEC
36 #include "update_engine/payload_consumer/fec_file_descriptor.h"
37 #endif
38 
39 namespace chromeos_update_engine {
40 using std::string;
41 
OpenCurrentECCPartition()42 bool VerifiedSourceFd::OpenCurrentECCPartition() {
43   // No support for ECC for full payloads.
44   // Full payload should not have any opeartion that requires ECC partitions.
45   if (source_ecc_fd_)
46     return true;
47 
48   if (source_ecc_open_failure_)
49     return false;
50 
51 #if USE_FEC
52   auto fd = std::make_shared<FecFileDescriptor>();
53   if (!fd->Open(source_path_.c_str(), O_RDONLY, 0)) {
54     PLOG(ERROR) << "Unable to open ECC source partition " << source_path_;
55     source_ecc_open_failure_ = true;
56     return false;
57   }
58   source_ecc_fd_ = fd;
59 #else
60   // No support for ECC compiled.
61   source_ecc_open_failure_ = true;
62 #endif  // USE_FEC
63 
64   return !source_ecc_open_failure_;
65 }
66 
WriteBackCorrectedSourceBlocks(const std::vector<unsigned char> & source_data,const google::protobuf::RepeatedPtrField<Extent> & extents)67 bool VerifiedSourceFd::WriteBackCorrectedSourceBlocks(
68     const std::vector<unsigned char>& source_data,
69     const google::protobuf::RepeatedPtrField<Extent>& extents) {
70   utils::SetBlockDeviceReadOnly(source_path_, false);
71   DEFER {
72     utils::SetBlockDeviceReadOnly(source_path_, true);
73   };
74   auto fd = std::make_shared<EintrSafeFileDescriptor>();
75   TEST_AND_RETURN_FALSE_ERRNO(fd->Open(source_path_.c_str(), O_RDWR));
76   DirectExtentWriter writer(fd);
77   TEST_AND_RETURN_FALSE(writer.Init(extents, block_size_));
78   TEST_AND_RETURN_FALSE(writer.Write(source_data.data(), source_data.size()));
79   return true;
80 }
81 
ChooseSourceFD(const InstallOperation & operation,ErrorCode * error)82 FileDescriptorPtr VerifiedSourceFd::ChooseSourceFD(
83     const InstallOperation& operation, ErrorCode* error) {
84   if (source_fd_ == nullptr) {
85     LOG(ERROR) << "ChooseSourceFD fail: source_fd_ == nullptr";
86     return nullptr;
87   }
88   if (error) {
89     *error = ErrorCode::kSuccess;
90   }
91   if (!operation.has_src_sha256_hash()) {
92     if (operation.type() == InstallOperation::SOURCE_COPY) {
93       // delta_generator always adds SHA256 hash for source data. If hash is
94       // missing, the only possibility is we are doing a partial update, and
95       // currently processing a partition that's not in the payload. Data on
96       // this partition would be copied to the new slot as is. So, if the
97       // current partition boots fine(either no corruption, or with FEC), the
98       // new partition would boot fine as well. Hence, just return |source_fd_|
99       // to save time.
100       return source_fd_;
101     }
102     // When the operation doesn't include a source hash, we attempt the error
103     // corrected device first since we can't verify the block in the raw device
104     // at this point, but we first need to make sure all extents are readable
105     // since the error corrected device can be shorter or not available.
106     if (OpenCurrentECCPartition() &&
107         fd_utils::ReadAndHashExtents(
108             source_ecc_fd_, operation.src_extents(), block_size_, nullptr)) {
109       if (error) {
110         *error = ErrorCode::kDownloadOperationHashMissingError;
111       }
112       return source_ecc_fd_;
113     }
114     return source_fd_;
115   }
116 
117   brillo::Blob source_hash;
118   brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
119                                     operation.src_sha256_hash().end());
120   if (!fd_utils::ReadAndHashExtents(
121           source_fd_, operation.src_extents(), block_size_, &source_hash)) {
122     LOG(ERROR) << "Failed to compute hash for operation " << operation.type()
123                << " data offset: " << operation.data_offset();
124     if (error) {
125       *error = ErrorCode::kDownloadOperationHashVerificationError;
126     }
127     return nullptr;
128   }
129   if (source_hash == expected_source_hash) {
130     return source_fd_;
131   }
132   if (error) {
133     *error = ErrorCode::kDownloadOperationHashMismatch;
134   }
135   // We fall back to use the error corrected device if the hash of the raw
136   // device doesn't match or there was an error reading the source partition.
137   if (!OpenCurrentECCPartition()) {
138     // The following function call will return false since the source hash
139     // mismatches, but we still want to call it so it prints the appropriate
140     // log message.
141     PartitionWriter::ValidateSourceHash(
142         source_hash, operation, source_fd_, error);
143     return nullptr;
144   }
145   LOG(WARNING) << "Source hash from RAW device mismatched: found "
146                << base::HexEncode(source_hash.data(), source_hash.size())
147                << ", expected "
148                << base::HexEncode(expected_source_hash.data(),
149                                   expected_source_hash.size());
150 
151   std::vector<unsigned char> source_data;
152   if (!utils::ReadExtents(
153           source_ecc_fd_, operation.src_extents(), &source_data, block_size_)) {
154     return nullptr;
155   }
156   if (!HashCalculator::RawHashOfData(source_data, &source_hash)) {
157     return nullptr;
158   }
159   if (PartitionWriter::ValidateSourceHash(
160           source_hash, operation, source_ecc_fd_, error)) {
161     source_ecc_recovered_failures_++;
162     if (WriteBackCorrectedSourceBlocks(source_data, operation.src_extents())) {
163       if (error) {
164         *error = ErrorCode::kSuccess;
165       }
166       return source_fd_;
167     }
168     return source_ecc_fd_;
169   }
170   return nullptr;
171 }
172 
Open()173 bool VerifiedSourceFd::Open() {
174   source_fd_ = std::make_shared<EintrSafeFileDescriptor>();
175   if (source_fd_ == nullptr)
176     return false;
177   if (!source_fd_->Open(source_path_.c_str(), O_RDONLY)) {
178     PLOG(ERROR) << "Failed to open " << source_path_;
179   }
180   return true;
181 }
182 
183 }  // namespace chromeos_update_engine
184