odrefresh: enable loading of system_server image files
Fix check in ValidateBootImageChecksum() to account for image files
with multiple components which exists for boot-framework when
compiling on device.
Update logic for only_load_system_executable to be
only_load_trusted_executable and treat /system and the ART APEX data
directory as trusted.
Add test to check .art,.odex,.vdex files derived from the
system_server classpath are mapped when the ART module updates.
Add test to check .art,.oat,.vdex files for the boot class path
extensions are present in the zygote processes.
(cherry picked from commit 094b1cfc9fa9e1c02238a2352c190be1746f5622)
Fix: 180949581
Test: atest com.android.tests.odsign.OnDeviceSigningHostTest#verifyGeneratedArtifactsLoaded
Merged-In: I3114fc6393402d8da2eb16ba756ab5fab713dc20
Change-Id: I493b2eaa99d27a865ced77745debdeaff16b7d6a
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 998f41a..e13758d 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -316,7 +316,7 @@
isa_,
class_loader_context.get(),
/*load_executable=*/ false,
- /*only_load_system_executable=*/ false,
+ /*only_load_trusted_executable=*/ false,
vdex_fd_,
oat_fd_,
zip_fd_);
diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc
index e39e49a..45c3e3e 100644
--- a/libartbase/base/file_utils.cc
+++ b/libartbase/base/file_utils.cc
@@ -612,18 +612,22 @@
return android::base::StartsWith(full_path, kApexDefaultPath);
}
-bool LocationIsOnSystem(const char* path) {
+bool LocationIsOnSystem(const std::string& location) {
#ifdef _WIN32
- UNUSED(path);
+ UNUSED(location);
LOG(FATAL) << "LocationIsOnSystem is unsupported on Windows.";
return false;
#else
- UniqueCPtr<const char[]> full_path(realpath(path, nullptr));
+ UniqueCPtr<const char[]> full_path(realpath(location.c_str(), nullptr));
return full_path != nullptr &&
android::base::StartsWith(full_path.get(), GetAndroidRoot().c_str());
#endif
}
+bool LocationIsTrusted(const std::string& location) {
+ return LocationIsOnSystem(location) || LocationIsOnArtApexData(location);
+}
+
bool ArtModuleRootDistinctFromAndroidRoot() {
std::string error_msg;
const char* android_root = GetAndroidDirSafe(kAndroidRootEnvVar,
diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h
index ac611ad..6af82ef 100644
--- a/libartbase/base/file_utils.h
+++ b/libartbase/base/file_utils.h
@@ -138,7 +138,7 @@
bool LocationIsOnI18nModule(std::string_view location);
// Return whether the location is on system (i.e. android root).
-bool LocationIsOnSystem(const char* location);
+bool LocationIsOnSystem(const std::string& location);
// Return whether the location is on system/framework (i.e. $ANDROID_ROOT/framework).
bool LocationIsOnSystemFramework(std::string_view location);
@@ -149,6 +149,11 @@
// Return whether the location is on /apex/.
bool LocationIsOnApex(std::string_view location);
+// Returns whether the location is trusted for loading oat files. Trusted locations are protected
+// by dm-verity or fs-verity. The recognized locations are on /system or
+// /data/misc/apexdata/com.android.art.
+bool LocationIsTrusted(const std::string& location);
+
// Compare the ART module root against android root. Returns true if they are
// both known and distinct. This is meant to be a proxy for 'running with apex'.
bool ArtModuleRootDistinctFromAndroidRoot();
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 1627d78..51582ce 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -846,11 +846,11 @@
// Use the boot image component count to calculate the checksum from
// the appropriate number of boot image chunks.
uint32_t boot_image_component_count = image_header.GetBootImageComponentCount();
- size_t boot_image_spaces_size = boot_image_spaces.size();
- if (boot_image_component_count > boot_image_spaces_size) {
+ size_t expected_image_component_count = ImageSpace::GetNumberOfComponents(boot_image_spaces);
+ if (boot_image_component_count > expected_image_component_count) {
*error_msg = StringPrintf("Too many boot image dependencies (%u > %zu) in image %s",
boot_image_component_count,
- boot_image_spaces_size,
+ expected_image_component_count,
image_filename);
return false;
}
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index cdb63f3..2c18ddb 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -144,7 +144,7 @@
DEBUG_NATIVE_DEBUGGABLE = 1 << 7,
DEBUG_JAVA_DEBUGGABLE = 1 << 8,
DISABLE_VERIFIER = 1 << 9,
- ONLY_USE_SYSTEM_OAT_FILES = 1 << 10,
+ ONLY_USE_TRUSTED_OAT_FILES = 1 << 10, // Formerly ONLY_USE_SYSTEM_OAT_FILES
DEBUG_GENERATE_MINI_DEBUG_INFO = 1 << 11,
HIDDEN_API_ENFORCEMENT_POLICY_MASK = (1 << 12)
| (1 << 13),
@@ -318,9 +318,9 @@
runtime_flags &= ~DISABLE_VERIFIER;
}
- if ((runtime_flags & ONLY_USE_SYSTEM_OAT_FILES) != 0 || is_system_server) {
- runtime->GetOatFileManager().SetOnlyUseSystemOatFiles();
- runtime_flags &= ~ONLY_USE_SYSTEM_OAT_FILES;
+ if ((runtime_flags & ONLY_USE_TRUSTED_OAT_FILES) != 0 || is_system_server) {
+ runtime->GetOatFileManager().SetOnlyUseTrustedOatFiles();
+ runtime_flags &= ~ONLY_USE_TRUSTED_OAT_FILES;
}
api_enforcement_policy = hiddenapi::EnforcementPolicyFromInt(
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index c74f19b..038fc0f 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -80,12 +80,12 @@
const InstructionSet isa,
ClassLoaderContext* context,
bool load_executable,
- bool only_load_system_executable)
+ bool only_load_trusted_executable)
: OatFileAssistant(dex_location,
isa,
context,
load_executable,
- only_load_system_executable,
+ only_load_trusted_executable,
/*vdex_fd=*/ -1,
/*oat_fd=*/ -1,
/*zip_fd=*/ -1) {}
@@ -95,14 +95,14 @@
const InstructionSet isa,
ClassLoaderContext* context,
bool load_executable,
- bool only_load_system_executable,
+ bool only_load_trusted_executable,
int vdex_fd,
int oat_fd,
int zip_fd)
: context_(context),
isa_(isa),
load_executable_(load_executable),
- only_load_system_executable_(only_load_system_executable),
+ only_load_trusted_executable_(only_load_trusted_executable),
odex_(this, /*is_oat_location=*/ false),
oat_(this, /*is_oat_location=*/ true),
vdex_for_odex_(this, /*is_oat_location=*/ false),
@@ -453,8 +453,8 @@
// zip_file_only_contains_uncompressed_dex_ is only set during fetching the dex checksums.
DCHECK(required_dex_checksums_attempted_);
- if (only_load_system_executable_ &&
- !LocationIsOnSystem(file.GetLocation().c_str()) &&
+ if (only_load_trusted_executable_ &&
+ !LocationIsTrusted(file.GetLocation()) &&
file.ContainsDexCode() &&
zip_file_only_contains_uncompressed_dex_) {
LOG(ERROR) << "Not loading "
@@ -846,8 +846,8 @@
&error_msg));
}
} else {
- if (executable && oat_file_assistant_->only_load_system_executable_) {
- executable = LocationIsOnSystem(filename_.c_str());
+ if (executable && oat_file_assistant_->only_load_trusted_executable_) {
+ executable = LocationIsTrusted(filename_);
}
VLOG(oat) << "Loading " << filename_ << " with executable: " << executable;
if (use_fd_) {
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 50b54af..9cfa781 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -107,13 +107,14 @@
// load_executable should be true if the caller intends to try and load
// executable code for this dex location.
//
- // only_load_system_executable should be true if the caller intends to have
- // only oat files from /system loaded executable.
+ // only_load_trusted_executable should be true if the caller intends to have
+ // only oat files from trusted locations loaded executable. See IsTrustedLocation() for
+ // details on trusted locations.
OatFileAssistant(const char* dex_location,
const InstructionSet isa,
ClassLoaderContext* context,
bool load_executable,
- bool only_load_system_executable = false);
+ bool only_load_trusted_executable = false);
// Similar to this(const char*, const InstructionSet, bool), however, if a valid zip_fd is
// provided, vdex, oat, and zip files will be read from vdex_fd, oat_fd and zip_fd respectively.
@@ -122,7 +123,7 @@
const InstructionSet isa,
ClassLoaderContext* context,
bool load_executable,
- bool only_load_system_executable,
+ bool only_load_trusted_executable,
int vdex_fd,
int oat_fd,
int zip_fd);
@@ -425,8 +426,8 @@
// Whether we will attempt to load oat files executable.
bool load_executable_ = false;
- // Whether only oat files on /system are loaded executable.
- const bool only_load_system_executable_ = false;
+ // Whether only oat files from trusted locations are loaded executable.
+ const bool only_load_trusted_executable_ = false;
// Whether the potential zip file only contains uncompressed dex.
// Will be set during GetRequiredDexChecksums.
bool zip_file_only_contains_uncompressed_dex_ = true;
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index 92929b3..391d597 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -72,7 +72,7 @@
WriterMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
CHECK(!only_use_system_oat_files_ ||
- LocationIsOnSystem(oat_file->GetLocation().c_str()) ||
+ LocationIsTrusted(oat_file->GetLocation()) ||
!oat_file->IsExecutable())
<< "Registering a non /system oat file: " << oat_file->GetLocation();
DCHECK(oat_file != nullptr);
@@ -800,7 +800,7 @@
}
}
-void OatFileManager::SetOnlyUseSystemOatFiles() {
+void OatFileManager::SetOnlyUseTrustedOatFiles() {
ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
// Make sure all files that were loaded up to this point are on /system.
// Skip the image files as they can encode locations that don't exist (eg not
@@ -810,8 +810,8 @@
for (const std::unique_ptr<const OatFile>& oat_file : oat_files_) {
if (boot_set.find(oat_file.get()) == boot_set.end()) {
- if (!LocationIsOnSystem(oat_file->GetLocation().c_str())) {
- // When the file is not on system, we check whether the oat file has any
+ if (!LocationIsTrusted(oat_file->GetLocation())) {
+ // When the file is not in a trusted location, we check whether the oat file has any
// AOT or DEX code. It is a fatal error if it has.
if (CompilerFilter::IsAotCompilationEnabled(oat_file->GetCompilerFilter()) ||
oat_file->ContainsDexCode()) {
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index 25d0023..f8a7341 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -123,7 +123,7 @@
void DumpForSigQuit(std::ostream& os);
- void SetOnlyUseSystemOatFiles();
+ void SetOnlyUseTrustedOatFiles();
// Spawn a background thread which verifies all classes in the given dex files.
void RunBackgroundVerification(const std::vector<const DexFile*>& dex_files,
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index b389ce4..15b49fe 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -431,7 +431,7 @@
.WithType<unsigned int>()
.IntoKey(M::MetricsReportingPeriod)
.Define("-Xonly-use-system-oat-files")
- .IntoKey(M::OnlyUseSystemOatFiles)
+ .IntoKey(M::OnlyUseTrustedOatFiles)
.Define("-Xverifier-logging-threshold=_")
.WithType<unsigned int>()
.IntoKey(M::VerifierLoggingThreshold)
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index aa8bd0a..fbb8508 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1925,9 +1925,9 @@
VLOG(startup) << "Runtime::Init exiting";
- // Set OnlyUseSystemOatFiles only after boot classpath has been set up.
- if (runtime_options.Exists(Opt::OnlyUseSystemOatFiles)) {
- oat_file_manager_->SetOnlyUseSystemOatFiles();
+ // Set OnlyUseTrustedOatFiles only after the boot classpath has been set up.
+ if (runtime_options.Exists(Opt::OnlyUseTrustedOatFiles)) {
+ oat_file_manager_->SetOnlyUseTrustedOatFiles();
}
return true;
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index 3743670..d1b37e5 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -169,7 +169,7 @@
RUNTIME_OPTIONS_KEY (unsigned int, GlobalRefAllocStackTraceLimit, 0) // 0 = off
RUNTIME_OPTIONS_KEY (Unit, UseStderrLogger)
-RUNTIME_OPTIONS_KEY (Unit, OnlyUseSystemOatFiles)
+RUNTIME_OPTIONS_KEY (Unit, OnlyUseTrustedOatFiles)
RUNTIME_OPTIONS_KEY (unsigned int, VerifierLoggingThreshold, 100)
RUNTIME_OPTIONS_KEY (bool, FastClassNotFoundException, true)
diff --git a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
index b2d5624..1ac89fd 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
@@ -18,14 +18,16 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.cts.install.lib.host.InstallUtilsHost;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.device.ITestDevice.ApexInfo;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.CommandResult;
import org.junit.After;
import org.junit.Before;
@@ -33,11 +35,16 @@
import org.junit.runner.RunWith;
import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
@RunWith(DeviceJUnit4ClassRunner.class)
public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
private static final String APEX_FILENAME = "test_com.android.art.apex";
+ private static final String ART_APEX_DALVIK_CACHE_DIRNAME =
+ "/data/misc/apexdata/com.android.art/dalvik-cache";
private static final String TEST_APP_PACKAGE_NAME = "com.android.tests.odsign";
private static final String TEST_APP_APK = "odsign_e2e_test_app.apk";
@@ -77,6 +84,137 @@
runDeviceTests(options);
}
+ private Set<String> getMappedArtifacts(String pid, String grepPattern) throws Exception {
+ final String grepCommand = String.format("grep \"%s\" /proc/%s/maps", grepPattern, pid);
+ CommandResult result = getDevice().executeShellV2Command(grepCommand);
+ assertTrue(result.toString(), result.getExitCode() == 0);
+ Set<String> mappedFiles = new HashSet<>();
+ for (String line : result.getStdout().split("\\R")) {
+ int start = line.indexOf(ART_APEX_DALVIK_CACHE_DIRNAME);
+ if (line.contains("[")) {
+ continue; // ignore anonymously mapped sections which are quoted in square braces.
+ }
+ mappedFiles.add(line.substring(start));
+ }
+ return mappedFiles;
+ }
+
+ private String[] getSystemServerClasspath() throws Exception {
+ String systemServerClasspath =
+ getDevice().executeShellCommand("echo $SYSTEMSERVERCLASSPATH");
+ return systemServerClasspath.split(":");
+ }
+
+ private String getSystemServerIsa(String mappedArtifact) {
+ // Artifact path for system server artifacts has the form:
+ // ART_APEX_DALVIK_CACHE_DIRNAME + "/<arch>/system@framework@some.jar@classes.odex"
+ // `mappedArtifacts` may include other artifacts, such as boot-framework.oat that are not
+ // prefixed by the architecture.
+ String[] pathComponents = mappedArtifact.split("/");
+ return pathComponents[pathComponents.length - 2];
+ }
+
+ private void verifySystemServerLoadedArtifacts() throws Exception {
+ String[] classpathElements = getSystemServerClasspath();
+ assertTrue("SYSTEMSERVERCLASSPATH is empty", classpathElements.length > 0);
+
+ String systemServerPid = getDevice().executeShellCommand("pgrep system_server");
+ assertTrue(systemServerPid != null);
+
+ // system_server artifacts are in the APEX data dalvik cache and names all contain
+ // the word "@classes". Look for mapped files that match this pattern in the proc map for
+ // system_server.
+ final String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + ".*@classes";
+ final Set<String> mappedArtifacts = getMappedArtifacts(systemServerPid, grepPattern);
+ assertTrue(
+ "No mapped artifacts under " + ART_APEX_DALVIK_CACHE_DIRNAME,
+ mappedArtifacts.size() > 0);
+ final String isa = getSystemServerIsa(mappedArtifacts.iterator().next());
+ final String isaCacheDirectory = String.format("%s/%s", ART_APEX_DALVIK_CACHE_DIRNAME, isa);
+
+ // Extension types for artifacts that this test looks for.
+ final String[] extensions = new String[] {".art", ".odex", ".vdex"};
+
+ // Check the non-APEX components in the system_server classpath have mapped artifacts.
+ for (String element : classpathElements) {
+ // Skip system_server classpath elements from APEXes as these are not currently
+ // compiled.
+ if (element.startsWith("/apex")) {
+ continue;
+ }
+ String escapedPath = element.substring(1).replace('/', '@');
+ for (String extension : extensions) {
+ final String fullArtifactPath =
+ String.format("%s/%s@classes%s", isaCacheDirectory, escapedPath, extension);
+ assertTrue(
+ "Missing " + fullArtifactPath, mappedArtifacts.contains(fullArtifactPath));
+ }
+ }
+
+ for (String mappedArtifact : mappedArtifacts) {
+ // Check no APEX JAR artifacts are mapped for system_server since if there
+ // are, then the policy around not compiling APEX jars for system_server has
+ // changed and this test needs updating here and in the system_server classpath
+ // check above.
+ assertTrue(
+ "Unexpected mapped artifact: " + mappedArtifact,
+ mappedArtifact.contains("/apex"));
+
+ // Check the mapped artifact has a .art, .odex or .vdex extension.
+ final boolean knownArtifactKind =
+ Arrays.stream(extensions).anyMatch(e -> mappedArtifact.endsWith(e));
+ assertTrue("Unknown artifact kind: " + mappedArtifact, knownArtifactKind);
+ }
+ }
+
+ private void verifyZygoteLoadedArtifacts(String zygotePid) throws Exception {
+ final String bootExtensionName = "boot-framework";
+ final Set<String> mappedArtifacts = getMappedArtifacts(zygotePid, bootExtensionName);
+
+ assertTrue("Expect 3 boot-framework artifacts", mappedArtifacts.size() == 3);
+
+ // Extension types for artifacts that this test looks for.
+ final String[] extensions = new String[] {".art", ".oat", ".vdex"};
+
+ for (String extension : extensions) {
+ final String artifact = bootExtensionName + extension;
+ final boolean found = mappedArtifacts.stream().anyMatch(a -> a.endsWith(artifact));
+ assertTrue(artifact + " not found", found);
+ }
+ }
+
+ private void verifyZygotesLoadedArtifacts() throws Exception {
+ // There are potentially two zygote processes "zygote" and "zygote64". These are
+ // instances 32-bit and 64-bit unspecialized app_process processes.
+ // (frameworks/base/cmds/app_process).
+ int zygoteCount = 0;
+ for (String processName : new String[] {"zygote", "zygote64"}) {
+ final CommandResult pgrepResult =
+ getDevice().executeShellV2Command("pgrep " + processName);
+ if (pgrepResult.getExitCode() != 0) {
+ continue;
+ }
+ final String zygotePid = pgrepResult.getStdout();
+ verifyZygoteLoadedArtifacts(zygotePid);
+ zygoteCount += 1;
+ }
+ assertTrue("No zygote processes found", zygoteCount > 0);
+ }
+
+ @Test
+ public void verifyGeneratedArtifactsLoaded() throws Exception {
+ // Checking zygote and system_server need the device have adb root to walk process maps.
+ final boolean adbEnabled = getDevice().enableAdbRoot();
+ assertTrue("ADB root failed and required to get process maps", adbEnabled);
+
+ // Check both zygote and system_server processes to see that they have loaded the
+ // artifacts compiled and signed by odrefresh and odsign. We check both here rather than
+ // having a separate test because the device reboots between each @Test method and
+ // that is an expensive use of time.
+ verifyZygotesLoadedArtifacts();
+ verifySystemServerLoadedArtifacts();
+ }
+
private void reboot() throws Exception {
getDevice().reboot();
boolean success = getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT.toMillis());