summaryrefslogtreecommitdiff
path: root/libartservice
diff options
context:
space:
mode:
author Jiakai Zhang <jiakaiz@google.com> 2024-11-11 11:29:26 +0000
committer Jiakai Zhang <jiakaiz@google.com> 2024-11-27 21:52:44 +0000
commit702b79ba770866d45e99bb429ad8d9d5e1d1b51d (patch)
tree6926d852b42bedc0a8c92822480e72deaa258a74 /libartservice
parent178425422ad6d80a571f4ce050333d7990cfba8e (diff)
Add a dump option to dump SDM status.
This will be used in CompilationTest in order to test SDM support. Bug: 377474232 Test: atest ArtServiceTests:DumpHelperTest Test: adb shell pm art dump --include-sdm-status <package-name> Change-Id: I1693ed0b3873e58ff4e611fba377eda42deb74b3
Diffstat (limited to 'libartservice')
-rw-r--r--libartservice/service/Android.bp1
-rw-r--r--libartservice/service/jarjar-rules.txt5
-rw-r--r--libartservice/service/java/com/android/server/art/ArtManagerLocal.java29
-rw-r--r--libartservice/service/java/com/android/server/art/ArtShellCommand.java22
-rw-r--r--libartservice/service/java/com/android/server/art/DumpHelper.java95
-rw-r--r--libartservice/service/javatests/com/android/server/art/DumpHelperTest.java102
6 files changed, 238 insertions, 16 deletions
diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp
index 62bdda9edd..9df3d4ef7b 100644
--- a/libartservice/service/Android.bp
+++ b/libartservice/service/Android.bp
@@ -101,6 +101,7 @@ java_defaults {
"sdk_module-lib_current_framework-connectivity",
],
static_libs: [
+ "android.content.pm.flags-aconfig-java-export",
"art-statslog-art-java",
"artd-aidl-java",
"dexopt_chroot_setup-aidl-java",
diff --git a/libartservice/service/jarjar-rules.txt b/libartservice/service/jarjar-rules.txt
index 54ff0a13d2..014ff22579 100644
--- a/libartservice/service/jarjar-rules.txt
+++ b/libartservice/service/jarjar-rules.txt
@@ -1,3 +1,8 @@
# Repackages static libraries to make them private to ART Services.
+rule android.content.pm.CustomFeatureFlags com.android.server.art.jarjar.@0
+rule android.content.pm.FakeFeatureFlagsImpl com.android.server.art.jarjar.@0
+rule android.content.pm.FeatureFlags com.android.server.art.jarjar.@0
+rule android.content.pm.FeatureFlagsImpl com.android.server.art.jarjar.@0
+rule android.content.pm.Flags com.android.server.art.jarjar.@0
rule com.android.modules.utils.** com.android.server.art.jarjar.@0
rule com.google.protobuf.** com.android.server.art.jarjar.@0
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 881a09f5ac..0afdf03d9c 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -996,8 +996,19 @@ public final class ArtManagerLocal {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void dump(
@NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+ dump(pw, snapshot, false /* verifySdmSignatures */);
+ }
+
+ /**
+ * Same as above, but allows to specify options.
+ *
+ * @hide
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void dump(@NonNull PrintWriter pw,
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot, boolean verifySdmSignatures) {
try (var pin = mInjector.createArtdPin()) {
- new DumpHelper(this).dump(pw, snapshot);
+ new DumpHelper(this).dump(pw, snapshot, verifySdmSignatures);
}
}
@@ -1013,9 +1024,21 @@ public final class ArtManagerLocal {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void dumpPackage(@NonNull PrintWriter pw,
@NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+ dumpPackage(pw, snapshot, packageName, false /* verifySdmSignatures */);
+ }
+
+ /**
+ * Same as above, but allows to specify options.
+ *
+ * @hide
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void dumpPackage(@NonNull PrintWriter pw,
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName,
+ boolean verifySdmSignatures) {
try (var pin = mInjector.createArtdPin()) {
- new DumpHelper(this).dumpPackage(
- pw, snapshot, Utils.getPackageStateOrThrow(snapshot, packageName));
+ new DumpHelper(this).dumpPackage(pw, snapshot,
+ Utils.getPackageStateOrThrow(snapshot, packageName), verifySdmSignatures);
}
}
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index ca8f48a0ad..1c19dad9c4 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -171,11 +171,22 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
return 0;
}
case "dump": {
+ boolean verifySdmSignatures = false;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "--verify-sdm-signatures":
+ verifySdmSignatures = true;
+ break;
+ }
+ }
+
String packageName = getNextArg();
if (packageName != null) {
- mArtManagerLocal.dumpPackage(pw, snapshot, packageName);
+ mArtManagerLocal.dumpPackage(pw, snapshot, packageName, verifySdmSignatures);
} else {
- mArtManagerLocal.dump(pw, snapshot);
+ mArtManagerLocal.dump(pw, snapshot, verifySdmSignatures);
}
return 0;
}
@@ -1014,10 +1025,13 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
pw.println(" Cleanup obsolete files, such as dexopt artifacts that are outdated or");
pw.println(" correspond to dex container files that no longer exist.");
pw.println();
- pw.println(" dump [PACKAGE_NAME]");
- pw.println(" Dumps the dexopt state in text format to stdout.");
+ pw.println(" dump [--verify-sdm-signatures] [PACKAGE_NAME]");
+ pw.println(" Dump the dexopt state in text format to stdout.");
pw.println(" If PACKAGE_NAME is empty, the command is for all packages. Otherwise, it");
pw.println(" is for the given package.");
+ pw.println(" Options:");
+ pw.println(" --verify-sdm-signatures Also verify SDM file signatures and include");
+ pw.println(" their statuses.");
pw.println();
pw.println(" dexopt-packages -r REASON");
pw.println(" Run batch dexopt for the given reason.");
diff --git a/libartservice/service/java/com/android/server/art/DumpHelper.java b/libartservice/service/java/com/android/server/art/DumpHelper.java
index 709c742231..56efac04f3 100644
--- a/libartservice/service/java/com/android/server/art/DumpHelper.java
+++ b/libartservice/service/java/com/android/server/art/DumpHelper.java
@@ -20,7 +20,12 @@ import static com.android.server.art.DexUseManagerLocal.CheckedSecondaryDexInfo;
import static com.android.server.art.DexUseManagerLocal.DexLoader;
import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.pm.PackageManager;
+import android.content.pm.SigningInfo;
+import android.content.pm.SigningInfoException;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -34,6 +39,7 @@ import com.android.server.pm.pkg.PackageState;
import dalvik.system.VMRuntime;
+import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Comparator;
@@ -65,13 +71,13 @@ public class DumpHelper {
}
/** Handles {@link ArtManagerLocal#dump(PrintWriter, PackageManagerLocal.FilteredSnapshot)}. */
- public void dump(
- @NonNull PrintWriter pw, @NonNull PackageManagerLocal.FilteredSnapshot snapshot) {
+ public void dump(@NonNull PrintWriter pw,
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot, boolean verifySdmSignatures) {
snapshot.getPackageStates()
.values()
.stream()
.sorted(Comparator.comparing(PackageState::getPackageName))
- .forEach(pkgState -> dumpPackage(pw, snapshot, pkgState));
+ .forEach(pkgState -> dumpPackage(pw, snapshot, pkgState, verifySdmSignatures));
pw.printf("\nCurrent GC: %s\n", ArtJni.getGarbageCollector());
}
@@ -80,8 +86,8 @@ public class DumpHelper {
* ArtManagerLocal#dumpPackage(PrintWriter, PackageManagerLocal.FilteredSnapshot, String)}.
*/
public void dumpPackage(@NonNull PrintWriter pw,
- @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
- @NonNull PackageState pkgState) {
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull PackageState pkgState,
+ boolean verifySdmSignatures) {
if (pkgState.isApex() || pkgState.getAndroidPackage() == null) {
return;
}
@@ -124,7 +130,7 @@ public class DumpHelper {
ipw.increaseIndent();
for (List<DexContainerFileDexoptStatus> fileStatuses : primaryStatusesByDexPath.values()) {
- dumpPrimaryDex(ipw, snapshot, fileStatuses, packageName);
+ dumpPrimaryDex(ipw, snapshot, fileStatuses, packageName, verifySdmSignatures);
}
if (!secondaryStatusesByDexPath.isEmpty()) {
ipw.println("known secondary dex files:");
@@ -141,7 +147,8 @@ public class DumpHelper {
private void dumpPrimaryDex(@NonNull IndentingPrintWriter ipw,
@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
- List<DexContainerFileDexoptStatus> fileStatuses, @NonNull String packageName) {
+ List<DexContainerFileDexoptStatus> fileStatuses, @NonNull String packageName,
+ boolean verifySdmSignatures) {
String dexPath = fileStatuses.get(0).getDexContainerFile();
ipw.printf("path: %s\n", dexPath);
ipw.increaseIndent();
@@ -149,6 +156,7 @@ public class DumpHelper {
dumpUsedByOtherApps(ipw, snapshot,
mInjector.getDexUseManager().getPrimaryDexLoaders(packageName, dexPath),
packageName);
+ dumpSdmStatus(ipw, dexPath, verifySdmSignatures);
ipw.decreaseIndent();
}
@@ -217,6 +225,66 @@ public class DumpHelper {
}
}
+ private void dumpSdmStatus(@NonNull IndentingPrintWriter ipw, @NonNull String dexPath,
+ boolean verifySdmSignatures) {
+ if (!android.content.pm.Flags.cloudCompilationPm()) {
+ return;
+ }
+
+ String sdmPath = getSdmPath(dexPath);
+ String status = "not-found";
+ String signature = "skipped";
+ if (mInjector.fileExists(sdmPath)) {
+ // "Pending" means yet to be picked up by dexopt. For now, "pending" is the only status
+ // because SDM files are not supported yet.
+ status = "pending";
+ // This operation is expensive, so hide it behind a flag.
+ if (verifySdmSignatures) {
+ signature = getSdmSignatureStatus(dexPath, sdmPath);
+ }
+ }
+ ipw.printf("sdm: [sdm-status=%s] [sdm-signature=%s]\n", status, signature);
+ }
+
+ // The new API usage is safe because it's guarded by a flag. The "NewApi" lint is wrong because
+ // it's meaningless (b/380891026). We have to work around the lint error because there is no
+ // `isAtLeastB` to check yet.
+ // TODO(jiakaiz): Remove this workaround, change @FlaggedApi to @RequiresApi here, and check
+ // `isAtLeastB` at the call site after B SDK is finalized.
+ @FlaggedApi(android.content.pm.Flags.FLAG_CLOUD_COMPILATION_PM)
+ @SuppressLint("NewApi")
+ @NonNull
+ private String getSdmSignatureStatus(@NonNull String dexPath, @NonNull String sdmPath) {
+ SigningInfo sdmSigningInfo;
+ try {
+ sdmSigningInfo =
+ mInjector.getVerifiedSigningInfo(sdmPath, SigningInfo.VERSION_SIGNING_BLOCK_V3);
+ } catch (SigningInfoException e) {
+ AsLog.w("Failed to verify SDM signature", e);
+ return "invalid-sdm-signature";
+ }
+
+ SigningInfo apkSigningInfo;
+ try {
+ apkSigningInfo =
+ mInjector.getVerifiedSigningInfo(dexPath, SigningInfo.VERSION_SIGNING_BLOCK_V3);
+ } catch (SigningInfoException e) {
+ AsLog.w("Failed to verify SDM signature", e);
+ return "invalid-apk-signature";
+ }
+
+ if (!sdmSigningInfo.signersMatchExactly(apkSigningInfo)) {
+ return "mismatched-signers";
+ }
+
+ return "verified";
+ }
+
+ @NonNull
+ private static String getSdmPath(@NonNull String dexPath) {
+ return Utils.replaceFileExtension(dexPath, ArtConstants.SECURE_DEX_METADATA_FILE_EXT);
+ }
+
@NonNull
private String getLoaderState(
@NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull DexLoader loader) {
@@ -252,5 +320,18 @@ public class DumpHelper {
public DexUseManagerLocal getDexUseManager() {
return GlobalInjector.getInstance().getDexUseManager();
}
+
+ public boolean fileExists(@NonNull String path) {
+ return new File(path).exists();
+ }
+
+ // TODO(jiakaiz): See another comment about "NewApi" above.
+ @FlaggedApi(android.content.pm.Flags.FLAG_CLOUD_COMPILATION_PM)
+ @SuppressLint("NewApi")
+ @NonNull
+ public SigningInfo getVerifiedSigningInfo(
+ @NonNull String path, int minAppSigningSchemeVersion) throws SigningInfoException {
+ return PackageManager.getVerifiedSigningInfo(path, minAppSigningSchemeVersion);
+ }
}
}
diff --git a/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
index 45a7010d09..24e5ac5216 100644
--- a/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DumpHelperTest.java
@@ -24,12 +24,19 @@ import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptSt
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.pm.SigningInfo;
+import android.content.pm.SigningInfoException;
import android.os.SystemProperties;
import androidx.test.filters.SmallTest;
@@ -70,6 +77,8 @@ public class DumpHelperTest {
@Mock private ArtManagerLocal mArtManagerLocal;
@Mock private DexUseManagerLocal mDexUseManagerLocal;
@Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+ @Mock private SigningInfo mSigningInfoA;
+ @Mock private SigningInfo mSigningInfoB;
private DumpHelper mDumpHelper;
@@ -99,6 +108,11 @@ public class DumpHelperTest {
setUpForBar();
setUpForSdk();
+ lenient().when(mSigningInfoA.signersMatchExactly(mSigningInfoA)).thenReturn(true);
+ lenient().when(mSigningInfoA.signersMatchExactly(mSigningInfoB)).thenReturn(false);
+ lenient().when(mSigningInfoB.signersMatchExactly(mSigningInfoB)).thenReturn(true);
+ lenient().when(mSigningInfoB.signersMatchExactly(mSigningInfoA)).thenReturn(false);
+
mDumpHelper = new DumpHelper(mInjector);
}
@@ -110,12 +124,14 @@ public class DumpHelperTest {
+ " [location is /somewhere/app/foo/oat/arm64/base.odex]\n"
+ " arm: [status=verify] [reason=install]\n"
+ " [location is /somewhere/app/foo/oat/arm/base.odex]\n"
+ + " sdm: [sdm-status=not-found] [sdm-signature=skipped]\n"
+ " path: /somewhere/app/foo/split_0.apk\n"
+ " arm64: [status=verify] [reason=vdex] [primary-abi]\n"
+ " [location is primary.vdex in /somewhere/app/foo/split_0.dm]\n"
+ " arm: [status=verify] [reason=vdex]\n"
+ " [location is primary.vdex in /somewhere/app/foo/split_0.dm]\n"
+ " used by other apps: [com.example2.bar (isa=arm)]\n"
+ + " sdm: [sdm-status=not-found] [sdm-signature=skipped]\n"
+ " known secondary dex files:\n"
+ " /data/user_de/0/foo/1.apk (removed)\n"
+ " arm: [status=run-from-apk] [reason=unknown]\n"
@@ -123,7 +139,8 @@ public class DumpHelperTest {
+ " class loader context: =VaryingClassLoaderContexts=\n"
+ " com.example1.foo (isolated): CLC1\n"
+ " com.example3.baz: CLC2\n"
- + " used by other apps: [com.example1.foo (isolated) (isa=arm64), com.example3.baz (removed)]\n"
+ + " used by other apps: [com.example1.foo (isolated) (isa=arm64), "
+ + "com.example3.baz (removed)]\n"
+ " /data/user_de/0/foo/2.apk (public)\n"
+ " arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]\n"
+ " [location is /data/user_de/0/foo/oat/arm64/2.odex]\n"
@@ -136,20 +153,96 @@ public class DumpHelperTest {
+ " [location is /somewhere/app/bar/oat/arm/base.odex]\n"
+ " arm64: [status=verify] [reason=install]\n"
+ " [location is /somewhere/app/bar/oat/arm64/base.odex]\n"
+ + " sdm: [sdm-status=not-found] [sdm-signature=skipped]\n"
+ "[com.example3.sdk]\n"
+ " path: /somewhere/app/sdk/base.apk\n"
+ " arm: [status=verify] [reason=install] [primary-abi]\n"
+ " [location is /somewhere/app/sdk/oat/arm/base.odex]\n"
+ " arm64: [status=verify] [reason=install]\n"
+ " [location is /somewhere/app/sdk/oat/arm64/base.odex]\n"
+ + " sdm: [sdm-status=not-found] [sdm-signature=skipped]\n"
+ "\n"
+ "Current GC: CollectorTypeCMC\n";
var stringWriter = new StringWriter();
- mDumpHelper.dump(new PrintWriter(stringWriter), mSnapshot);
+ mDumpHelper.dump(new PrintWriter(stringWriter), mSnapshot, false /* verifySdmSignatures */);
assertThat(stringWriter.toString()).isEqualTo(expected);
}
+ @Test
+ public void testDumpSdmStatusNotFound() throws Exception {
+ when(mInjector.fileExists(any())).thenReturn(false);
+
+ var stringWriter = new StringWriter();
+ mDumpHelper.dumpPackage(new PrintWriter(stringWriter), mSnapshot,
+ getPackageState(PKG_NAME_BAR), true /* verifySdmSignatures */);
+ assertThat(stringWriter.toString()).contains("sdm: [sdm-status=not-found]");
+ }
+
+ @Test
+ public void testDumpSdmStatusInvalidSdmSignature() throws Exception {
+ when(mInjector.fileExists("/somewhere/app/bar/base.sdm")).thenReturn(true);
+ when(mInjector.getVerifiedSigningInfo(eq("/somewhere/app/bar/base.sdm"), anyInt()))
+ .thenThrow(SigningInfoException.class);
+
+ var stringWriter = new StringWriter();
+ mDumpHelper.dumpPackage(new PrintWriter(stringWriter), mSnapshot,
+ getPackageState(PKG_NAME_BAR), true /* verifySdmSignatures */);
+ assertThat(stringWriter.toString())
+ .contains("sdm: [sdm-status=pending] [sdm-signature=invalid-sdm-signature]");
+ }
+
+ @Test
+ public void testDumpSdmStatusInvalidApkSignature() throws Exception {
+ when(mInjector.fileExists("/somewhere/app/bar/base.sdm")).thenReturn(true);
+ doReturn(mSigningInfoA)
+ .when(mInjector)
+ .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.sdm"), anyInt());
+ doThrow(SigningInfoException.class)
+ .when(mInjector)
+ .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.apk"), anyInt());
+
+ var stringWriter = new StringWriter();
+ mDumpHelper.dumpPackage(new PrintWriter(stringWriter), mSnapshot,
+ getPackageState(PKG_NAME_BAR), true /* verifySdmSignatures */);
+ assertThat(stringWriter.toString())
+ .contains("sdm: [sdm-status=pending] [sdm-signature=invalid-apk-signature]");
+ }
+
+ @Test
+ public void testDumpSdmStatusSignersNotMatch() throws Exception {
+ when(mInjector.fileExists("/somewhere/app/bar/base.sdm")).thenReturn(true);
+ doReturn(mSigningInfoA)
+ .when(mInjector)
+ .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.sdm"), anyInt());
+ doReturn(mSigningInfoB)
+ .when(mInjector)
+ .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.apk"), anyInt());
+
+ var stringWriter = new StringWriter();
+ mDumpHelper.dumpPackage(new PrintWriter(stringWriter), mSnapshot,
+ getPackageState(PKG_NAME_BAR), true /* verifySdmSignatures */);
+ assertThat(stringWriter.toString())
+ .contains("sdm: [sdm-status=pending] [sdm-signature=mismatched-signers]");
+ }
+
+ @Test
+ public void testDumpSdmStatusVerified() throws Exception {
+ when(mInjector.fileExists("/somewhere/app/bar/base.sdm")).thenReturn(true);
+ doReturn(mSigningInfoA)
+ .when(mInjector)
+ .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.sdm"), anyInt());
+ doReturn(mSigningInfoA)
+ .when(mInjector)
+ .getVerifiedSigningInfo(eq("/somewhere/app/bar/base.apk"), anyInt());
+
+ var stringWriter = new StringWriter();
+ mDumpHelper.dumpPackage(new PrintWriter(stringWriter), mSnapshot,
+ getPackageState(PKG_NAME_BAR), true /* verifySdmSignatures */);
+ assertThat(stringWriter.toString())
+ .contains("sdm: [sdm-status=pending] [sdm-signature=verified]");
+ }
+
private PackageState createPackageState(@NonNull String packageName, int appId, boolean isApex,
boolean hasPackage, @NonNull String primaryAbi, @NonNull String secondaryAbi) {
var pkgState = mock(PackageState.class);
@@ -317,4 +410,9 @@ public class DumpHelperTest {
PKG_NAME_SDK, "/somewhere/app/sdk/base.apk"))
.thenReturn(Set.of());
}
+
+ @SuppressLint("DirectInvocationOnMock")
+ private PackageState getPackageState(String packageName) {
+ return mSnapshot.getPackageState(packageName);
+ }
}