feat: add support for validating asar archives on macOS (#30667)
* feat: add support for validating asar archives on macOS * chore: fix lint * chore: update as per feedback * feat: switch implementation to asar integrity hash checks * feat: make ranged requests work with the asar file validator DataSourceFilter * chore: fix lint * chore: fix missing log include on non-darwin * fix: do not pull block size out of missing optional * fix: match ValidateOrDie symbol on non-darwin * chore: fix up asar specs by repacking archives * fix: maintain integrity chain, do not load file integrity if header integrity was not loaded * debug test * Update node-spec.ts * fix: initialize header_validated_ * chore: update PR per feedback * chore: update per feedback * build: use final asar module * Update fuses.json5
This commit is contained in:
parent
fcad531f2e
commit
57d088517c
35 changed files with 705 additions and 43 deletions
|
@ -18,6 +18,8 @@
|
|||
#include "base/task/post_task.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "base/values.h"
|
||||
#include "electron/fuses.h"
|
||||
#include "shell/common/asar/asar_util.h"
|
||||
#include "shell/common/asar/scoped_temporary_file.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
|
@ -95,6 +97,7 @@ bool GetNodeFromPath(std::string path,
|
|||
|
||||
bool FillFileInfoWithNode(Archive::FileInfo* info,
|
||||
uint32_t header_size,
|
||||
bool load_integrity,
|
||||
const base::DictionaryValue* node) {
|
||||
int size;
|
||||
if (!node->GetInteger("size", &size))
|
||||
|
@ -113,6 +116,42 @@ bool FillFileInfoWithNode(Archive::FileInfo* info,
|
|||
|
||||
node->GetBoolean("executable", &info->executable);
|
||||
|
||||
#if defined(OS_MAC)
|
||||
if (load_integrity &&
|
||||
electron::fuses::IsEmbeddedAsarIntegrityValidationEnabled()) {
|
||||
const base::DictionaryValue* integrity;
|
||||
if (node->GetDictionary("integrity", &integrity)) {
|
||||
IntegrityPayload integrity_payload;
|
||||
std::string algorithm;
|
||||
const base::ListValue* blocks;
|
||||
int block_size;
|
||||
if (integrity->GetString("algorithm", &algorithm) &&
|
||||
integrity->GetString("hash", &integrity_payload.hash) &&
|
||||
integrity->GetInteger("blockSize", &block_size) &&
|
||||
integrity->GetList("blocks", &blocks) && block_size > 0) {
|
||||
integrity_payload.block_size = static_cast<uint32_t>(block_size);
|
||||
for (size_t i = 0; i < blocks->GetSize(); i++) {
|
||||
std::string block;
|
||||
if (!blocks->GetString(i, &block)) {
|
||||
LOG(FATAL)
|
||||
<< "Invalid block integrity value for file in ASAR archive";
|
||||
}
|
||||
integrity_payload.blocks.push_back(block);
|
||||
}
|
||||
if (algorithm == "SHA256") {
|
||||
integrity_payload.algorithm = HashAlgorithm::SHA256;
|
||||
info->integrity = std::move(integrity_payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!info->integrity.has_value()) {
|
||||
LOG(FATAL) << "Failed to read integrity for file in ASAR archive";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -191,6 +230,32 @@ bool Archive::Init() {
|
|||
return false;
|
||||
}
|
||||
|
||||
#if defined(OS_MAC)
|
||||
// Validate header signature if required and possible
|
||||
if (electron::fuses::IsEmbeddedAsarIntegrityValidationEnabled() &&
|
||||
RelativePath().has_value()) {
|
||||
absl::optional<IntegrityPayload> integrity = HeaderIntegrity();
|
||||
if (!integrity.has_value()) {
|
||||
LOG(FATAL) << "Failed to get integrity for validatable asar archive: "
|
||||
<< RelativePath().value();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Currently we only support the sha256 algorithm, we can add support for
|
||||
// more below ensure we read them in preference order from most secure to
|
||||
// least
|
||||
if (integrity.value().algorithm != HashAlgorithm::NONE) {
|
||||
ValidateIntegrityOrDie(header.c_str(), header.length(),
|
||||
integrity.value());
|
||||
} else {
|
||||
LOG(FATAL) << "No eligible hash for validatable asar archive: "
|
||||
<< RelativePath().value();
|
||||
}
|
||||
|
||||
header_validated_ = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
absl::optional<base::Value> value = base::JSONReader::Read(header);
|
||||
if (!value || !value->is_dict()) {
|
||||
LOG(ERROR) << "Failed to parse header";
|
||||
|
@ -203,6 +268,16 @@ bool Archive::Init() {
|
|||
return true;
|
||||
}
|
||||
|
||||
#if !defined(OS_MAC)
|
||||
absl::optional<IntegrityPayload> Archive::HeaderIntegrity() const {
|
||||
return absl::optional<IntegrityPayload>();
|
||||
}
|
||||
|
||||
absl::optional<base::FilePath> Archive::RelativePath() const {
|
||||
return absl::optional<base::FilePath>();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Archive::GetFileInfo(const base::FilePath& path, FileInfo* info) const {
|
||||
if (!header_)
|
||||
return false;
|
||||
|
@ -215,7 +290,7 @@ bool Archive::GetFileInfo(const base::FilePath& path, FileInfo* info) const {
|
|||
if (node->GetString("link", &link))
|
||||
return GetFileInfo(base::FilePath::FromUTF8Unsafe(link), info);
|
||||
|
||||
return FillFileInfoWithNode(info, header_size_, node);
|
||||
return FillFileInfoWithNode(info, header_size_, header_validated_, node);
|
||||
}
|
||||
|
||||
bool Archive::Stat(const base::FilePath& path, Stats* stats) const {
|
||||
|
@ -238,7 +313,7 @@ bool Archive::Stat(const base::FilePath& path, Stats* stats) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
return FillFileInfoWithNode(stats, header_size_, node);
|
||||
return FillFileInfoWithNode(stats, header_size_, header_validated_, node);
|
||||
}
|
||||
|
||||
bool Archive::Readdir(const base::FilePath& path,
|
||||
|
@ -304,7 +379,8 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
|
|||
|
||||
auto temp_file = std::make_unique<ScopedTemporaryFile>();
|
||||
base::FilePath::StringType ext = path.Extension();
|
||||
if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size))
|
||||
if (!temp_file->InitFromFile(&file_, ext, info.offset, info.size,
|
||||
info.integrity))
|
||||
return false;
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
|
@ -319,7 +395,7 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
|
|||
return true;
|
||||
}
|
||||
|
||||
int Archive::GetFD() const {
|
||||
int Archive::GetUnsafeFD() const {
|
||||
return fd_;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue