summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jiakai Zhang <jiakaiz@google.com> 2025-03-12 16:44:02 +0000
committer Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2025-03-20 16:33:35 -0700
commitb2d23dd55d0f1d16beb43a247d62bf09a8060665 (patch)
treee684811a36c48b5337ce34f9e9e872b267d1b569
parent6f3beba5e01780c30364e34be15335da51c4e0e5 (diff)
Add helper classes for reading and writing SDC files.
Secure dex metadata companion (SDC) file is a file type that augments a secure dex metadata (SDM) file with additional metadata. It is generated on device when the SDM file is being installed. Bug: 377474232 Test: m test-art-host-gtest-art_runtime_tests Change-Id: I13cf86db9cdcca85e5b59bd992a4deddeeb57501
-rw-r--r--runtime/Android.bp2
-rw-r--r--runtime/oat/sdc_file.cc113
-rw-r--r--runtime/oat/sdc_file.h89
-rw-r--r--runtime/oat/sdc_file_test.cc167
4 files changed, 371 insertions, 0 deletions
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 892f1b0a19..c0a65f27a7 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -397,6 +397,7 @@ cc_defaults {
"oat/oat_file_assistant_context.cc",
"oat/oat_file_manager.cc",
"oat/oat_quick_method_header.cc",
+ "oat/sdc_file.cc",
"oat/stack_map.cc",
"object_lock.cc",
"offsets.cc",
@@ -1129,6 +1130,7 @@ art_cc_defaults {
"native_stack_dump_test.cc",
"oat/oat_file_assistant_test.cc",
"oat/oat_file_test.cc",
+ "oat/sdc_file_test.cc",
"parsed_options_test.cc",
"prebuilt_tools_test.cc",
"proxy_test.cc",
diff --git a/runtime/oat/sdc_file.cc b/runtime/oat/sdc_file.cc
new file mode 100644
index 0000000000..35a759e576
--- /dev/null
+++ b/runtime/oat/sdc_file.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2025 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 "sdc_file.h"
+
+#include <memory>
+#include <regex>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+#include "android-base/file.h"
+#include "android-base/parseint.h"
+#include "android-base/scopeguard.h"
+#include "base/macros.h"
+#include "base/utils.h"
+
+namespace art HIDDEN {
+
+using ::android::base::ParseInt;
+using ::android::base::ReadFileToString;
+using ::android::base::WriteStringToFd;
+
+std::unique_ptr<SdcReader> SdcReader::Load(const std::string filename, std::string* error_msg) {
+ std::unique_ptr<SdcReader> reader(new SdcReader());
+
+ // The sdc file is supposed to be small, so read fully into memory for simplicity.
+ if (!ReadFileToString(filename, &reader->content_)) {
+ *error_msg = ART_FORMAT("Failed to load sdc file '{}': {}", filename, strerror(errno));
+ return nullptr;
+ }
+
+ std::vector<std::string_view> lines;
+ Split(reader->content_, '\n', &lines);
+ std::unordered_map<std::string_view, std::string_view> map;
+ for (std::string_view line : lines) {
+ size_t pos = line.find('=');
+ if (pos == std::string_view::npos || pos == 0) {
+ *error_msg = ART_FORMAT("Malformed line '{}' in sdc file '{}'", line, filename);
+ return nullptr;
+ }
+ if (!map.try_emplace(line.substr(0, pos), line.substr(pos + 1)).second) {
+ *error_msg = ART_FORMAT("Duplicate key '{}' in sdc file '{}'", line.substr(0, pos), filename);
+ return nullptr;
+ }
+ }
+
+ decltype(map)::iterator it;
+ if ((it = map.find("sdm-timestamp")) == map.end()) {
+ *error_msg = ART_FORMAT("Missing key 'sdm-timestamp' in sdc file '{}'", filename);
+ return nullptr;
+ }
+ if (!ParseInt(std::string(it->second), &reader->sdm_timestamp_, /*min=*/1l)) {
+ *error_msg = ART_FORMAT("Invalid 'sdm-timestamp' {}", it->second);
+ return nullptr;
+ }
+
+ if ((it = map.find("apex-versions")) == map.end()) {
+ *error_msg = ART_FORMAT("Missing key 'apex-versions' in sdc file '{}'", filename);
+ return nullptr;
+ }
+ if (!std::regex_match(it->second.begin(), it->second.end(), std::regex("[0-9/]*"))) {
+ *error_msg = ART_FORMAT("Invalid 'apex-versions' {}", it->second);
+ return nullptr;
+ }
+ reader->apex_versions_ = it->second;
+
+ if (map.size() > 2) {
+ *error_msg = ART_FORMAT("Malformed sdc file '{}'. Unrecognized keys", filename);
+ return nullptr;
+ }
+
+ return reader;
+}
+
+bool SdcWriter::Save(std::string* error_msg) {
+ auto cleanup = android::base::make_scope_guard([this] { (void)file_.FlushClose(); });
+ if (sdm_timestamp_ <= 0) {
+ *error_msg = ART_FORMAT("Invalid 'sdm-timestamp' {}", sdm_timestamp_);
+ return false;
+ }
+ DCHECK_EQ(file_.GetLength(), 0);
+ std::string content =
+ ART_FORMAT("sdm-timestamp={}\napex-versions={}\n", sdm_timestamp_, apex_versions_);
+ if (!WriteStringToFd(content, file_.Fd())) {
+ *error_msg = ART_FORMAT("Failed to write sdc file '{}': {}", file_.GetPath(), strerror(errno));
+ return false;
+ }
+ int res = file_.FlushClose();
+ if (res != 0) {
+ *error_msg =
+ ART_FORMAT("Failed to flush close sdc file '{}': {}", file_.GetPath(), strerror(-res));
+ return false;
+ }
+ cleanup.Disable();
+ return true;
+}
+
+} // namespace art
diff --git a/runtime/oat/sdc_file.h b/runtime/oat/sdc_file.h
new file mode 100644
index 0000000000..18c34a3527
--- /dev/null
+++ b/runtime/oat/sdc_file.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+
+#ifndef ART_RUNTIME_OAT_SDC_FILE_H_
+#define ART_RUNTIME_OAT_SDC_FILE_H_
+
+#include <ctime>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/os.h"
+
+namespace art HIDDEN {
+
+// A helper class to read a secure dex metadata companion (SDC) file.
+//
+// Secure dex metadata companion (SDC) file is a file type that augments a secure dex metadata (SDM)
+// file with additional metadata.
+//
+// 1. There may be exactly one SDC file accompanying each SDM file. An SDC file without a
+// corresponding SDM file, or with a mismatching SDM timestamp, is garbage.
+// 2. They are always local on device.
+// 3. They are only read and written by the ART module.
+// 4. A later version of the ART module must be able to understand the contents.
+//
+// It is a text file in the format of:
+// key1=value1\n
+// key2=value2\n
+// ...
+// Repeated keys are not allowed. This is an extensible format, so versioning is not needed.
+class SdcReader {
+ public:
+ static std::unique_ptr<SdcReader> Load(const std::string filename, std::string* error_msg);
+
+ // The mtime of the SDM file on device.
+ // This is for detecting obsolete SDC files.
+ time_t GetSdmTimestamp() const { return sdm_timestamp_; }
+
+ // The value of `Runtime::GetApexVersions` at the time where the SDM file was first seen on
+ // device. This is for detecting samegrade placebos.
+ std::string_view GetApexVersions() const { return apex_versions_; }
+
+ private:
+ SdcReader() = default;
+
+ std::string content_;
+ time_t sdm_timestamp_;
+ std::string_view apex_versions_;
+};
+
+// A helper class to write a secure dex metadata companion (SDC) file.
+class EXPORT SdcWriter {
+ public:
+ // Takes ownership of the file.
+ explicit SdcWriter(File&& file) : file_(std::move(file)) {}
+
+ // See `SdcReader::GetSdmTimestamp`.
+ void SetSdmTimestamp(time_t value) { sdm_timestamp_ = value; }
+
+ // See `SdcReader::GetApexVersions`.
+ void SetApexVersions(std::string_view value) { apex_versions_ = value; }
+
+ bool Save(std::string* error_msg);
+
+ private:
+ File file_;
+ time_t sdm_timestamp_ = 0;
+ std::string apex_versions_;
+};
+
+} // namespace art
+
+#endif // ART_RUNTIME_OAT_SDC_FILE_H_
diff --git a/runtime/oat/sdc_file_test.cc b/runtime/oat/sdc_file_test.cc
new file mode 100644
index 0000000000..f3bf1bc36d
--- /dev/null
+++ b/runtime/oat/sdc_file_test.cc
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2025 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 "sdc_file.h"
+
+#include <memory>
+#include <string>
+
+#include "android-base/file.h"
+#include "base/common_art_test.h"
+#include "base/macros.h"
+#include "base/os.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace art HIDDEN {
+
+using ::android::base::ReadFileToString;
+using ::android::base::WriteStringToFile;
+using ::testing::HasSubstr;
+using ::testing::StartsWith;
+
+class SdcFileTestBase : public CommonArtTest {
+ protected:
+ void SetUp() override {
+ CommonArtTest::SetUp();
+
+ scratch_dir_ = std::make_unique<ScratchDir>();
+ test_file_ = scratch_dir_->GetPath() + "test.sdc";
+ }
+
+ void TearDown() override {
+ scratch_dir_.reset();
+ CommonArtTest::TearDown();
+ }
+
+ std::unique_ptr<ScratchDir> scratch_dir_;
+ std::string test_file_;
+};
+
+class SdcReaderTest : public SdcFileTestBase {};
+
+TEST_F(SdcReaderTest, Success) {
+ ASSERT_TRUE(
+ WriteStringToFile("sdm-timestamp=987654321\napex-versions=/12345678/12345679\n", test_file_));
+
+ std::string error_msg;
+ std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg);
+ ASSERT_NE(reader, nullptr) << error_msg;
+
+ EXPECT_EQ(reader->GetApexVersions(), "/12345678/12345679");
+ EXPECT_EQ(reader->GetSdmTimestamp(), 987654321l);
+}
+
+TEST_F(SdcReaderTest, NotFound) {
+ std::string error_msg;
+ std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg);
+ ASSERT_EQ(reader, nullptr);
+
+ EXPECT_THAT(error_msg, StartsWith("Failed to load sdc file"));
+}
+
+TEST_F(SdcReaderTest, MissingApexVersions) {
+ ASSERT_TRUE(WriteStringToFile("sdm-timestamp=987654321\n", test_file_));
+
+ std::string error_msg;
+ std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg);
+ ASSERT_EQ(reader, nullptr);
+
+ EXPECT_THAT(error_msg, StartsWith("Missing key 'apex-versions' in sdc file"));
+}
+
+TEST_F(SdcReaderTest, InvalidSdmTimestamp) {
+ ASSERT_TRUE(WriteStringToFile("sdm-timestamp=0\napex-versions=/12345678/12345679\n", test_file_));
+
+ std::string error_msg;
+ std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg);
+ ASSERT_EQ(reader, nullptr);
+
+ EXPECT_THAT(error_msg, HasSubstr("Invalid 'sdm-timestamp'"));
+}
+
+TEST_F(SdcReaderTest, InvalidApexVersions) {
+ ASSERT_TRUE(WriteStringToFile("sdm-timestamp=987654321\napex-versions=abc\n", test_file_));
+
+ std::string error_msg;
+ std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg);
+ ASSERT_EQ(reader, nullptr);
+
+ EXPECT_THAT(error_msg, HasSubstr("Invalid 'apex-versions'"));
+}
+
+TEST_F(SdcReaderTest, UnrecognizedKey) {
+ ASSERT_TRUE(WriteStringToFile(
+ "sdm-timestamp=987654321\napex-versions=/12345678/12345679\nwrong-key=12345678\n",
+ test_file_));
+
+ std::string error_msg;
+ std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg);
+ ASSERT_EQ(reader, nullptr);
+
+ EXPECT_THAT(error_msg, HasSubstr("Unrecognized keys"));
+}
+
+class SdcWriterTest : public SdcFileTestBase {};
+
+TEST_F(SdcWriterTest, Success) {
+ std::unique_ptr<File> file(OS::CreateEmptyFileWriteOnly(test_file_.c_str()));
+ ASSERT_NE(file, nullptr);
+ SdcWriter writer(std::move(*file));
+
+ writer.SetApexVersions("/12345678/12345679");
+ writer.SetSdmTimestamp(987654321l);
+
+ std::string error_msg;
+ ASSERT_TRUE(writer.Save(&error_msg)) << error_msg;
+
+ std::string content;
+ ASSERT_TRUE(ReadFileToString(test_file_, &content));
+
+ EXPECT_EQ(content, "sdm-timestamp=987654321\napex-versions=/12345678/12345679\n");
+}
+
+TEST_F(SdcWriterTest, SaveFailed) {
+ ASSERT_TRUE(WriteStringToFile("", test_file_));
+
+ std::unique_ptr<File> file(OS::OpenFileForReading(test_file_.c_str()));
+ ASSERT_NE(file, nullptr);
+ SdcWriter writer(
+ File(file->Release(), file->GetPath(), /*check_usage=*/false, /*read_only_mode=*/false));
+
+ writer.SetApexVersions("/12345678/12345679");
+ writer.SetSdmTimestamp(987654321l);
+
+ std::string error_msg;
+ EXPECT_FALSE(writer.Save(&error_msg));
+
+ EXPECT_THAT(error_msg, StartsWith("Failed to write sdc file"));
+}
+
+TEST_F(SdcWriterTest, InvalidSdmTimestamp) {
+ std::unique_ptr<File> file(OS::CreateEmptyFileWriteOnly(test_file_.c_str()));
+ ASSERT_NE(file, nullptr);
+ SdcWriter writer(std::move(*file));
+
+ writer.SetApexVersions("/12345678/12345679");
+
+ std::string error_msg;
+ EXPECT_FALSE(writer.Save(&error_msg));
+
+ EXPECT_THAT(error_msg, StartsWith("Invalid 'sdm-timestamp'"));
+}
+
+} // namespace art