blob: 86dab085aee609ae54a99f75479ee0db030c538e [file] [log] [blame]
Greg Kerr019d59c2016-11-17 14:28:49 -08001// Copyright 2016 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Ben Chan045849f2017-12-18 17:27:07 -08005#include "imageloader/component.h"
Greg Kerr019d59c2016-11-17 14:28:49 -08006
7#include <fcntl.h>
8
9#include <algorithm>
10#include <string>
Eric Caruso089bbff2017-03-21 11:34:15 -070011#include <utility>
Greg Kerr019d59c2016-11-17 14:28:49 -080012#include <vector>
13
Qijiang Fan713061e2021-03-08 15:45:12 +090014#include <base/check.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080015#include <base/files/file.h>
Eric Caruso089bbff2017-03-21 11:34:15 -070016#include <base/files/file_enumerator.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080017#include <base/files/file_path.h>
18#include <base/files/file_util.h>
19#include <base/files/scoped_file.h>
20#include <base/json/json_string_value_serializer.h>
21#include <base/logging.h>
Qijiang Fan886c4692021-02-19 11:54:10 +090022#include <base/notreached.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080023#include <base/numerics/safe_conversions.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080024#include <base/posix/eintr_wrapper.h>
Eric Caruso089bbff2017-03-21 11:34:15 -070025#include <base/strings/string_number_conversions.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080026#include <base/strings/string_util.h>
27#include <crypto/secure_hash.h>
28#include <crypto/sha2.h>
29#include <crypto/signature_verifier.h>
30
Amin Hassani17a185b2021-02-10 12:07:57 -080031#include "imageloader/global_context.h"
Greg Kerr09f06de2018-02-16 15:32:07 -080032#include "imageloader/helper_process_proxy.h"
Greg Kerr9944e242017-01-26 15:09:31 -080033
Greg Kerr019d59c2016-11-17 14:28:49 -080034namespace imageloader {
35
36namespace {
37
38// The name of the imageloader manifest file.
39constexpr char kManifestName[] = "imageloader.json";
40// The name of the fingerprint file.
41constexpr char kFingerprintName[] = "manifest.fingerprint";
42// The manifest signature.
Eric Caruso0b79bc82017-03-21 13:44:34 -070043constexpr char kManifestSignatureNamePattern[] = "imageloader.sig.[1-2]";
Xiaochu Liuc2264342017-08-14 16:37:42 -070044// The name of the image file (squashfs).
45constexpr char kImageFileNameSquashFS[] = "image.squash";
46// The name of the image file (ext4).
47constexpr char kImageFileNameExt4[] = "image.ext4";
Greg Kerr019d59c2016-11-17 14:28:49 -080048// The name of the table file.
49constexpr char kTableFileName[] = "table";
Greg Kerr019d59c2016-11-17 14:28:49 -080050
51base::FilePath GetManifestPath(const base::FilePath& component_dir) {
52 return component_dir.Append(kManifestName);
53}
54
Eric Caruso089bbff2017-03-21 11:34:15 -070055bool GetSignaturePath(const base::FilePath& component_dir,
56 base::FilePath* signature_path,
Eric Caruso9588e642017-04-07 15:18:45 -070057 size_t* key_number) {
Eric Caruso089bbff2017-03-21 11:34:15 -070058 DCHECK(signature_path);
59 DCHECK(key_number);
60
Greg Kerr09f06de2018-02-16 15:32:07 -080061 base::FileEnumerator files(component_dir, false,
Eric Caruso089bbff2017-03-21 11:34:15 -070062 base::FileEnumerator::FileType::FILES,
63 kManifestSignatureNamePattern);
64 for (base::FilePath path = files.Next(); !path.empty(); path = files.Next()) {
65 // Extract the key number.
66 std::string key_ext = path.FinalExtension();
67 if (key_ext.empty())
68 continue;
69
Eric Caruso9588e642017-04-07 15:18:45 -070070 size_t ext_number;
71 if (!base::StringToSizeT(key_ext.substr(1), &ext_number))
Eric Caruso089bbff2017-03-21 11:34:15 -070072 continue;
73
74 *signature_path = path;
75 *key_number = ext_number;
76 return true;
77 }
78 return false;
79}
80
81base::FilePath GetSignaturePathForKey(const base::FilePath& component_dir,
Eric Caruso9588e642017-04-07 15:18:45 -070082 size_t key_number) {
Eric Caruso089bbff2017-03-21 11:34:15 -070083 std::string signature_name(kManifestSignatureNamePattern);
84 signature_name =
85 signature_name.substr(0, signature_name.find_last_of('.') + 1);
Hidehiko Abe0deb0542019-08-15 01:56:10 +090086 return component_dir.Append(signature_name +
87 base::NumberToString(key_number));
Greg Kerr019d59c2016-11-17 14:28:49 -080088}
89
90base::FilePath GetFingerprintPath(const base::FilePath& component_dir) {
91 return component_dir.Append(kFingerprintName);
92}
93
94base::FilePath GetTablePath(const base::FilePath& component_dir) {
95 return component_dir.Append(kTableFileName);
96}
97
Xiaochu Liuc2264342017-08-14 16:37:42 -070098base::FilePath GetImagePath(const base::FilePath& component_dir,
Xiaochu Liue61e1d62018-11-12 13:20:09 -080099 FileSystem fs_type) {
100 if (fs_type == FileSystem::kExt4) {
Xiaochu Liuc2264342017-08-14 16:37:42 -0700101 return component_dir.Append(kImageFileNameExt4);
Xiaochu Liue61e1d62018-11-12 13:20:09 -0800102 } else if (fs_type == FileSystem::kSquashFS) {
Xiaochu Liuc2264342017-08-14 16:37:42 -0700103 return component_dir.Append(kImageFileNameSquashFS);
Greg Kerr09f06de2018-02-16 15:32:07 -0800104 } else {
Xiaochu Liuc2264342017-08-14 16:37:42 -0700105 NOTREACHED();
106 return base::FilePath();
107 }
Greg Kerr019d59c2016-11-17 14:28:49 -0800108}
109
110bool WriteFileToDisk(const base::FilePath& path, const std::string& contents) {
Greg Kerr09f06de2018-02-16 15:32:07 -0800111 base::ScopedFD fd(HANDLE_EINTR(open(
112 path.value().c_str(), O_CREAT | O_WRONLY | O_EXCL, kComponentFilePerms)));
Greg Kerr019d59c2016-11-17 14:28:49 -0800113 if (!fd.is_valid()) {
114 PLOG(ERROR) << "Error creating file for " << path.value();
115 return false;
116 }
117
118 base::File file(fd.release());
119 int size = base::checked_cast<int>(contents.size());
120 return file.Write(0, contents.data(), contents.size()) == size;
121}
122
Greg Kerr019d59c2016-11-17 14:28:49 -0800123bool GetAndVerifyTable(const base::FilePath& path,
124 const std::vector<uint8_t>& hash,
125 std::string* out_table) {
126 std::string table;
127 if (!base::ReadFileToStringWithMaxSize(path, &table, kMaximumFilesize)) {
128 return false;
129 }
130
131 std::vector<uint8_t> table_hash(crypto::kSHA256Length);
132 crypto::SHA256HashString(table, table_hash.data(), table_hash.size());
133 if (table_hash != hash) {
134 LOG(ERROR) << "dm-verity table file has the wrong hash.";
135 return false;
136 }
137
138 out_table->assign(table);
139 return true;
140}
141
142} // namespace
143
Eric Caruso089bbff2017-03-21 11:34:15 -0700144Component::Component(const base::FilePath& component_dir, int key_number)
145 : component_dir_(component_dir), key_number_(key_number) {}
Greg Kerr019d59c2016-11-17 14:28:49 -0800146
Eric Carusocbe1c5c2017-03-15 14:21:08 -0700147std::unique_ptr<Component> Component::Create(
Greg Kerr09f06de2018-02-16 15:32:07 -0800148 const base::FilePath& component_dir, const Keys& public_keys) {
Amin Hassani17a185b2021-02-10 12:07:57 -0800149 bool is_official_build = GlobalContext::Current()->IsOfficialBuild();
150
151 // Try to verify signatures in all type of images (signed/test/etc) if they
152 // exists. Only for non-official images, if the signature is missing, ignore
153 // verification otherwise fail.
Eric Caruso089bbff2017-03-21 11:34:15 -0700154 base::FilePath signature_path;
Amin Hassani17a185b2021-02-10 12:07:57 -0800155 size_t key_number = 0;
156 if (GetSignaturePath(component_dir, &signature_path, &key_number)) {
157 if (key_number < 1 || key_number > public_keys.size()) {
158 LOG(ERROR) << "Invalid key number.";
159 return nullptr;
160 }
161 } else if (is_official_build) {
162 LOG(ERROR) << "Could not find manifest signature.";
Eric Caruso089bbff2017-03-21 11:34:15 -0700163 return nullptr;
Amin Hassani17a185b2021-02-10 12:07:57 -0800164 } else {
165 LOG(WARNING) << "Could not find manifest signature, but since this is not "
166 << "an official image, we allow loading the component.";
Eric Caruso0b79bc82017-03-21 13:44:34 -0700167 }
Eric Caruso089bbff2017-03-21 11:34:15 -0700168
169 std::unique_ptr<Component> component(
170 new Component(component_dir, key_number));
Amin Hassani17a185b2021-02-10 12:07:57 -0800171 if (key_number > 0) {
172 if (!component->LoadManifest(public_keys[key_number - 1])) {
173 return nullptr;
174 }
175 } else if (!component->LoadManifestWithoutVerifyingKeyForTestingOnly()) {
Eric Carusocbe1c5c2017-03-15 14:21:08 -0700176 return nullptr;
Amin Hassani17a185b2021-02-10 12:07:57 -0800177 }
Eric Carusocbe1c5c2017-03-15 14:21:08 -0700178 return component;
Greg Kerr019d59c2016-11-17 14:28:49 -0800179}
180
Xiaochu Liue61e1d62018-11-12 13:20:09 -0800181const Manifest& Component::manifest() {
Greg Kerr019d59c2016-11-17 14:28:49 -0800182 return manifest_;
183}
184
Greg Kerr09f06de2018-02-16 15:32:07 -0800185bool Component::Mount(HelperProcessProxy* mounter,
186 const base::FilePath& dest_dir) {
Eric Carusocbe1c5c2017-03-15 14:21:08 -0700187 // Read the table in and verify the hash.
Greg Kerr019d59c2016-11-17 14:28:49 -0800188 std::string table;
Xiaochu Liuc209aab2018-06-19 13:42:15 -0700189 if (!GetAndVerifyTable(GetTablePath(component_dir_), manifest_.table_sha256(),
Greg Kerr019d59c2016-11-17 14:28:49 -0800190 &table)) {
191 LOG(ERROR) << "Could not read and verify dm-verity table.";
192 return false;
193 }
194
Xiaochu Liuc209aab2018-06-19 13:42:15 -0700195 base::FilePath image_path(GetImagePath(component_dir_, manifest_.fs_type()));
Greg Kerr019d59c2016-11-17 14:28:49 -0800196 base::File image(image_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
197 if (!image.IsValid()) {
198 LOG(ERROR) << "Could not open image file.";
199 return false;
200 }
201 base::ScopedFD image_fd(image.TakePlatformFile());
202
Xiaochu Liuc2264342017-08-14 16:37:42 -0700203 return mounter->SendMountCommand(image_fd.get(), dest_dir.value(),
Xiaochu Liuc209aab2018-06-19 13:42:15 -0700204 manifest_.fs_type(), table);
Greg Kerr019d59c2016-11-17 14:28:49 -0800205}
206
Amin Hassani17a185b2021-02-10 12:07:57 -0800207bool Component::LoadManifestWithoutVerifyingKeyForTestingOnly() {
208 if (!base::ReadFileToStringWithMaxSize(GetManifestPath(component_dir_),
209 &manifest_raw_, kMaximumFilesize)) {
210 LOG(ERROR) << "Could not read manifest file.";
211 return false;
212 }
213 return manifest_.ParseManifest(manifest_raw_);
214}
215
Greg Kerr019d59c2016-11-17 14:28:49 -0800216bool Component::LoadManifest(const std::vector<uint8_t>& public_key) {
217 if (!base::ReadFileToStringWithMaxSize(GetManifestPath(component_dir_),
218 &manifest_raw_, kMaximumFilesize)) {
219 LOG(ERROR) << "Could not read manifest file.";
220 return false;
221 }
Eric Caruso089bbff2017-03-21 11:34:15 -0700222 if (!base::ReadFileToStringWithMaxSize(
Greg Kerr09f06de2018-02-16 15:32:07 -0800223 GetSignaturePathForKey(component_dir_, key_number_), &manifest_sig_,
224 kMaximumFilesize)) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800225 LOG(ERROR) << "Could not read signature file.";
226 return false;
227 }
228
229 crypto::SignatureVerifier verifier;
230
231 if (!verifier.VerifyInit(
232 crypto::SignatureVerifier::ECDSA_SHA256,
233 reinterpret_cast<const uint8_t*>(manifest_sig_.data()),
234 base::checked_cast<int>(manifest_sig_.size()), public_key.data(),
235 base::checked_cast<int>(public_key.size()))) {
236 LOG(ERROR) << "Failed to initialize signature verification.";
237 return false;
238 }
239
240 verifier.VerifyUpdate(reinterpret_cast<const uint8_t*>(manifest_raw_.data()),
241 base::checked_cast<int>(manifest_raw_.size()));
242
243 if (!verifier.VerifyFinal()) {
244 LOG(ERROR) << "Manifest failed signature verification.";
245 return false;
246 }
Xiaochu Liuc209aab2018-06-19 13:42:15 -0700247 return manifest_.ParseManifest(manifest_raw_);
Greg Kerr019d59c2016-11-17 14:28:49 -0800248}
249
250bool Component::CopyTo(const base::FilePath& dest_dir) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800251 if (!WriteFileToDisk(GetManifestPath(dest_dir), manifest_raw_) ||
Amin Hassani17a185b2021-02-10 12:07:57 -0800252 (key_number_ > 0 &&
253 !WriteFileToDisk(GetSignaturePathForKey(dest_dir, key_number_),
254 manifest_sig_))) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800255 LOG(ERROR) << "Could not write manifest and signature to disk.";
256 return false;
257 }
258
259 base::FilePath table_src(GetTablePath(component_dir_));
260 base::FilePath table_dest(GetTablePath(dest_dir));
Xiaochu Liuc209aab2018-06-19 13:42:15 -0700261 if (!CopyComponentFile(table_src, table_dest, manifest_.table_sha256())) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800262 LOG(ERROR) << "Could not copy table file.";
263 return false;
264 }
265
Xiaochu Liuc209aab2018-06-19 13:42:15 -0700266 base::FilePath image_src(GetImagePath(component_dir_, manifest_.fs_type()));
267 base::FilePath image_dest(GetImagePath(dest_dir, manifest_.fs_type()));
268 if (!CopyComponentFile(image_src, image_dest, manifest_.image_sha256())) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800269 LOG(ERROR) << "Could not copy image file.";
270 return false;
271 }
272
273 if (!CopyFingerprintFile(component_dir_, dest_dir)) {
274 LOG(ERROR) << "Could not copy manifest.fingerprint file.";
275 return false;
276 }
277
278 return true;
279}
280
281bool Component::CopyComponentFile(const base::FilePath& src,
Eric Caruso355e37c2017-03-15 14:31:41 -0700282 const base::FilePath& dest_path,
283 const std::vector<uint8_t>& expected_hash) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800284 base::File file(src, base::File::FLAG_OPEN | base::File::FLAG_READ);
Eric Caruso355e37c2017-03-15 14:31:41 -0700285 if (!file.IsValid())
286 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800287
288 base::ScopedFD dest(
289 HANDLE_EINTR(open(dest_path.value().c_str(), O_CREAT | O_WRONLY | O_EXCL,
290 kComponentFilePerms)));
Eric Caruso355e37c2017-03-15 14:31:41 -0700291 if (!dest.is_valid())
292 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800293
294 base::File out_file(dest.release());
295 std::unique_ptr<crypto::SecureHash> sha256(
296 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
297
298 std::vector<uint8_t> file_hash(crypto::kSHA256Length);
299 if (!ReadHashAndCopyFile(&file, &file_hash, &out_file)) {
300 LOG(ERROR) << "Failed to read image file.";
301 return false;
302 }
303
304 if (expected_hash != file_hash) {
305 LOG(ERROR) << "Image is corrupt or modified.";
306 return false;
307 }
308 return true;
309}
310
311bool Component::ReadHashAndCopyFile(base::File* file,
312 std::vector<uint8_t>* file_hash,
313 base::File* out_file) {
314 std::unique_ptr<crypto::SecureHash> sha256(
315 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
316 int size = file->GetLength();
Eric Caruso355e37c2017-03-15 14:31:41 -0700317 if (size <= 0)
318 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800319
320 int rv = 0, bytes_read = 0;
321 char buf[4096];
322 do {
323 int remaining = size - bytes_read;
324 int bytes_to_read =
325 std::min(remaining, base::checked_cast<int>(sizeof(buf)));
326
327 rv = file->ReadAtCurrentPos(buf, bytes_to_read);
Greg Kerr09f06de2018-02-16 15:32:07 -0800328 if (rv <= 0)
329 break;
Greg Kerr019d59c2016-11-17 14:28:49 -0800330
331 bytes_read += rv;
332 sha256->Update(buf, rv);
333 if (out_file) {
334 out_file->WriteAtCurrentPos(buf, rv);
335 }
336 } while (bytes_read <= size);
337
338 sha256->Finish(file_hash->data(), file_hash->size());
339 return bytes_read == size;
340}
341
342bool Component::CopyFingerprintFile(const base::FilePath& src,
343 const base::FilePath& dest) {
344 base::FilePath fingerprint_path(GetFingerprintPath(src));
345 if (base::PathExists(fingerprint_path)) {
346 std::string fingerprint_contents;
347 if (!base::ReadFileToStringWithMaxSize(
348 fingerprint_path, &fingerprint_contents, kMaximumFilesize)) {
349 return false;
350 }
351
Eric Caruso355e37c2017-03-15 14:31:41 -0700352 if (!IsValidFingerprintFile(fingerprint_contents))
353 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800354
355 if (!WriteFileToDisk(GetFingerprintPath(dest), fingerprint_contents)) {
356 return false;
357 }
358 }
359 return true;
360}
361
362// The client inserts manifest.fingerprint into components after unpacking the
363// CRX. The file is used for delta updates. Since Chrome OS doesn't rely on it
Greg Kerr04c1cee2020-10-15 14:08:44 +0000364// for security of the disk image, we are fine with validating the contents
Greg Kerr019d59c2016-11-17 14:28:49 -0800365// and then preserving the unsigned file.
366bool Component::IsValidFingerprintFile(const std::string& contents) {
367 return contents.size() <= 256 &&
368 std::find_if_not(contents.begin(), contents.end(), [](char ch) {
369 return base::IsAsciiAlpha(ch) || base::IsAsciiDigit(ch) || ch == '.';
370 }) == contents.end();
371}
372
373} // namespace imageloader