From aed49bda753bb3f3c26de03869f308066d2129e9 Mon Sep 17 00:00:00 2001 From: Ying Yu Date: Tue, 21 Jul 2026 20:32:43 +0000 Subject: [PATCH] Load SSL root certificates to avoid runtime wipe crashes (#11413) This pull request loads the SSL root certificates at startup directly into the bssl trust store. It also safely guards TryLoadCert against triggering NOTREACHED() if a certificate file fails to load at runtime. This is a new issue with C26/C27 because we switched to 2-slot, one system image, and one installation slot. When the app is updated from system image and runs the package from the installation slot, right before it downloads an update, it'll wipe the installation slot including its own certs, which caused the issue. But before C26, there're 3 slots in total, and the updater doesn't wipe its own slot. On the other hand, the core content (cobalt_shell.pak) and UI fonts (SkFileMemoryChunkStream) are safe from this wipe because they use base::MemoryMappedFile and hold a persistent FILE* handle, which the OS honors natively. Issue: 535681824, 533843031 (cherry picked from commit f5d2add8af72bb9f1eb4f5f5bfd5f64a6d740db1) --- .../trust_store_in_memory_starboard.cc | 137 +++++------------- .../trust_store_in_memory_starboard.h | 22 +-- 2 files changed, 41 insertions(+), 118 deletions(-) diff --git a/net/cert/internal/trust_store_in_memory_starboard.cc b/net/cert/internal/trust_store_in_memory_starboard.cc index 1bc400f5ce53..4d546c5996dc 100644 --- a/net/cert/internal/trust_store_in_memory_starboard.cc +++ b/net/cert/internal/trust_store_in_memory_starboard.cc @@ -16,51 +16,30 @@ #include -#include "base/files/file.h" #include "base/files/file_path.h" -#include "base/path_service.h" +#include "base/files/file_util.h" #include "base/logging.h" +#include "base/notreached.h" +#include "base/path_service.h" #include "base/time/time.h" -#include "net/cert/pki/cert_errors.h" #include "net/cert/pem.h" +#include "net/cert/pki/cert_errors.h" #include "net/cert/x509_certificate.h" #include "net/cert/x509_util.h" #include "starboard/common/string.h" #include "starboard/configuration_constants.h" -#include "starboard/directory.h" -#include "starboard/file.h" #include "starboard/string.h" -#include "third_party/boringssl/src/include/openssl/digest.h" -#include "third_party/boringssl/src/include/openssl/sha.h" #include "third_party/boringssl/src/include/openssl/x509.h" namespace net { namespace { -// PEM encoded DER cert is usually around or less than 2000 bytes long. -const short kCertBufferSize = 8 * 1024; // Each certificate file name is 8 bit hash + ".0" suffix. const short kCertFileNameLength = 10; const char kCertificateHeader[] = "CERTIFICATE"; const char kSSLDirName[] = "ssl"; const char kCertsDirName[] = "certs"; -// Essentially an X509_NAME_hash without using X509_NAME. We did not use the -// boringSSL function directly because net does not store certs with X509 -// struct anymore and converting binary certs to X509_NAME is slightly -// expensive. -unsigned long CertNameHash(const void* data, size_t length) { - unsigned long ret = 0; - unsigned char md[SHA_DIGEST_LENGTH]; - if (!EVP_Digest(data, length, md, NULL, EVP_sha1(), NULL)) - return 0; - - ret = (((unsigned long)md[0]) | ((unsigned long)md[1] << 8L) | - ((unsigned long)md[2] << 16L) | ((unsigned long)md[3] << 24L)) & - 0xffffffffL; - return ret; -} - base::FilePath GetCertificateDirPath() { base::FilePath cert_path; base::PathService::Get(base::DIR_EXE, &cert_path); @@ -68,9 +47,9 @@ base::FilePath GetCertificateDirPath() { return std::move(cert_path); } -std::unordered_set GetCertNamesOnDisk() { - DIR* sb_certs_directory = - opendir(GetCertificateDirPath().value().c_str()); +std::vector> GetAllCertsOnDisk() { + base::FilePath cert_dir_path = GetCertificateDirPath(); + DIR* sb_certs_directory = opendir(cert_dir_path.value().c_str()); if (!sb_certs_directory) { // Unit tests, for example, do not use production certificates. #if defined(STARBOARD_BUILD_TYPE_QA) || defined(STARBOARD_BUILD_TYPE_GOLD) @@ -79,9 +58,10 @@ std::unordered_set GetCertNamesOnDisk() { DLOG(WARNING) << "ssl/certs directory is not valid, no root certificates" " will be loaded"; #endif - return std::unordered_set(); + return {}; } - std::unordered_set trusted_certs_on_disk; + + std::vector> certs; std::vector dir_entry(kSbFileMaxName); struct dirent dirent_buffer; @@ -99,90 +79,51 @@ std::unordered_set GetCertNamesOnDisk() { if (strlen(dir_entry.data()) != kCertFileNameLength) { continue; } - trusted_certs_on_disk.emplace(dir_entry.data()); + + base::FilePath cert_path = cert_dir_path.Append(dir_entry.data()); + std::string cert_buffer; + CHECK(base::ReadFileToString(cert_path, &cert_buffer)) + << "ssl/certs/" << cert_path.value() << " failed to open."; + PEMTokenizer pem_tokenizer(cert_buffer, {kCertificateHeader}); + CHECK(pem_tokenizer.GetNext()) << "Failed to parse PEM from cert file: " + << cert_path.value(); + std::string decoded(pem_tokenizer.data()); + CHECK(!pem_tokenizer.GetNext()) << "Multiple certificates found in " + << cert_path.value(); + auto crypto_buffer = x509_util::CreateCryptoBuffer(decoded); + CHECK(crypto_buffer) << "Failed to create crypto buffer for " + << cert_path.value(); + CertErrors errors; + auto parsed = ParsedCertificate::Create( + std::move(crypto_buffer), x509_util::DefaultParseCertificateOptions(), + &errors); + CHECK(parsed) << "Failed to parse certificate " << cert_path.value() + << ": " << errors.ToDebugString(); + certs.push_back(parsed); } closedir(sb_certs_directory); - return std::move(trusted_certs_on_disk); + return certs; } } // namespace -std::shared_ptr TrustStoreInMemoryStarboard::TryLoadCert( - const base::StringPiece& cert_name) const { - auto hash = CertNameHash(cert_name.data(), cert_name.length()); - char cert_file_name[256]; - snprintf(cert_file_name, 256, "%08lx.%d", hash, 0); - - if (trusted_cert_names_on_disk_.find(cert_file_name) == - trusted_cert_names_on_disk_.end()) { - // The requested certificate is not found. - return nullptr; +TrustStoreInMemoryStarboard::TrustStoreInMemoryStarboard() { + for (const auto& cert : GetAllCertsOnDisk()) { + underlying_trust_store_.AddTrustAnchor(cert); } - - char cert_buffer[kCertBufferSize]; - base::FilePath cert_path = GetCertificateDirPath().Append(cert_file_name); - base::File cert_file(cert_path, base::File::Flags::FLAG_OPEN | base::File::Flags::FLAG_READ); - // The file was in certs directory when we iterated the directory at startup, - // opening it should not fail. - if (!cert_file.IsValid()) { - NOTREACHED() << "ssl/certs/" << cert_path << " failed to open."; - return nullptr; - } - int cert_size = cert_file.ReadAtCurrentPos(cert_buffer, kCertBufferSize); - PEMTokenizer pem_tokenizer(base::StringPiece(cert_buffer, cert_size), - {kCertificateHeader}); - pem_tokenizer.GetNext(); - std::string decoded(pem_tokenizer.data()); - DCHECK(!pem_tokenizer.GetNext()); - auto crypto_buffer = x509_util::CreateCryptoBuffer(decoded); - CertErrors errors; - auto parsed = ParsedCertificate::Create( - std::move(crypto_buffer), - x509_util::DefaultParseCertificateOptions(), &errors); - CHECK(parsed) << errors.ToDebugString(); - return parsed; } -TrustStoreInMemoryStarboard::TrustStoreInMemoryStarboard() - : trusted_cert_names_on_disk_(std::move(GetCertNamesOnDisk())) {} - TrustStoreInMemoryStarboard::~TrustStoreInMemoryStarboard() = default; void TrustStoreInMemoryStarboard::SyncGetIssuersOf( const ParsedCertificate* cert, ParsedCertificateList* issuers) { - DCHECK(issuers); - DCHECK(issuers->empty()); - base::AutoLock scoped_lock(load_mutex_); - // Look up the request certificate first in the trust store in memory. underlying_trust_store_.SyncGetIssuersOf(cert, issuers); - if (issuers->empty()) { - // If the requested certificate is not found, compute certificate hash name - // and see if the certificate is stored on disk. - auto parsed_cert = TryLoadCert(cert->normalized_issuer().AsStringView()); - if (parsed_cert.get()) { - issuers->push_back(parsed_cert); - underlying_trust_store_.AddTrustAnchor(parsed_cert); - } - } } -CertificateTrust TrustStoreInMemoryStarboard::GetTrust(const ParsedCertificate* cert, - base::SupportsUserData* debug_data) { - base::AutoLock scoped_lock(load_mutex_); - // Loop up the request certificate first in the trust store in memory. - CertificateTrust trust = underlying_trust_store_.GetTrust(cert, debug_data); - if (trust.HasUnspecifiedTrust()) { - // If the requested certificate is not found, compute certificate hash name - // and see if the certificate is stored on disk. - auto parsed_cert = TryLoadCert(cert->normalized_subject().AsStringView()); - if (parsed_cert.get()) { - trust = CertificateTrust::ForTrustAnchor(); - const_cast(this) - ->underlying_trust_store_.AddTrustAnchor(parsed_cert); - } - } - return trust; +CertificateTrust TrustStoreInMemoryStarboard::GetTrust( + const ParsedCertificate* cert, + base::SupportsUserData* debug_data) { + return underlying_trust_store_.GetTrust(cert, debug_data); } } // namespace net - diff --git a/net/cert/internal/trust_store_in_memory_starboard.h b/net/cert/internal/trust_store_in_memory_starboard.h index d8cb1d292834..169847e5a819 100644 --- a/net/cert/internal/trust_store_in_memory_starboard.h +++ b/net/cert/internal/trust_store_in_memory_starboard.h @@ -15,15 +15,12 @@ #ifndef NET_CERT_INTERNAL_TRUST_STORE_IN_MEMORY_STARBOARD_H_ #define NET_CERT_INTERNAL_TRUST_STORE_IN_MEMORY_STARBOARD_H_ -#include - #include "base/strings/string_piece.h" -#include "base/synchronization/lock.h" #include "net/cert/pki/trust_store_in_memory.h" namespace net { -// Wrapper around TrustStoreInMemory to lazily load trusted root certificates. +// Wrapper around TrustStoreInMemory to eagerly load trusted root certificates. class NET_EXPORT TrustStoreInMemoryStarboard : public TrustStore { public: TrustStoreInMemoryStarboard(); @@ -33,12 +30,11 @@ class NET_EXPORT TrustStoreInMemoryStarboard : public TrustStore { void SyncGetIssuersOf(const ParsedCertificate* cert, ParsedCertificateList* issuers) override; CertificateTrust GetTrust(const ParsedCertificate* cert, - base::SupportsUserData* debug_data) override; + base::SupportsUserData* debug_data) override; // Returns true if the trust store contains the given ParsedCertificate // (matches by DER). bool Contains(const ParsedCertificate* cert) const { - base::AutoLock scoped_lock(load_mutex_); return underlying_trust_store_.Contains(cert); } @@ -48,22 +44,8 @@ class NET_EXPORT TrustStoreInMemoryStarboard : public TrustStore { delete; TrustStoreInMemory underlying_trust_store_; - - // Given a certificate's canonical name, try to load this cert from trusted - // certs on disk if it is found. - std::shared_ptr TryLoadCert( - const base::StringPiece& cert_name) const; - - // The memory trust store can be accessed by multiple threads, in Chromium, - // the synchronization issue is solved by initializing trust store at startup - // and passing constant reference to consumers. Cobalt loads certs lazily and - // therefore guards the underlying_trust_store_ with mutex. - mutable base::Lock load_mutex_; - - const std::unordered_set trusted_cert_names_on_disk_; }; } // namespace net #endif // NET_CERT_INTERNAL_TRUST_STORE_IN_MEMORY_STARBOARD_H_ -