Make ApexPreinstalledData a singleton class
Storing state in a global variable inside a namespace is prone to
errors, and doesn't provide a clean api. Given that there are several
different entities that would like/already are to mount apexes, it's
time to add a cleaner api for them to use.
ApexPreinstalledData is now a non-copyable and non-moveable singletone,
with some documentation on how it's expected to be used. This is a first
cl in a sequence that tries to do a small cleanup of apexd codebase. :)
Test: presubmit
Test: atest ApexTestCases
Bug: 165948777
Change-Id: Icb26e4a7f6ed08c3702223a173ccfc399069b5d5
Merged-In: Icb26e4a7f6ed08c3702223a173ccfc399069b5d5
(cherry picked from commit 645d612e4c6fd2d3ac6e1acb6b71713cdaceb5f2)
diff --git a/apexd/Android.bp b/apexd/Android.bp
index a966790..4f0d765 100644
--- a/apexd/Android.bp
+++ b/apexd/Android.bp
@@ -324,6 +324,22 @@
"| cut -c 3-| tee $(out)"
}
+genrule {
+ // Generates an apex which has same module name as apex.apexd_test.apex, but
+ // is actually signed with a different key.
+ name: "gen_key_mismatch_apex",
+ out: ["apex.apexd_test_different_key.apex"],
+ srcs: [":apex.apexd_test_no_inst_key"],
+ tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
+ cmd: "unzip -q $(in) -d $(genDir) && " +
+ "$(location conv_apex_manifest) setprop name com.android.apex.test_package $(genDir)/apex_manifest.pb && " +
+ "$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
+ "-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
+ "-o $(genDir)/unaligned.apex && " +
+ "$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
+ "$(genDir)/apex.apexd_test_different_key.apex"
+}
+
cc_test {
name: "ApexTestCases",
defaults: [
@@ -352,6 +368,7 @@
":apex.apexd_test_prepostinstall.fail",
":apex.apexd_test_v2",
":apex.corrupted_b146895998",
+ ":gen_key_mismatch_apex",
":gen_manifest_mismatch_apex",
":gen_manifest_mismatch_apex_no_hashtree",
":gen_corrupt_superblock_apex",
@@ -369,6 +386,7 @@
"apex_database_test.cpp",
"apex_file_test.cpp",
"apex_manifest_test.cpp",
+ "apex_preinstalled_data_test.cpp",
"apexd_verity_test.cpp",
"apexservice_test.cpp",
],
diff --git a/apexd/apex_file.cpp b/apexd/apex_file.cpp
index 0d7502b..36fd7c9 100644
--- a/apexd/apex_file.cpp
+++ b/apexd/apex_file.cpp
@@ -239,7 +239,10 @@
return Errorf("Unknown vmbeta_image_verify return value");
}
- Result<const std::string> public_key = getApexKey(apex.GetManifest().name());
+ auto& instance = ApexPreinstalledData::GetInstance();
+ // TODO(b/165948777): extract into verifier.
+ Result<const std::string> public_key =
+ instance.GetPublicKey(apex.GetManifest().name());
if (public_key.ok()) {
// TODO(b/115718846)
// We need to decide whether we need rollback protection, and whether
diff --git a/apexd/apex_file_test.cpp b/apexd/apex_file_test.cpp
index 8ac1824..8555e47 100644
--- a/apexd/apex_file_test.cpp
+++ b/apexd/apex_file_test.cpp
@@ -93,7 +93,8 @@
}
TEST_P(ApexFileTest, VerifyApexVerity) {
- ASSERT_RESULT_OK(collectPreinstalledData({"/system_ext/apex"}));
+ ApexPreinstalledData& instance = ApexPreinstalledData::GetInstance();
+ ASSERT_RESULT_OK(instance.Initialize({"/system_ext/apex"}));
const std::string filePath = testDataDir + GetParam().prefix + ".apex";
Result<ApexFile> apexFile = ApexFile::Open(filePath);
ASSERT_RESULT_OK(apexFile);
diff --git a/apexd/apex_preinstalled_data.cpp b/apexd/apex_preinstalled_data.cpp
index d62c28c..dea5336 100644
--- a/apexd/apex_preinstalled_data.cpp
+++ b/apexd/apex_preinstalled_data.cpp
@@ -27,7 +27,6 @@
#include "apex_constants.h"
#include "apex_file.h"
#include "apexd_utils.h"
-#include "string_log.h"
using android::base::Error;
using android::base::Result;
@@ -35,28 +34,13 @@
namespace android {
namespace apex {
-namespace {
-
-struct ApexPreinstalledData {
- std::string name;
- std::string key;
- std::string path;
-};
-
-std::unordered_map<std::string, ApexPreinstalledData> gScannedPreinstalledData;
-
-Result<std::vector<ApexPreinstalledData>> collectPreinstalleDataFromDir(
- const std::string& dir) {
+Result<void> ApexPreinstalledData::ScanDir(const std::string& dir) {
LOG(INFO) << "Scanning " << dir << " for preinstalled data";
- std::vector<ApexPreinstalledData> ret;
if (access(dir.c_str(), F_OK) != 0 && errno == ENOENT) {
- LOG(INFO) << "... does not exist. Skipping";
- return ret;
+ LOG(INFO) << dir << " does not exist. Skipping";
+ return {};
}
- const bool scanBuiltinApexes = isPathForBuiltinApexes(dir);
- if (!scanBuiltinApexes) {
- return Error() << "Can't scan preinstalled APEX data from " << dir;
- }
+
Result<std::vector<std::string>> apex_files = FindApexFilesByName(dir);
if (!apex_files.ok()) {
return apex_files.error();
@@ -67,67 +51,63 @@
if (!apex_file.ok()) {
return Error() << "Failed to open " << file << " : " << apex_file.error();
}
- ApexPreinstalledData apexPreInstalledData;
- apexPreInstalledData.name = apex_file->GetManifest().name();
- apexPreInstalledData.key = apex_file->GetBundledPublicKey();
- apexPreInstalledData.path = apex_file->GetPath();
- ret.push_back(apexPreInstalledData);
- }
- return ret;
-}
-Result<void> updatePreinstalledData(
- const std::vector<ApexPreinstalledData>& apexes) {
- for (const ApexPreinstalledData& apex : apexes) {
- if (gScannedPreinstalledData.find(apex.name) ==
- gScannedPreinstalledData.end()) {
- gScannedPreinstalledData.insert({apex.name, apex});
+ const std::string& name = apex_file->GetManifest().name();
+ ApexData apex_data;
+ apex_data.public_key = apex_file->GetBundledPublicKey();
+ apex_data.path = apex_file->GetPath();
+
+ auto it = data_.find(name);
+ if (it == data_.end()) {
+ LOG(INFO) << "Added " << apex_data.path << " ( " << name
+ << " ) to list of pre-installed apexes";
+ data_[name] = apex_data;
+ } else if (it->second.public_key != apex_data.public_key) {
+ return Error() << "Key for package " << name
+ << " does not match with previously scanned key";
} else {
- const std::string& existing_key =
- gScannedPreinstalledData.at(apex.name).key;
- if (existing_key != apex.key) {
- return Error() << "Key for package " << apex.name
- << " does not match with previously scanned key";
- }
+ LOG(INFO) << apex_data.path << " ( " << name << " ) is already added";
}
}
return {};
}
-} // namespace
+ApexPreinstalledData& ApexPreinstalledData::GetInstance() {
+ static ApexPreinstalledData instance;
+ return instance;
+}
-Result<void> collectPreinstalledData(const std::vector<std::string>& dirs) {
+Result<void> ApexPreinstalledData::Initialize(
+ const std::vector<std::string>& dirs) {
for (const auto& dir : dirs) {
- Result<std::vector<ApexPreinstalledData>> preinstalledData =
- collectPreinstalleDataFromDir(dir);
- if (!preinstalledData.ok()) {
- return Error() << "Failed to collect keys from " << dir << " : "
- << preinstalledData.error();
- }
- Result<void> st = updatePreinstalledData(*preinstalledData);
- if (!st.ok()) {
- return st;
+ if (auto result = ScanDir(dir); !result.ok()) {
+ return result.error();
}
}
return {};
}
-Result<const std::string> getApexKey(const std::string& name) {
- if (gScannedPreinstalledData.find(name) == gScannedPreinstalledData.end()) {
+Result<const std::string> ApexPreinstalledData::GetPublicKey(
+ const std::string& name) const {
+ auto it = data_.find(name);
+ if (it == data_.end()) {
return Error() << "No preinstalled data found for package " << name;
}
- return gScannedPreinstalledData[name].key;
+ return it->second.public_key;
}
-Result<const std::string> getApexPreinstalledPath(const std::string& name) {
- if (gScannedPreinstalledData.find(name) == gScannedPreinstalledData.end()) {
+Result<const std::string> ApexPreinstalledData::GetPreinstalledPath(
+ const std::string& name) const {
+ auto it = data_.find(name);
+ if (it == data_.end()) {
return Error() << "No preinstalled data found for package " << name;
}
- return gScannedPreinstalledData[name].path;
+ return it->second.path;
}
-bool HasPreInstalledVersion(const std::string& name) {
- return gScannedPreinstalledData.find(name) != gScannedPreinstalledData.end();
+bool ApexPreinstalledData::HasPreInstalledVersion(
+ const std::string& name) const {
+ return data_.find(name) != data_.end();
}
} // namespace apex
diff --git a/apexd/apex_preinstalled_data.h b/apexd/apex_preinstalled_data.h
index 1b80aad..59aeebc 100644
--- a/apexd/apex_preinstalled_data.h
+++ b/apexd/apex_preinstalled_data.h
@@ -17,6 +17,7 @@
#pragma once
#include <string>
+#include <unordered_map>
#include <vector>
#include <android-base/result.h>
@@ -24,11 +25,61 @@
namespace android {
namespace apex {
-android::base::Result<void> collectPreinstalledData(
- const std::vector<std::string>& apex_dirs);
-android::base::Result<const std::string> getApexKey(const std::string& name);
-android::base::Result<const std::string> getApexPreinstalledPath(
- const std::string& name);
-bool HasPreInstalledVersion(const std::string& name);
+// This class encapsulates pre-installed data for all the apexes on device.
+// This data can be used to verify validity of an apex before trying to mount
+// it.
+//
+// It's expected to have a single instance of this class in a process that
+// mounts apexes (e.g. apexd, otapreopt_chroot).
+class ApexPreinstalledData final {
+ public:
+ // c-tor and d-tor are exposed for testing.
+ ApexPreinstalledData(){};
+
+ ~ApexPreinstalledData() { data_.clear(); };
+
+ // Returns a singletone instance of this class.
+ static ApexPreinstalledData& GetInstance();
+
+ // Initializes instance by collecting pre-installed data from the given
+ // |dirs|.
+ // Note: this call is **not thread safe** and is expected to be performed in a
+ // single thread during initialization of apexd. After initialization is
+ // finished, all queries to the instance are thread safe.
+ android::base::Result<void> Initialize(const std::vector<std::string>& dirs);
+
+ // Returns trusted public key for an apex with the given |name|.
+ android::base::Result<const std::string> GetPublicKey(
+ const std::string& name) const;
+
+ // Returns path to the pre-installed version of an apex with the given |name|.
+ android::base::Result<const std::string> GetPreinstalledPath(
+ const std::string& name) const;
+
+ // Checks whether there is a pre-installed version of an apex with the given
+ // |name|.
+ bool HasPreInstalledVersion(const std::string& name) const;
+
+ private:
+ // Non-copyable && non-moveable.
+ ApexPreinstalledData(const ApexPreinstalledData&) = delete;
+ ApexPreinstalledData& operator=(const ApexPreinstalledData&) = delete;
+ ApexPreinstalledData& operator=(ApexPreinstalledData&&) = delete;
+ ApexPreinstalledData(ApexPreinstalledData&&) = delete;
+
+ // Scans apexes in the given directory and adds collected data into |data_|.
+ android::base::Result<void> ScanDir(const std::string& dir);
+
+ // Internal struct to hold pre-installed data for the given apex.
+ struct ApexData {
+ // Public key of this apex.
+ std::string public_key;
+ // Path to the pre-installed version of this apex.
+ std::string path;
+ };
+
+ std::unordered_map<std::string, ApexData> data_;
+};
+
} // namespace apex
} // namespace android
diff --git a/apexd/apex_preinstalled_data_test.cpp b/apexd/apex_preinstalled_data_test.cpp
new file mode 100644
index 0000000..0e9de06
--- /dev/null
+++ b/apexd/apex_preinstalled_data_test.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <filesystem>
+#include <string>
+
+#include <errno.h>
+#include <sys/stat.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "apex_file.h"
+#include "apex_preinstalled_data.h"
+#include "apexd_test_utils.h"
+#include "apexd_verity.h"
+
+namespace android {
+namespace apex {
+
+using namespace std::literals;
+
+namespace fs = std::filesystem;
+
+using android::apex::testing::IsOk;
+using android::base::GetExecutableDirectory;
+using android::base::StringPrintf;
+using ::testing::HasSubstr;
+
+static std::string GetTestDataDir() { return GetExecutableDirectory(); }
+static std::string GetTestFile(const std::string& name) {
+ return GetTestDataDir() + "/" + name;
+}
+
+TEST(ApexPreinstalledDataTest, InitializeSuccess) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
+ fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), td.path);
+
+ ApexPreinstalledData instance;
+ ASSERT_TRUE(IsOk(instance.Initialize({td.path})));
+
+ // Now test that apexes were scanned correctly;
+ auto test_fn = [&](const std::string& apex_name) {
+ auto apex = ApexFile::Open(GetTestFile(apex_name));
+ ASSERT_TRUE(IsOk(apex));
+
+ {
+ auto ret = instance.GetPublicKey(apex->GetManifest().name());
+ ASSERT_TRUE(IsOk(ret));
+ ASSERT_EQ(apex->GetBundledPublicKey(), *ret);
+ }
+
+ {
+ auto ret = instance.GetPreinstalledPath(apex->GetManifest().name());
+ ASSERT_TRUE(IsOk(ret));
+ ASSERT_EQ(StringPrintf("%s/%s", td.path, apex_name.c_str()), *ret);
+ }
+
+ ASSERT_TRUE(instance.HasPreInstalledVersion(apex->GetManifest().name()));
+ };
+
+ test_fn("apex.apexd_test.apex");
+ test_fn("apex.apexd_test_different_app.apex");
+}
+
+TEST(ApexPreinstalledDataTest, InitializeFailureCorruptApex) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
+ fs::copy(GetTestFile("apex.apexd_test_corrupt_superblock_apex.apex"),
+ td.path);
+
+ ApexPreinstalledData instance;
+ ASSERT_FALSE(IsOk(instance.Initialize({td.path})));
+}
+
+TEST(ApexPreinstalledData, InitializeFailureSameNameDifferentKeys) {
+ // Prepare test data.
+ TemporaryDir td;
+ fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
+ fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), td.path);
+
+ ApexPreinstalledData instance;
+ auto result = instance.Initialize({td.path});
+
+ ASSERT_FALSE(IsOk(result));
+ ASSERT_THAT(result.error().message(),
+ HasSubstr("does not match with previously scanned key"));
+}
+
+} // namespace apex
+} // namespace android
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index 33f9b57..0a359e1 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -1121,7 +1121,9 @@
std::vector<com::android::apex::ApexInfo> apexInfos;
auto convertToAutogen = [&apexInfos](const ApexFile& apex, bool isActive) {
- auto preinstalledPath = getApexPreinstalledPath(apex.GetManifest().name());
+ auto preinstalledPath =
+ ApexPreinstalledData::GetInstance().GetPreinstalledPath(
+ apex.GetManifest().name());
std::optional<std::string> preinstalledModulePath;
if (preinstalledPath.ok()) {
preinstalledModulePath = *preinstalledPath;
@@ -1311,7 +1313,8 @@
}
bool ShouldActivateApexOnData(const ApexFile& apex) {
- return HasPreInstalledVersion(apex.GetManifest().name());
+ return ApexPreinstalledData::GetInstance().HasPreInstalledVersion(
+ apex.GetManifest().name());
}
} // namespace
@@ -1937,16 +1940,17 @@
<< preAllocate.error();
}
- std::vector<std::string> bootstrap_apex_dirs{
+ ApexPreinstalledData& instance = ApexPreinstalledData::GetInstance();
+ static const std::vector<std::string> kBootstrapApexDirs{
kApexPackageSystemDir, kApexPackageSystemExtDir, kApexPackageVendorDir};
- Result<void> status = collectPreinstalledData(bootstrap_apex_dirs);
+ Result<void> status = instance.Initialize(kBootstrapApexDirs);
if (!status.ok()) {
LOG(ERROR) << "Failed to collect APEX keys : " << status.error();
return 1;
}
// Activate built-in APEXes for processes launched before /data is mounted.
- for (const auto& dir : bootstrap_apex_dirs) {
+ for (const auto& dir : kBootstrapApexDirs) {
auto scan_status = ScanApexFiles(dir.c_str());
if (!scan_status.ok()) {
LOG(ERROR) << "Failed to scan APEX files in " << dir << " : "
@@ -1997,7 +2001,8 @@
void initialize(CheckpointInterface* checkpoint_service) {
initializeVold(checkpoint_service);
- Result<void> status = collectPreinstalledData(kApexPackageBuiltinDirs);
+ ApexPreinstalledData& instance = ApexPreinstalledData::GetInstance();
+ Result<void> status = instance.Initialize(kApexPackageBuiltinDirs);
if (!status.ok()) {
LOG(ERROR) << "Failed to collect APEX keys : " << status.error();
return;
diff --git a/apexd/apexd_verity_test.cpp b/apexd/apexd_verity_test.cpp
index cf2317c..33fd782 100644
--- a/apexd/apexd_verity_test.cpp
+++ b/apexd/apexd_verity_test.cpp
@@ -45,7 +45,8 @@
}
TEST(ApexdVerityTest, ReusesHashtree) {
- ASSERT_TRUE(IsOk(collectPreinstalledData({"/system_ext/apex"})));
+ ApexPreinstalledData& instance = ApexPreinstalledData::GetInstance();
+ ASSERT_TRUE(IsOk(instance.Initialize({"/system_ext/apex"})));
TemporaryDir td;
auto apex = ApexFile::Open(GetTestFile("apex.apexd_test_no_hashtree.apex"));
@@ -78,7 +79,8 @@
}
TEST(ApexdVerityTest, RegenerateHashree) {
- ASSERT_TRUE(IsOk(collectPreinstalledData({"/system_ext/apex"})));
+ ApexPreinstalledData& instance = ApexPreinstalledData::GetInstance();
+ ASSERT_TRUE(IsOk(instance.Initialize({"/system_ext/apex"})));
TemporaryDir td;
auto apex = ApexFile::Open(GetTestFile("apex.apexd_test_no_hashtree.apex"));
diff --git a/apexd/apexservice.cpp b/apexd/apexservice.cpp
index 034a08e..75bead0 100644
--- a/apexd/apexservice.cpp
+++ b/apexd/apexservice.cpp
@@ -275,7 +275,8 @@
out.isFactory = package.IsBuiltin();
out.isActive = false;
Result<std::string> preinstalledPath =
- getApexPreinstalledPath(package.GetManifest().name());
+ ApexPreinstalledData::GetInstance().GetPreinstalledPath(
+ package.GetManifest().name());
if (preinstalledPath.ok()) {
out.preinstalledModulePath = *preinstalledPath;
}
@@ -562,7 +563,8 @@
!root.isOk()) {
return root;
}
- if (auto res = ::android::apex::collectPreinstalledData(paths); !res) {
+ ApexPreinstalledData& instance = ApexPreinstalledData::GetInstance();
+ if (auto res = instance.Initialize(paths); !res) {
return BinderStatus::fromExceptionCode(
BinderStatus::EX_SERVICE_SPECIFIC,
String8(res.error().message().c_str()));