| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "biod/cros_fp_firmware.h" |
| |
| #include <string> |
| |
| #include <base/logging.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/memory_mapped_file.h> |
| |
| #include <chromeos/ec/ec_commands.h> |
| #include <fmap.h> |
| |
| namespace { |
| |
| constexpr char kFmapRoIdLabel[] = "RO_FRID"; |
| constexpr char kFmapRwIdLabel[] = "RW_FWID"; |
| |
| bool DecodeVersionFromArea(const base::MemoryMappedFile& image, |
| const struct fmap* fmap, |
| const char* area_name, |
| std::string* ver) { |
| DCHECK(fmap != nullptr); |
| DCHECK(area_name != nullptr); |
| DCHECK(ver != nullptr); |
| |
| const auto area = fmap_find_area(fmap, area_name); |
| if (area == nullptr) { |
| LOG(ERROR) << "Failed to find FMAP area " << area_name << "."; |
| return false; |
| } |
| if ((area->offset + area->size) > fmap->size) { |
| LOG(ERROR) << "FMAP area " << area_name << " has offset " << area->offset |
| << " with size " << area->size |
| << ", which spans outside the firmware image, of size " |
| << fmap->size << "."; |
| return false; |
| } |
| |
| // area->size can be larger than the printable characters in the buffer |
| // std::string(char*, size_t) constructor will not stop at first \0 |
| const char* str = |
| reinterpret_cast<const char*>(&(image.data()[area->offset])); |
| *ver = std::string(str, strnlen(str, area->size)); |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace biod { |
| |
| CrosFpFirmware::CrosFpFirmware(const base::FilePath& image_path) |
| : path_(image_path) { |
| DecodeVersionFromFile(); |
| } |
| |
| base::FilePath CrosFpFirmware::GetPath() const { |
| return path_; |
| } |
| |
| bool CrosFpFirmware::IsValid() const { |
| return status_ == Status::kOk; |
| } |
| |
| CrosFpFirmware::Status CrosFpFirmware::GetStatus() const { |
| return status_; |
| } |
| |
| std::string CrosFpFirmware::GetStatusString() const { |
| return StatusToString(GetStatus()); |
| } |
| |
| const CrosFpFirmware::ImageVersion& CrosFpFirmware::GetVersion() const { |
| return version_; |
| } |
| |
| std::string CrosFpFirmware::StatusToString(Status status) { |
| switch (status) { |
| case CrosFpFirmware::Status::kUninitialized: |
| return "Not initialized"; |
| case CrosFpFirmware::Status::kNotFound: |
| return "Firmware file does not exist."; |
| case CrosFpFirmware::Status::kBadFmap: |
| return "Firmware file has bad FMAP."; |
| case CrosFpFirmware::Status::kOpenError: |
| return "Failed to open firmware file version."; |
| case CrosFpFirmware::Status::kOk: |
| return "Firmware file is okay."; |
| } |
| |
| NOTREACHED(); |
| return ""; |
| } |
| |
| void CrosFpFirmware::DecodeVersionFromFile() { |
| if (!base::PathExists(path_) || base::DirectoryExists(path_)) { |
| LOG(ERROR) << "Failed to find firmware file '" << path_.value() << "'."; |
| status_ = Status::kNotFound; |
| return; |
| } |
| |
| base::MemoryMappedFile image; |
| if (!image.Initialize(path_) || !image.IsValid()) { |
| PLOG(ERROR) << "Failed to open firmware file '" << path_.value() << "'."; |
| status_ = Status::kOpenError; |
| return; |
| } |
| |
| auto fmap_offset = fmap_find(image.data(), image.length()); |
| if (fmap_offset < 0 || fmap_offset >= image.length()) { |
| LOG(ERROR) << "Failed to find FMAP inside firmware file '" << path_.value() |
| << "'."; |
| status_ = Status::kBadFmap; |
| return; |
| } |
| DLOG(INFO) << "FMAP signature found at offset " << fmap_offset << "."; |
| |
| base::MemoryMappedFile image_fmap_aligned; |
| const base::MemoryMappedFile::Region image_fmap_aligned_region = { |
| .offset = fmap_offset, |
| .size = image.length() - fmap_offset, |
| }; |
| // MemoryMappedFile doesn't allow sharing the same open file with another |
| // MemoryMappedFile nor does it allow mapping multiple file regions, |
| // so we need to open the same file again. |
| if (!image_fmap_aligned.Initialize( |
| base::File(path_, base::File::FLAG_OPEN | base::File::FLAG_READ), |
| image_fmap_aligned_region) || |
| !image_fmap_aligned.IsValid()) { |
| PLOG(ERROR) << "Failed to open firmware file's fmap region '" |
| << path_.value() << "'."; |
| status_ = Status::kOpenError; |
| return; |
| } |
| const auto fmap = |
| reinterpret_cast<const struct fmap*>(image_fmap_aligned.data()); |
| |
| // The firmware file's self reported size should not be larger |
| // than the file size. |
| if (fmap->size > image.length()) { |
| LOG(ERROR) << "FMAP reported an image size of " << fmap->size |
| << ", which is larger than the entire file size, " |
| << image.length() << ", for '" << path_.value() << "'."; |
| status_ = Status::kBadFmap; |
| return; |
| } |
| |
| if (!DecodeVersionFromArea(image, fmap, kFmapRoIdLabel, |
| &version_.ro_version)) { |
| status_ = Status::kBadFmap; |
| return; |
| } |
| if (!DecodeVersionFromArea(image, fmap, kFmapRwIdLabel, |
| &version_.rw_version)) { |
| status_ = Status::kBadFmap; |
| return; |
| } |
| status_ = Status::kOk; |
| } |
| |
| } // namespace biod |