blob: 1f1327dad9df4fca57fed85ed3cf42da46adfe75 [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
14#include <base/files/file.h>
Eric Caruso089bbff2017-03-21 11:34:15 -070015#include <base/files/file_enumerator.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080016#include <base/files/file_path.h>
17#include <base/files/file_util.h>
18#include <base/files/scoped_file.h>
19#include <base/json/json_string_value_serializer.h>
20#include <base/logging.h>
21#include <base/numerics/safe_conversions.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080022#include <base/posix/eintr_wrapper.h>
Eric Caruso089bbff2017-03-21 11:34:15 -070023#include <base/strings/string_number_conversions.h>
Greg Kerr019d59c2016-11-17 14:28:49 -080024#include <base/strings/string_util.h>
25#include <crypto/secure_hash.h>
26#include <crypto/sha2.h>
27#include <crypto/signature_verifier.h>
28
Greg Kerr09f06de2018-02-16 15:32:07 -080029#include "imageloader/helper_process_proxy.h"
Greg Kerr9944e242017-01-26 15:09:31 -080030
Greg Kerr019d59c2016-11-17 14:28:49 -080031namespace imageloader {
32
33namespace {
34
35// The name of the imageloader manifest file.
36constexpr char kManifestName[] = "imageloader.json";
37// The name of the fingerprint file.
38constexpr char kFingerprintName[] = "manifest.fingerprint";
39// The manifest signature.
Eric Caruso0b79bc82017-03-21 13:44:34 -070040constexpr char kManifestSignatureNamePattern[] = "imageloader.sig.[1-2]";
Greg Kerr019d59c2016-11-17 14:28:49 -080041// The current version of the manifest file.
42constexpr int kCurrentManifestVersion = 1;
43// The name of the version field in the manifest.
44constexpr char kManifestVersionField[] = "manifest-version";
45// The name of the component version field in the manifest.
46constexpr char kVersionField[] = "version";
47// The name of the field containing the image hash.
48constexpr char kImageHashField[] = "image-sha256-hash";
Xiaochu Liu1e5dc142017-10-11 17:33:33 -070049// The name of the bool field indicating whether component is removable.
50constexpr char kIsRemovableField[] = "is-removable";
Eric Caruso26a91442017-10-25 16:05:40 -070051// The name of the metadata field.
52constexpr char kMetadataField[] = "metadata";
Xiaochu Liuc2264342017-08-14 16:37:42 -070053// The name of the image file (squashfs).
54constexpr char kImageFileNameSquashFS[] = "image.squash";
55// The name of the image file (ext4).
56constexpr char kImageFileNameExt4[] = "image.ext4";
Greg Kerr019d59c2016-11-17 14:28:49 -080057// The name of the field containing the table hash.
58constexpr char kTableHashField[] = "table-sha256-hash";
Xiaochu Liuc2264342017-08-14 16:37:42 -070059// The name of the optional field containing the file system type.
60constexpr char kFSType[] = "fs-type";
Greg Kerr019d59c2016-11-17 14:28:49 -080061// The name of the table file.
62constexpr char kTableFileName[] = "table";
63// The maximum size of any file to read into memory.
64constexpr size_t kMaximumFilesize = 4096 * 10;
65
66base::FilePath GetManifestPath(const base::FilePath& component_dir) {
67 return component_dir.Append(kManifestName);
68}
69
Eric Caruso089bbff2017-03-21 11:34:15 -070070bool GetSignaturePath(const base::FilePath& component_dir,
71 base::FilePath* signature_path,
Eric Caruso9588e642017-04-07 15:18:45 -070072 size_t* key_number) {
Eric Caruso089bbff2017-03-21 11:34:15 -070073 DCHECK(signature_path);
74 DCHECK(key_number);
75
Greg Kerr09f06de2018-02-16 15:32:07 -080076 base::FileEnumerator files(component_dir, false,
Eric Caruso089bbff2017-03-21 11:34:15 -070077 base::FileEnumerator::FileType::FILES,
78 kManifestSignatureNamePattern);
79 for (base::FilePath path = files.Next(); !path.empty(); path = files.Next()) {
80 // Extract the key number.
81 std::string key_ext = path.FinalExtension();
82 if (key_ext.empty())
83 continue;
84
Eric Caruso9588e642017-04-07 15:18:45 -070085 size_t ext_number;
86 if (!base::StringToSizeT(key_ext.substr(1), &ext_number))
Eric Caruso089bbff2017-03-21 11:34:15 -070087 continue;
88
89 *signature_path = path;
90 *key_number = ext_number;
91 return true;
92 }
93 return false;
94}
95
96base::FilePath GetSignaturePathForKey(const base::FilePath& component_dir,
Eric Caruso9588e642017-04-07 15:18:45 -070097 size_t key_number) {
Eric Caruso089bbff2017-03-21 11:34:15 -070098 std::string signature_name(kManifestSignatureNamePattern);
99 signature_name =
100 signature_name.substr(0, signature_name.find_last_of('.') + 1);
Eric Caruso9588e642017-04-07 15:18:45 -0700101 return component_dir.Append(signature_name + base::SizeTToString(key_number));
Greg Kerr019d59c2016-11-17 14:28:49 -0800102}
103
104base::FilePath GetFingerprintPath(const base::FilePath& component_dir) {
105 return component_dir.Append(kFingerprintName);
106}
107
108base::FilePath GetTablePath(const base::FilePath& component_dir) {
109 return component_dir.Append(kTableFileName);
110}
111
Xiaochu Liuc2264342017-08-14 16:37:42 -0700112base::FilePath GetImagePath(const base::FilePath& component_dir,
113 FileSystem fs_type) {
Greg Kerr09f06de2018-02-16 15:32:07 -0800114 if (fs_type == FileSystem::kExt4) {
Xiaochu Liuc2264342017-08-14 16:37:42 -0700115 return component_dir.Append(kImageFileNameExt4);
Greg Kerr09f06de2018-02-16 15:32:07 -0800116 } else if (fs_type == FileSystem::kSquashFS) {
Xiaochu Liuc2264342017-08-14 16:37:42 -0700117 return component_dir.Append(kImageFileNameSquashFS);
Greg Kerr09f06de2018-02-16 15:32:07 -0800118 } else {
Xiaochu Liuc2264342017-08-14 16:37:42 -0700119 NOTREACHED();
120 return base::FilePath();
121 }
Greg Kerr019d59c2016-11-17 14:28:49 -0800122}
123
124bool WriteFileToDisk(const base::FilePath& path, const std::string& contents) {
Greg Kerr09f06de2018-02-16 15:32:07 -0800125 base::ScopedFD fd(HANDLE_EINTR(open(
126 path.value().c_str(), O_CREAT | O_WRONLY | O_EXCL, kComponentFilePerms)));
Greg Kerr019d59c2016-11-17 14:28:49 -0800127 if (!fd.is_valid()) {
128 PLOG(ERROR) << "Error creating file for " << path.value();
129 return false;
130 }
131
132 base::File file(fd.release());
133 int size = base::checked_cast<int>(contents.size());
134 return file.Write(0, contents.data(), contents.size()) == size;
135}
136
137bool GetSHA256FromString(const std::string& hash_str,
138 std::vector<uint8_t>* bytes) {
Eric Caruso355e37c2017-03-15 14:31:41 -0700139 if (!base::HexStringToBytes(hash_str, bytes))
140 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800141 return bytes->size() == crypto::kSHA256Length;
142}
143
144bool GetAndVerifyTable(const base::FilePath& path,
145 const std::vector<uint8_t>& hash,
146 std::string* out_table) {
147 std::string table;
148 if (!base::ReadFileToStringWithMaxSize(path, &table, kMaximumFilesize)) {
149 return false;
150 }
151
152 std::vector<uint8_t> table_hash(crypto::kSHA256Length);
153 crypto::SHA256HashString(table, table_hash.data(), table_hash.size());
154 if (table_hash != hash) {
155 LOG(ERROR) << "dm-verity table file has the wrong hash.";
156 return false;
157 }
158
159 out_table->assign(table);
160 return true;
161}
162
Eric Caruso26a91442017-10-25 16:05:40 -0700163// Ensure the metadata entry is a dictionary mapping strings to strings and
164// parse it into |out_metadata| and return true if so.
165bool ParseMetadata(const base::Value* metadata_element,
166 std::map<std::string, std::string>* out_metadata) {
167 DCHECK(out_metadata);
168
169 const base::DictionaryValue* metadata_dict = nullptr;
170 if (!metadata_element->GetAsDictionary(&metadata_dict))
171 return false;
172
173 base::DictionaryValue::Iterator it(*metadata_dict);
174 for (; !it.IsAtEnd(); it.Advance()) {
175 std::string parsed_value;
176 if (!it.value().GetAsString(&parsed_value)) {
177 LOG(ERROR) << "Key \"" << it.key() << "\" did not map to string value";
178 return false;
179 }
180
181 (*out_metadata)[it.key()] = std::move(parsed_value);
182 }
183
184 return true;
185}
186
Greg Kerr019d59c2016-11-17 14:28:49 -0800187} // namespace
188
Eric Caruso089bbff2017-03-21 11:34:15 -0700189Component::Component(const base::FilePath& component_dir, int key_number)
190 : component_dir_(component_dir), key_number_(key_number) {}
Greg Kerr019d59c2016-11-17 14:28:49 -0800191
Eric Carusocbe1c5c2017-03-15 14:21:08 -0700192std::unique_ptr<Component> Component::Create(
Greg Kerr09f06de2018-02-16 15:32:07 -0800193 const base::FilePath& component_dir, const Keys& public_keys) {
Eric Caruso089bbff2017-03-21 11:34:15 -0700194 base::FilePath signature_path;
Eric Caruso9588e642017-04-07 15:18:45 -0700195 size_t key_number;
Eric Caruso089bbff2017-03-21 11:34:15 -0700196 if (!GetSignaturePath(component_dir, &signature_path, &key_number)) {
197 LOG(ERROR) << "Could not find manifest signature";
198 return nullptr;
199 }
Eric Caruso0b79bc82017-03-21 13:44:34 -0700200 if (key_number < 1 || key_number > public_keys.size()) {
201 LOG(ERROR) << "Invalid key number";
202 return nullptr;
203 }
Eric Caruso089bbff2017-03-21 11:34:15 -0700204
205 std::unique_ptr<Component> component(
206 new Component(component_dir, key_number));
Eric Caruso0b79bc82017-03-21 13:44:34 -0700207 if (!component->LoadManifest(public_keys[key_number - 1]))
Eric Carusocbe1c5c2017-03-15 14:21:08 -0700208 return nullptr;
209 return component;
Greg Kerr019d59c2016-11-17 14:28:49 -0800210}
211
212const Component::Manifest& Component::manifest() {
Greg Kerr019d59c2016-11-17 14:28:49 -0800213 return manifest_;
214}
215
Greg Kerr09f06de2018-02-16 15:32:07 -0800216bool Component::Mount(HelperProcessProxy* mounter,
217 const base::FilePath& dest_dir) {
Eric Carusocbe1c5c2017-03-15 14:21:08 -0700218 // Read the table in and verify the hash.
Greg Kerr019d59c2016-11-17 14:28:49 -0800219 std::string table;
220 if (!GetAndVerifyTable(GetTablePath(component_dir_), manifest_.table_sha256,
221 &table)) {
222 LOG(ERROR) << "Could not read and verify dm-verity table.";
223 return false;
224 }
225
Xiaochu Liuc2264342017-08-14 16:37:42 -0700226 base::FilePath image_path(GetImagePath(component_dir_, manifest_.fs_type));
Greg Kerr019d59c2016-11-17 14:28:49 -0800227 base::File image(image_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
228 if (!image.IsValid()) {
229 LOG(ERROR) << "Could not open image file.";
230 return false;
231 }
232 base::ScopedFD image_fd(image.TakePlatformFile());
233
Xiaochu Liuc2264342017-08-14 16:37:42 -0700234 return mounter->SendMountCommand(image_fd.get(), dest_dir.value(),
235 manifest_.fs_type, table);
Greg Kerr019d59c2016-11-17 14:28:49 -0800236}
237
238bool Component::ParseManifest() {
239 // Now deserialize the manifest json and read out the rest of the component.
240 int error_code;
241 std::string error_message;
242 JSONStringValueDeserializer deserializer(manifest_raw_);
243 std::unique_ptr<base::Value> value =
244 deserializer.Deserialize(&error_code, &error_message);
245
246 if (!value) {
247 LOG(ERROR) << "Could not deserialize the manifest file. Error "
248 << error_code << ": " << error_message;
249 return false;
250 }
251
252 base::DictionaryValue* manifest_dict = nullptr;
253 if (!value->GetAsDictionary(&manifest_dict)) {
254 LOG(ERROR) << "Could not parse manifest file as JSON.";
255 return false;
256 }
257
258 // This will have to be changed if the manifest version is bumped.
259 int version;
260 if (!manifest_dict->GetInteger(kManifestVersionField, &version)) {
261 LOG(ERROR) << "Could not parse manifest version field from manifest.";
262 return false;
263 }
264 if (version != kCurrentManifestVersion) {
265 LOG(ERROR) << "Unsupported version of the manifest.";
266 return false;
267 }
268 manifest_.manifest_version = version;
269
270 std::string image_hash_str;
271 if (!manifest_dict->GetString(kImageHashField, &image_hash_str)) {
272 LOG(ERROR) << "Could not parse image hash from manifest.";
273 return false;
274 }
275
276 if (!GetSHA256FromString(image_hash_str, &(manifest_.image_sha256))) {
277 LOG(ERROR) << "Could not convert image hash to bytes.";
278 return false;
279 }
280
281 std::string table_hash_str;
282 if (!manifest_dict->GetString(kTableHashField, &table_hash_str)) {
283 LOG(ERROR) << "Could not parse table hash from manifest.";
284 return false;
285 }
286
287 if (!GetSHA256FromString(table_hash_str, &(manifest_.table_sha256))) {
288 LOG(ERROR) << "Could not convert table hash to bytes.";
289 return false;
290 }
291
292 if (!manifest_dict->GetString(kVersionField, &(manifest_.version))) {
293 LOG(ERROR) << "Could not parse component version from manifest.";
294 return false;
295 }
296
Xiaochu Liuc2264342017-08-14 16:37:42 -0700297 // The fs_type field is optional, and squashfs by default.
298 manifest_.fs_type = FileSystem::kSquashFS;
299 std::string fs_type;
300 if (manifest_dict->GetString(kFSType, &fs_type)) {
301 if (fs_type == "ext4") {
302 manifest_.fs_type = FileSystem::kExt4;
303 } else if (fs_type == "squashfs") {
304 manifest_.fs_type = FileSystem::kSquashFS;
305 } else {
306 LOG(ERROR) << "Unsupported file system type: " << fs_type;
307 return false;
308 }
309 }
310
Xiaochu Liu1e5dc142017-10-11 17:33:33 -0700311 if (!manifest_dict->GetBoolean(kIsRemovableField,
312 &(manifest_.is_removable))) {
313 // If is_removable field does not exist, by default it is false.
314 manifest_.is_removable = false;
315 }
316
Eric Caruso26a91442017-10-25 16:05:40 -0700317 // Copy out the metadata, if it's there.
318 const base::Value* metadata = nullptr;
319 if (manifest_dict->Get(kMetadataField, &metadata)) {
320 if (!ParseMetadata(metadata, &(manifest_.metadata))) {
321 LOG(ERROR) << "Manifest metadata was malformed";
322 return false;
323 }
324 }
325
Greg Kerr019d59c2016-11-17 14:28:49 -0800326 return true;
327}
328
329bool Component::LoadManifest(const std::vector<uint8_t>& public_key) {
330 if (!base::ReadFileToStringWithMaxSize(GetManifestPath(component_dir_),
331 &manifest_raw_, kMaximumFilesize)) {
332 LOG(ERROR) << "Could not read manifest file.";
333 return false;
334 }
Eric Caruso089bbff2017-03-21 11:34:15 -0700335 if (!base::ReadFileToStringWithMaxSize(
Greg Kerr09f06de2018-02-16 15:32:07 -0800336 GetSignaturePathForKey(component_dir_, key_number_), &manifest_sig_,
337 kMaximumFilesize)) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800338 LOG(ERROR) << "Could not read signature file.";
339 return false;
340 }
341
342 crypto::SignatureVerifier verifier;
343
344 if (!verifier.VerifyInit(
345 crypto::SignatureVerifier::ECDSA_SHA256,
346 reinterpret_cast<const uint8_t*>(manifest_sig_.data()),
347 base::checked_cast<int>(manifest_sig_.size()), public_key.data(),
348 base::checked_cast<int>(public_key.size()))) {
349 LOG(ERROR) << "Failed to initialize signature verification.";
350 return false;
351 }
352
353 verifier.VerifyUpdate(reinterpret_cast<const uint8_t*>(manifest_raw_.data()),
354 base::checked_cast<int>(manifest_raw_.size()));
355
356 if (!verifier.VerifyFinal()) {
357 LOG(ERROR) << "Manifest failed signature verification.";
358 return false;
359 }
360 return ParseManifest();
361}
362
363bool Component::CopyTo(const base::FilePath& dest_dir) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800364 if (!WriteFileToDisk(GetManifestPath(dest_dir), manifest_raw_) ||
Eric Caruso089bbff2017-03-21 11:34:15 -0700365 !WriteFileToDisk(GetSignaturePathForKey(dest_dir, key_number_),
Greg Kerr09f06de2018-02-16 15:32:07 -0800366 manifest_sig_)) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800367 LOG(ERROR) << "Could not write manifest and signature to disk.";
368 return false;
369 }
370
371 base::FilePath table_src(GetTablePath(component_dir_));
372 base::FilePath table_dest(GetTablePath(dest_dir));
373 if (!CopyComponentFile(table_src, table_dest, manifest_.table_sha256)) {
374 LOG(ERROR) << "Could not copy table file.";
375 return false;
376 }
377
Xiaochu Liuc2264342017-08-14 16:37:42 -0700378 base::FilePath image_src(GetImagePath(component_dir_, manifest_.fs_type));
379 base::FilePath image_dest(GetImagePath(dest_dir, manifest_.fs_type));
Greg Kerr019d59c2016-11-17 14:28:49 -0800380 if (!CopyComponentFile(image_src, image_dest, manifest_.image_sha256)) {
381 LOG(ERROR) << "Could not copy image file.";
382 return false;
383 }
384
385 if (!CopyFingerprintFile(component_dir_, dest_dir)) {
386 LOG(ERROR) << "Could not copy manifest.fingerprint file.";
387 return false;
388 }
389
390 return true;
391}
392
393bool Component::CopyComponentFile(const base::FilePath& src,
Eric Caruso355e37c2017-03-15 14:31:41 -0700394 const base::FilePath& dest_path,
395 const std::vector<uint8_t>& expected_hash) {
Greg Kerr019d59c2016-11-17 14:28:49 -0800396 base::File file(src, base::File::FLAG_OPEN | base::File::FLAG_READ);
Eric Caruso355e37c2017-03-15 14:31:41 -0700397 if (!file.IsValid())
398 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800399
400 base::ScopedFD dest(
401 HANDLE_EINTR(open(dest_path.value().c_str(), O_CREAT | O_WRONLY | O_EXCL,
402 kComponentFilePerms)));
Eric Caruso355e37c2017-03-15 14:31:41 -0700403 if (!dest.is_valid())
404 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800405
406 base::File out_file(dest.release());
407 std::unique_ptr<crypto::SecureHash> sha256(
408 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
409
410 std::vector<uint8_t> file_hash(crypto::kSHA256Length);
411 if (!ReadHashAndCopyFile(&file, &file_hash, &out_file)) {
412 LOG(ERROR) << "Failed to read image file.";
413 return false;
414 }
415
416 if (expected_hash != file_hash) {
417 LOG(ERROR) << "Image is corrupt or modified.";
418 return false;
419 }
420 return true;
421}
422
423bool Component::ReadHashAndCopyFile(base::File* file,
424 std::vector<uint8_t>* file_hash,
425 base::File* out_file) {
426 std::unique_ptr<crypto::SecureHash> sha256(
427 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
428 int size = file->GetLength();
Eric Caruso355e37c2017-03-15 14:31:41 -0700429 if (size <= 0)
430 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800431
432 int rv = 0, bytes_read = 0;
433 char buf[4096];
434 do {
435 int remaining = size - bytes_read;
436 int bytes_to_read =
437 std::min(remaining, base::checked_cast<int>(sizeof(buf)));
438
439 rv = file->ReadAtCurrentPos(buf, bytes_to_read);
Greg Kerr09f06de2018-02-16 15:32:07 -0800440 if (rv <= 0)
441 break;
Greg Kerr019d59c2016-11-17 14:28:49 -0800442
443 bytes_read += rv;
444 sha256->Update(buf, rv);
445 if (out_file) {
446 out_file->WriteAtCurrentPos(buf, rv);
447 }
448 } while (bytes_read <= size);
449
450 sha256->Finish(file_hash->data(), file_hash->size());
451 return bytes_read == size;
452}
453
454bool Component::CopyFingerprintFile(const base::FilePath& src,
455 const base::FilePath& dest) {
456 base::FilePath fingerprint_path(GetFingerprintPath(src));
457 if (base::PathExists(fingerprint_path)) {
458 std::string fingerprint_contents;
459 if (!base::ReadFileToStringWithMaxSize(
460 fingerprint_path, &fingerprint_contents, kMaximumFilesize)) {
461 return false;
462 }
463
Eric Caruso355e37c2017-03-15 14:31:41 -0700464 if (!IsValidFingerprintFile(fingerprint_contents))
465 return false;
Greg Kerr019d59c2016-11-17 14:28:49 -0800466
467 if (!WriteFileToDisk(GetFingerprintPath(dest), fingerprint_contents)) {
468 return false;
469 }
470 }
471 return true;
472}
473
474// The client inserts manifest.fingerprint into components after unpacking the
475// CRX. The file is used for delta updates. Since Chrome OS doesn't rely on it
476// for security of the disk image, we are fine with sanity checking the contents
477// and then preserving the unsigned file.
478bool Component::IsValidFingerprintFile(const std::string& contents) {
479 return contents.size() <= 256 &&
480 std::find_if_not(contents.begin(), contents.end(), [](char ch) {
481 return base::IsAsciiAlpha(ch) || base::IsAsciiDigit(ch) || ch == '.';
482 }) == contents.end();
483}
484
485} // namespace imageloader