diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt index 48f2c1554c30a..7b3fe7fa73f3a 100644 --- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt +++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt @@ -205,6 +205,7 @@ public class PackageParsingPerfTest { override fun hasFeature(feature: String) = true override fun startParsingPackage( + flags: Int, packageName: String, baseApkPath: String, path: String, diff --git a/core/java/android/app/ActivityThreadHooks.java b/core/java/android/app/ActivityThreadHooks.java index b98b29ce2f1c0..a3f4e51ee6748 100644 --- a/core/java/android/app/ActivityThreadHooks.java +++ b/core/java/android/app/ActivityThreadHooks.java @@ -59,8 +59,8 @@ static Bundle onBind(ActivityThread.AppBindData appBindData) { return args; } - // called after ActivityThread instrumentation is inited, which happens before execution of any - // of app's code + // called after ActivityThread instrumentation is inited, which happens before Application class + // init but after app-overridable AppComponentFactory init // ActivityThread.handleBindApplication static void onBind2(Context appContext, Bundle appBindArgs) { ActivityThreadHooks.appContext = appContext; diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index b1f0fc042391b..38ab51ba6802b 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2735,7 +2735,12 @@ public void setUpdateAvailable(String packageName, boolean updateAvailable) { @Override public String getInstallerPackageName(String packageName) { try { - return mPM.getInstallerPackageName(packageName); + String res = mPM.getInstallerPackageName(packageName); + if (!android.ext.PackageId.PLAY_STORE_NAME.equals(res) + && InstallSourceSpoofingHooks.shouldSpoof(mContext, this, packageName) != null) { + return android.ext.PackageId.PLAY_STORE_NAME; + } + return res; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2753,6 +2758,12 @@ public InstallSourceInfo getInstallSourceInfo(String packageName) throws NameNot if (installSourceInfo == null) { throw new NameNotFoundException(packageName); } + if (!android.ext.PackageId.PLAY_STORE_NAME.equals(installSourceInfo.getInstallingPackageName())) { + var res = InstallSourceSpoofingHooks.shouldSpoof(mContext, this, packageName); + if (res != null) { + return InstallSourceSpoofingHooks.getPlayStoreInstallSourceInfo(res); + } + } return installSourceInfo; } diff --git a/core/java/android/app/InstallSourceSpoofingHooks.java b/core/java/android/app/InstallSourceSpoofingHooks.java new file mode 100644 index 0000000000000..24e5446176087 --- /dev/null +++ b/core/java/android/app/InstallSourceSpoofingHooks.java @@ -0,0 +1,90 @@ +package android.app; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.ApplicationInfoFlags; +import android.content.pm.SigningInfo; +import android.ext.PackageId; +import android.ext.settings.BoolSetting; +import android.ext.settings.Setting; +import android.provider.Settings; +import android.util.Log; + +import java.util.Objects; + +/** @hide */ +public class InstallSourceSpoofingHooks { + static final String TAG = "InstallSourceSpoofingHooks"; + + private static final BoolSetting setting = new BoolSetting(Setting.Scope.PER_USER, + Settings.Secure.SPOOF_INSTALL_SOURCE_FOR_PLAY_STORE_APPS, true); + + // returns Play Store signing info if spoofing should be performed + static SigningInfo shouldSpoof(Context context, PackageManager pm, String pkgName) { + SigningInfo res = shouldSpoofInner(context, pm, pkgName); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "shouldSpoof result for " + pkgName + ": " + res); + } + return res; + } + + private static SigningInfo shouldSpoofInner(Context context, PackageManager pm, String pkgName) { + ApplicationInfo selfAppInfo = context.getApplicationInfo(); + if (selfAppInfo.isSystemApp()) { + return null; + } + switch (selfAppInfo.ext().getPackageId()) { + case PackageId.GMS_CORE: + case PackageId.PLAY_STORE: + return null; + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "shouldSpoof called for " + pkgName, new Throwable()); + } + if (!setting.get(context)) { + return null; + } + try { + ApplicationInfo appInfo = pm.getApplicationInfo(pkgName, + ApplicationInfoFlags.of(PackageManager.GET_PLAY_STORE_SOURCE_STAMP_STATE)); + if (appInfo.isSystemApp() || (!appInfo.hasPlayStoreSourceStamp() && !appInfo.ext().hasCompatChange( + com.android.server.os.nano.AppCompatProtos.SPOOF_INSTALLER_CHECKS))) { + return null; + } + } catch (PackageManager.NameNotFoundException e) { + return null; + } + PackageInfo playStorePkgInfo; + try { + playStorePkgInfo = pm.getPackageInfo(PackageId.PLAY_STORE_NAME, + PackageManager.GET_SIGNING_CERTIFICATES); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + ApplicationInfo playStoreAppInfo = playStorePkgInfo.applicationInfo; + if (playStoreAppInfo == null) { + return null; + } + if (playStoreAppInfo.ext().getPackageId() != PackageId.PLAY_STORE) { + return null; + } + return Objects.requireNonNull(playStorePkgInfo.signingInfo); + } + + static InstallSourceInfo getPlayStoreInstallSourceInfo(SigningInfo playStoreSigningInfo) { + return new InstallSourceInfo( + PackageId.PLAY_STORE_NAME, // initiatingPackageName + playStoreSigningInfo, // initiatingPackageSigningInfo + null, // originatingPackageName, which is hidden from packages without the + // privileged INSTALL_PACKAGES permission. Spoofing of installer checks can be + // enabled only for third-party apps, which are always unprivileged. + PackageId.PLAY_STORE_NAME, // installingPackageName + null, // updateOwnerPackageName + // Play Store doesn't set the package source value + PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED); + } +} diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 57af3320ff795..3a445027efef3 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -2139,6 +2139,7 @@ public ApplicationInfo() { public ApplicationInfo(ApplicationInfo orig) { super(orig); ext = orig.ext; + playStoreSourceStampPresence = orig.playStoreSourceStampPresence; taskAffinity = orig.taskAffinity; permission = orig.permission; mKnownActivityEmbeddingCerts = orig.mKnownActivityEmbeddingCerts; @@ -2239,6 +2240,7 @@ public void writeToParcel(Parcel dest, int parcelableFlags) { final int preWriteSize = dest.dataSize(); super.writeToParcel(dest, parcelableFlags); ext.writeToParcel(dest, parcelableFlags); + dest.writeInt(playStoreSourceStampPresence); dest.writeString8(taskAffinity); dest.writeString8(permission); dest.writeString8(processName); @@ -2371,6 +2373,7 @@ public ApplicationInfo[] newArray(int size) { private ApplicationInfo(Parcel source) { super(source); ext = AppInfoExt.CREATOR.createFromParcel(source); + playStoreSourceStampPresence = source.readInt(); taskAffinity = source.readString8(); permission = source.readString8(); processName = source.readString8(); @@ -3222,31 +3225,26 @@ public void setExt(AppInfoExt ext) { return ext; } - private static volatile Boolean hasPlayStoreSourceStamp; + private int playStoreSourceStampPresence; + private static final int PLAY_STORE_SOURCE_STAMP_NOT_PRESENT = -1; + private static final int PLAY_STORE_SOURCE_STAMP_PRESENCE_UNKNOWN = 0; + private static final int PLAY_STORE_SOURCE_STAMP_PRESENT = 1; /** @hide */ - public boolean hasPlayStoreSourceStamp() { - Boolean cache = hasPlayStoreSourceStamp; - if (cache != null) { - return cache.booleanValue(); - } + public void setPlayStoreSourceStampPresent(boolean present) { + playStoreSourceStampPresence = present ? PLAY_STORE_SOURCE_STAMP_PRESENT : PLAY_STORE_SOURCE_STAMP_NOT_PRESENT; + } - var apkPaths = new ArrayList(); - apkPaths.add(Objects.requireNonNull(sourceDir)); - String[] splits = splitSourceDirs; - if (splits != null) { - for (String splitPath : splits) { - apkPaths.add(Objects.requireNonNull(splitPath)); + /** @hide */ + public boolean hasPlayStoreSourceStamp() { + if (playStoreSourceStampPresence == PLAY_STORE_SOURCE_STAMP_PRESENCE_UNKNOWN) { + String msg = "playStoreSourceStampPresence is unknown; check whether PackageManager.GET_PLAY_STORE_SOURCE_STAMP_STATE flag is used"; + if (android.os.Flags.isDevBuild()) { + throw new RuntimeException(msg); + } else { + Log.e(TAG, msg); } } - byte[] playStoreSourceStampCertDigest = java.util.HexFormat.of().parseHex( - "3257d599a49d2c961a471ca9843f59d341a405884583fc087df4237b733bbd6d"); - boolean result = android.util.apk.SourceStampVerifier - .verify(apkPaths, /* requiredSourceStamp */ playStoreSourceStampCertDigest) - .isVerified(); - hasPlayStoreSourceStamp = Boolean.valueOf(result); - android.util.Log.d("PlayStoreSourceStampCheck", - "result for " + packageName + ": " + result); - return result; + return playStoreSourceStampPresence == PLAY_STORE_SOURCE_STAMP_PRESENT; } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 4360a8a45d117..4b5b4e105eb33 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -974,6 +974,8 @@ default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) { MATCH_DIRECT_BOOT_AWARE, MATCH_DIRECT_BOOT_UNAWARE, GET_ATTRIBUTIONS_LONG, + CACHE_SIGNING_CERTIFICATE_DIGESTS, + GET_PLAY_STORE_SOURCE_STAMP_STATE, }) @Retention(RetentionPolicy.SOURCE) public @interface PackageInfoFlagsBits {} @@ -995,6 +997,7 @@ default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) { MATCH_APEX, MATCH_ARCHIVED_PACKAGES, GET_APP_LOCK_INFO, + GET_PLAY_STORE_SOURCE_STAMP_STATE, }) @Retention(RetentionPolicy.SOURCE) public @interface ApplicationInfoFlagsBits {} @@ -1467,6 +1470,11 @@ default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) { @FlaggedApi(android.security.Flags.FLAG_APP_LOCK_APIS) public static final long GET_APP_LOCK_INFO = 1L << 35; + /** @hide */ + public static final long CACHE_SIGNING_CERTIFICATE_DIGESTS = 1L << 55; + /** @hide */ + public static final long GET_PLAY_STORE_SOURCE_STAMP_STATE = 1L << 56; + //------------------------------------------------------------------------- // End of GET_ and MATCH_ flags //------------------------------------------------------------------------- diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java index 3e032872c5e22..e4bb45091bc76 100644 --- a/core/java/android/content/pm/Signature.java +++ b/core/java/android/content/pm/Signature.java @@ -216,6 +216,22 @@ public byte[] toByteArray() { return bytes; } + @Nullable + private volatile byte[] mSha256Digest; + + /** @hide */ + @NonNull + public byte[] getSha256Digest() { + byte[] cache = mSha256Digest; + if (cache != null) { + return cache; + } + byte[] result = android.util.PackageUtils.computeSha256DigestBytes(mSignature); + java.util.Objects.requireNonNull(result); + mSha256Digest = result; + return result; + } + /** * Returns the public key for this signature. * @@ -286,6 +302,7 @@ public int describeContents() { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeByteArray(mSignature); + dest.writeByteArray(mSha256Digest); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR @@ -307,6 +324,7 @@ public void writeToXmlAttributeBytesHex(@NonNull TypedXmlSerializer out, private Signature(Parcel source) { mSignature = source.createByteArray(); + mSha256Digest = source.createByteArray(); } /** diff --git a/core/java/android/content/pm/SigningDetails.java b/core/java/android/content/pm/SigningDetails.java index 6b0c1ddbd6add..7133a293b50f2 100644 --- a/core/java/android/content/pm/SigningDetails.java +++ b/core/java/android/content/pm/SigningDetails.java @@ -1021,8 +1021,7 @@ private boolean hasSha256CertificateInternal(byte[] sha256Certificate, int flags // check all past certs, except for the last one, which automatically gets all // capabilities, since it is the same as the current signature, and is checked below for (int i = 0; i < mPastSigningCertificates.length - 1; i++) { - byte[] digest = PackageUtils.computeSha256DigestBytes( - mPastSigningCertificates[i].toByteArray()); + byte[] digest = mPastSigningCertificates[i].getSha256Digest(); if (Arrays.equals(sha256Certificate, digest)) { if (flags == PAST_CERT_EXISTS || (flags & mPastSigningCertificates[i].getFlags()) == flags) { @@ -1034,7 +1033,7 @@ private boolean hasSha256CertificateInternal(byte[] sha256Certificate, int flags // not in previous certs signing history, just check the current signer if (mSignatures.length == 1) { - byte[] digest = PackageUtils.computeSha256DigestBytes(mSignatures[0].toByteArray()); + byte[] digest = mSignatures[0].getSha256Digest(); return Arrays.equals(sha256Certificate, digest); } return false; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bcf423537bb66..78f1d7e75232a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7523,6 +7523,11 @@ public static final class Secure extends NameValueTable { @Protected(readWrite = KnownSystemPackage.SETTINGS) public static final String DISALLOW_DELAYED_LOCKING_ON_USER_STOP = "disallow_delayed_locking_on_user_stop"; + /** @hide */ + @Readable + @Protected(restrictReads = false, readWrite = KnownSystemPackage.SETTINGS) + public static final String SPOOF_INSTALL_SOURCE_FOR_PLAY_STORE_APPS = "spoof_install_source_for_play_store_apps"; + // ExtSettings END // NOTE: If you add new settings here, be sure to add them to diff --git a/core/java/com/android/internal/app/PairipHooks.java b/core/java/com/android/internal/app/PairipHooks.java index 6ddd1259fcf35..9c22ad285816b 100644 --- a/core/java/com/android/internal/app/PairipHooks.java +++ b/core/java/com/android/internal/app/PairipHooks.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.ApplicationInfoFlags; import android.ext.PackageId; import android.os.RemoteException; import android.provider.Settings; @@ -55,18 +56,26 @@ private static boolean shouldBypass(Context context) { if (cache != null) { return cache.booleanValue(); } - - boolean res = context.getApplicationInfo().hasPlayStoreSourceStamp(); + PackageManager pm = context.getPackageManager(); + String selfPkgName = context.getPackageName(); + ApplicationInfo selfAppInfo; + try { + selfAppInfo = pm.getApplicationInfo(selfPkgName, + ApplicationInfoFlags.of(PackageManager.GET_PLAY_STORE_SOURCE_STAMP_STATE)); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException(e); + } + boolean res = selfAppInfo.hasPlayStoreSourceStamp(); if (res) { boolean installedFromPlayStore = false; String installerPkg; try { - installerPkg = AppGlobals.getPackageManager().getInstallerPackageName(context.getPackageName()); + // ApplicationPackageManager result might be spoofed, see InstallSourceSpoofingHooks + installerPkg = AppGlobals.getPackageManager().getInstallerPackageName(selfPkgName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } if (PackageId.PLAY_STORE_NAME.equals(installerPkg)) { - PackageManager pm = context.getPackageManager(); try { ApplicationInfo ai = pm.getApplicationInfo(PackageId.PLAY_STORE_NAME, 0); installedFromPlayStore = ai.ext().getPackageId() == PackageId.PLAY_STORE; diff --git a/core/java/com/android/internal/pm/parsing/PackageParser2.java b/core/java/com/android/internal/pm/parsing/PackageParser2.java index 1f4ab65874fb5..6d30b3e7c4af4 100644 --- a/core/java/com/android/internal/pm/parsing/PackageParser2.java +++ b/core/java/com/android/internal/pm/parsing/PackageParser2.java @@ -188,14 +188,23 @@ public void close() { public abstract static class Callback implements ParsingPackageUtils.Callback { + private static volatile boolean idOwnershipChecksEnabled; + + public static void enableIdOwnershipChecks() { + idOwnershipChecksEnabled = true; + } + @Override - public final ParsingPackage startParsingPackage(@NonNull String packageName, + public final ParsingPackage startParsingPackage(int flags, @NonNull String packageName, @NonNull String baseCodePath, @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) { - var res = PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray, + var pkg = PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray, isCoreApp, Callback.this); - res.initPackageParsingHooks(); - return res; + if ((flags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0 && idOwnershipChecksEnabled) { + pkg.enableIdOwnershipChecks(); + } + pkg.initPackageParsingHooks(); + return pkg; } /** diff --git a/core/java/com/android/internal/pm/parsing/PackageParserConfig.java b/core/java/com/android/internal/pm/parsing/PackageParserConfig.java new file mode 100644 index 0000000000000..421fbc1e94555 --- /dev/null +++ b/core/java/com/android/internal/pm/parsing/PackageParserConfig.java @@ -0,0 +1,180 @@ +package com.android.internal.pm.parsing; + +import android.annotation.Nullable; +import android.os.Build; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.pm.parsing.nano.ApkParserConfig; +import com.android.internal.pm.parsing.nano.ApcPackageConfig; +import com.android.internal.pm.parsing.nano.PackageInstallRequirements; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.parsing.ParsingPackage; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; + +public class PackageParserConfig { + public static final String TAG = "PackageParserConfig"; + + private ApkParserConfig config; + private HashSet nonInstallablePackages; + + private static volatile PackageParserConfig instance; + + public static PackageParserConfig get() { + PackageParserConfig res = instance; + if (res == null) { + throw new RuntimeException("PackageParserConfig was not initialized"); + } + return res; + } + + public static void init() { + Log.d(TAG, "init"); + + ApkParserConfig res; + try { + byte[] protobuf = Files.readAllBytes(Path.of("/product/etc/apk-parser-config.pb")); + res = ApkParserConfig.parseFrom(protobuf); + } catch (IOException e) { + if (Build.IS_DEBUGGABLE || Build.IS_EMULATOR) { + if (!Build.IS_EMULATOR) { + Slog.e(TAG, "", e); + } + res = new ApkParserConfig(); + } else { + // apk-parser-config.pb is a trusted part of the OS + throw new SecurityException(e); + } + } + if (res.permissionOwners == null) { + res.permissionOwners = Map.of(); + } + if (res.permissionGroupOwners == null) { + res.permissionGroupOwners = Map.of(); + } + if (res.contentProviderAuthorityOwners == null) { + res.contentProviderAuthorityOwners = Map.of(); + } + if (res.parsingConfigs == null) { + res.parsingConfigs = Map.of(); + } + if (res.installablePackages == null) { + res.installablePackages = Map.of(); + } + + var ppc = new PackageParserConfig(); + ppc.config = res; + ppc.nonInstallablePackages = new HashSet<>(Arrays.asList(res.nonInstallablePackages)); + + HashSet packages = new HashSet<>(ppc.nonInstallablePackages); + for (String pkg : res.installablePackages.keySet()) { + packages.add(pkg); + } + for (String pkg : res.permissionOwners.values()) { + if (!packages.contains(pkg)) { + throw new SecurityException(pkg); + } + } + for (String pkg : res.permissionGroupOwners.values()) { + if (!packages.contains(pkg)) { + throw new SecurityException(pkg); + } + } + for (String pkg : res.contentProviderAuthorityOwners.values()) { + if (!packages.contains(pkg)) { + throw new SecurityException(pkg); + } + } + for (String pkg : res.parsingConfigs.keySet()) { + if (!packages.contains(pkg)) { + throw new SecurityException(pkg); + } + } + + instance = ppc; + } + + public boolean isInstallationBlocked(String pkgName) { + return nonInstallablePackages.contains(pkgName); + } + + public void checkPermissionOwnership(ParsingPackage pkg, ParsedPermission permission) { + Map ownershipMap = config.permissionOwners; + String name = permission.getName(); + { + String ownerPkgName = ownershipMap.get(name); + if (ownerPkgName != null) { + String pkgName = pkg.getPackageName(); + if (!ownerPkgName.equals(pkgName)) { + pkg.recordIdOwnershipViolation("permission " + name + " is owned by " + ownerPkgName); + } + } + } + if (permission.isTree()) { + String prefix = name + '.'; + for (String permName : ownershipMap.keySet()) { + if (!permName.startsWith(prefix)) { + continue; + } + String ownerPkgName = ownershipMap.get(permName); + String pkgName = pkg.getPackageName(); + if (!ownerPkgName.equals(pkgName)) { + pkg.recordIdOwnershipViolation("permission-tree " + name + " conflicts with " + permName + " which is owned by " + ownerPkgName); + } + } + } + } + + public void checkPermissionGroupOwnership(ParsingPackage pkg, ParsedPermissionGroup permissionGroup) { + Map ownershipMap = config.permissionGroupOwners; + String name = permissionGroup.getName(); + String ownerPkgName = ownershipMap.get(name); + if (ownerPkgName != null) { + String pkgName = pkg.getPackageName(); + if (!ownerPkgName.equals(pkgName)) { + pkg.recordIdOwnershipViolation("permission-group " + name + " is owned by " + ownerPkgName); + } + } + } + + public void checkContentProviderAuthorityOwnership(ParsingPackage pkg, ParsedProvider provider) { + String authority = provider.getAuthority(); + if (authority == null) { + return; + } + Map ownershipMap = config.contentProviderAuthorityOwners; + String pkgName = pkg.getPackageName(); + + for (String auth : authority.split(";")) { + String authOwnerPkgName = ownershipMap.get(auth); + if (authOwnerPkgName == null) { + continue; + } + if (!authOwnerPkgName.equals(pkgName)) { + pkg.recordIdOwnershipViolation("provider " + provider.getName() + " has authority " + auth + " which is owned by " + authOwnerPkgName); + } + } + } + + @Nullable + public ApcPackageConfig getApcPackageConfig(String pkgName) { + return config.parsingConfigs.get(pkgName); + } + + @Nullable + public PackageInstallRequirements getPackageInstallRequirements(String pkgName) { + return config.installablePackages.get(pkgName); + } + + public static boolean hasApcFlag(ApcPackageConfig config, int flag) { + return (config.flags & (1L << flag)) != 0; + } +} diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java index 08eeaa36fa817..d8885a1ea3b7a 100644 --- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java +++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java @@ -24,6 +24,7 @@ import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityThread; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; @@ -45,6 +46,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.util.SparseIntArray; @@ -52,6 +54,8 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.pm.parsing.AppInfoUtils; +import com.android.internal.pm.parsing.PackageParserConfig; +import com.android.internal.pm.parsing.nano.ApcPackageConfig; import com.android.internal.pm.pkg.AndroidPackageSplitImpl; import com.android.internal.pm.pkg.SEInfoUtil; import com.android.internal.pm.pkg.component.ComponentMutateUtils; @@ -103,7 +107,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.function.Function; @@ -610,14 +613,27 @@ public ParsingPackage addOverlayable(String overlayableName, String actorName) { return this; } + @Override + public boolean areIdOwnershipChecksEnabled() { + return idOwnershipChecksEnabled; + } + @Override public PackageImpl addPermission(ParsedPermission permission) { + if (areIdOwnershipChecksEnabled()) { + PackageParserConfig.get().checkPermissionOwnership(this, permission); + } + this.permissions = CollectionUtils.add(this.permissions, permission); return this; } @Override public PackageImpl addPermissionGroup(ParsedPermissionGroup permissionGroup) { + if (areIdOwnershipChecksEnabled()) { + PackageParserConfig.get().checkPermissionGroupOwnership(this, permissionGroup); + } + this.permissionGroups = CollectionUtils.add(this.permissionGroups, permissionGroup); return this; } @@ -651,9 +667,14 @@ public PackageImpl addProtectedBroadcast(String protectedBroadcast) { @Override public PackageImpl addProvider(ParsedProvider parsedProvider) { if (getPackageParsingHooks().shouldSkipProvider(parsedProvider)) { + Log.d("PackageParsingHooks", "skipped provider " + parsedProvider.getAuthority() + " in " + getPackageName()); return this; } + if (areIdOwnershipChecksEnabled()) { + PackageParserConfig.get().checkContentProviderAuthorityOwnership(this, parsedProvider); + } + this.providers = CollectionUtils.add(this.providers, parsedProvider); addMimeGroupsFromComponent(parsedProvider); return this; @@ -4066,13 +4087,25 @@ private static class Booleans2 { } private PackageParsingHooks packageParsingHooks = PackageParsingHooks.DEFAULT; + @Nullable + private ApcPackageConfig apcPackageConfig; public static Function packageParsingHooksSupplier; @Override public void initPackageParsingHooks() { var supplier = packageParsingHooksSupplier; - packageParsingHooks = supplier != null ? supplier.apply(getPackageName()) : PackageParsingHooks.DEFAULT; + String pkgName = getPackageName(); + packageParsingHooks = supplier != null ? supplier.apply(pkgName) : PackageParsingHooks.DEFAULT; + if (ActivityThread.isSystem()) { + apcPackageConfig = PackageParserConfig.get().getApcPackageConfig(pkgName); + } + } + + @Nullable + @Override + public ApcPackageConfig getApcPackageConfig() { + return apcPackageConfig; } @Override @@ -4080,6 +4113,27 @@ public PackageParsingHooks getPackageParsingHooks() { return packageParsingHooks; } + private boolean idOwnershipChecksEnabled; + private final ArrayList idOwnershipViolations = new ArrayList<>(); + + public void enableIdOwnershipChecks() { + idOwnershipChecksEnabled = true; + } + + @Override + public void recordIdOwnershipViolation(String text) { + synchronized (idOwnershipViolations) { + idOwnershipViolations.add(text); + } + } + + @Override + public String[] getIdOwnershipViolations() { + synchronized (idOwnershipViolations) { + return idOwnershipViolations.toArray(String[]::new); + } + } + private PackageExtIface ext = PackageExtDefault.INSTANCE; @Override @@ -4093,6 +4147,30 @@ public PackageExtIface ext() { return ext; } + private volatile Boolean hasPlayStoreSourceStamp; + + @Override + public boolean hasPlayStoreSourceStamp() { + Boolean cache = hasPlayStoreSourceStamp; + if (cache != null) { + return cache.booleanValue(); + } + + var apkPaths = new ArrayList(); + apkPaths.add(getBaseApkPath()); + apkPaths.addAll(Arrays.asList(getSplitCodePaths())); + + byte[] playStoreSourceStampCertDigest = java.util.HexFormat.of().parseHex( + "3257d599a49d2c961a471ca9843f59d341a405884583fc087df4237b733bbd6d"); + boolean result = android.util.apk.SourceStampVerifier + .verify(apkPaths, /* requiredSourceStamp */ playStoreSourceStampCertDigest) + .isVerified(); + hasPlayStoreSourceStamp = Boolean.valueOf(result); + android.util.Slog.d("PlayStoreSourceStampCheck", + "result for " + packageName + ": " + result); + return result; + } + public long cachedCompatConfigVersionCode; public Object cachedCompatConfig; } diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java index 3beb7174220a3..5696a3a2bfcd1 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java @@ -31,6 +31,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; +import com.android.internal.pm.parsing.nano.ApcPackageConfig; import com.android.internal.pm.parsing.pkg.PackageExtIface; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivity; @@ -636,10 +637,19 @@ ParsingPackage setResetEnabledSettingsOnAppDataCleared( void initPackageParsingHooks(); + @Nullable + ApcPackageConfig getApcPackageConfig(); + default PackageParsingHooks getPackageParsingHooks() { return PackageParsingHooks.DEFAULT; } + boolean areIdOwnershipChecksEnabled(); + + void recordIdOwnershipViolation(String text); + + String[] getIdOwnershipViolations(); + void setPackageExt(@Nullable PackageExtIface ext); boolean isDeclaredHavingCode(); diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index 699072c44a873..fc6d4dd21152c 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -84,6 +84,7 @@ import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -327,6 +328,13 @@ public ParsingPackageUtils(String[] separateProcesses, DisplayMetrics displayMet mCallback = callback; } + @Nullable + public static PackageIdOwnershipChecksIface packageIdOwnershipChecks; + + public interface PackageIdOwnershipChecksIface { + ParseResult run(ParseInput input, ParsingPackage pkg); + } + /** * Parse the package at the given location. Automatically detects if the package is a monolithic * style (single APK file) or cluster style (directory of APKs). @@ -343,11 +351,22 @@ public ParsingPackageUtils(String[] separateProcesses, DisplayMetrics displayMet * do so. */ public ParseResult parsePackage(ParseInput input, File packageFile, int flags) { + ParseResult result; if (packageFile.isDirectory()) { - return parseClusterPackage(input, packageFile, flags); + result = parseClusterPackage_(input, packageFile, flags); } else { - return parseMonolithicPackage(input, packageFile, flags); + result = parseMonolithicPackage_(input, packageFile, flags); } + if ((flags & PARSE_IS_SYSTEM_DIR) == 0 && result.isSuccess()) { + PackageIdOwnershipChecksIface check = packageIdOwnershipChecks; + if (check != null) { + ParseResult override = check.run(input, result.getResult()); + if (override != null) { + return override; + } + } + } + return result; } /** @@ -359,7 +378,7 @@ public ParseResult parsePackage(ParseInput input, File packageFi * Note that this does not perform signature verification; that must be done separately * in {@link #getSigningDetails(ParseInput, ParsedPackage, boolean)}. */ - private ParseResult parseClusterPackage(ParseInput input, File packageDir, + private ParseResult parseClusterPackage_(ParseInput input, File packageDir, int flags) { int liteParseFlags = 0; if ((flags & PARSE_APK_IN_APEX) != 0) { @@ -434,7 +453,7 @@ private ParseResult parseClusterPackage(ParseInput input, File p * Note that this does not perform signature verification; that must be done separately * in {@link #getSigningDetails(ParseInput, ParsedPackage, boolean)}. */ - private ParseResult parseMonolithicPackage(ParseInput input, File apkFile, + private ParseResult parseMonolithicPackage_(ParseInput input, File apkFile, int flags) { // The signature parsing will be done later in method parseBaseApk. int liteParseFlags = flags & ~PARSE_COLLECT_CERTIFICATES; @@ -477,7 +496,7 @@ public ParseResult parsePackageFromPackageLite(ParseInput input, final String pkgName = lite.getPackageName(); final TypedArray manifestArray = null; - final ParsingPackage pkg = mCallback.startParsingPackage(pkgName, + final ParsingPackage pkg = mCallback.startParsingPackage(flags, pkgName, lite.getBaseApkPath(), lite.getPath(), manifestArray, lite.isCoreApp()); final int targetSdk = lite.getTargetSdk(); @@ -777,7 +796,7 @@ private ParseResult parseBaseApk(ParseInput input, String apkPat try { final boolean isCoreApp = parser.getAttributeBooleanValue(null /*namespace*/, "coreApp", false); - final ParsingPackage pkg = mCallback.startParsingPackage( + final ParsingPackage pkg = mCallback.startParsingPackage(flags, pkgName, apkPath, codePath, manifestArray, isCoreApp); final ParseResult result = parseBaseApkTags(input, pkg, manifestArray, res, parser, flags, @@ -1582,7 +1601,13 @@ private ParseResult parseUsesPermission(ParseInput input, if (!found) { var p = new ParsedUsesPermissionImpl(name, usesPermissionFlags, purposeStringResource, purposes, generalPurposes); - if (!pkg.getPackageParsingHooks().shouldSkipUsesPermission(p)) { + var config = pkg.getApcPackageConfig(); + if (config != null && ArrayUtils.contains(config.usesPermissionsToIgnore, name)) { + // ArrayUtils.contains is O(n), but this array is expected to be small + Log.d(TAG, "PackageParserConfig: " + pkg.getPackageName() + ": skipped uses-permission " + name); + } else if (pkg.getPackageParsingHooks().shouldSkipUsesPermission(p)) { + Log.d(TAG, "PackageParsingHooks: " + pkg.getPackageName() + ": skipped uses-permission " + name); + } else { pkg.addUsesPermission(p); } } @@ -3756,7 +3781,7 @@ public static ArrayMap> readKeySetMapping(@NonNull P public interface Callback { boolean hasFeature(String feature); - ParsingPackage startParsingPackage(@NonNull String packageName, + ParsingPackage startParsingPackage(int flags, @NonNull String packageName, @NonNull String baseApkPath, @NonNull String path, @NonNull TypedArray manifestArray, boolean isCoreApp); diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java index c80f34502b8f6..5ed38c7ca892c 100644 --- a/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -48,6 +48,7 @@ import android.util.SparseIntArray; import com.android.internal.R; +import com.android.internal.pm.parsing.nano.ApcPackageConfig; import com.android.internal.pm.parsing.pkg.PackageExtIface; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedAllowComponentAccessPolicy; @@ -1556,10 +1557,18 @@ public interface AndroidPackage { */ int getIntentMatchingFlags(); + /** @hide */ + boolean hasPlayStoreSourceStamp(); + /** @hide */ @Immutable.Ignore PackageExtIface ext(); + /** @hide */ + @Immutable.Ignore + @Nullable + ApcPackageConfig getApcPackageConfig(); + /** * Returns true if this application has any component that should run in * the Private Compute Core sandbox. diff --git a/core/res/res/values/public-ext.xml b/core/res/res/values/public-ext.xml index f53b1ec17b23e..356a221c8b5b6 100644 --- a/core/res/res/values/public-ext.xml +++ b/core/res/res/values/public-ext.xml @@ -135,5 +135,7 @@ + + diff --git a/core/res/res/values/string_ext.xml b/core/res/res/values/string_ext.xml index 12fbc7f26e3b6..4c8e3bd33456d 100644 --- a/core/res/res/values/string_ext.xml +++ b/core/res/res/values/string_ext.xml @@ -53,4 +53,7 @@ USB-C port security feature has malfunctioned + %1$s was blocked from claiming a reserved name + + diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt index 99b2524f0f9e6..6ca8962a4bc12 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt @@ -39,9 +39,10 @@ import androidx.compose.ui.unit.DpOffset import com.android.settingslib.spa.framework.theme.SettingsDimension @Composable -fun CopyableBody(body: String) { +fun CopyableBody(bodyCharSequence: CharSequence, showDropdownTitle: Boolean = true) { var expanded by remember { mutableStateOf(false) } var dpOffset by remember { mutableStateOf(DpOffset.Unspecified) } + val body = remember(bodyCharSequence) { bodyCharSequence.toString() } Box(modifier = Modifier .fillMaxWidth() @@ -54,14 +55,16 @@ fun CopyableBody(body: String) { ) } ) { - SettingsBody(body) + SettingsBody(bodyCharSequence) DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, offset = dpOffset, ) { - DropdownMenuTitle(body) + if (showDropdownTitle) { + DropdownMenuTitle(body) + } DropdownMenuCopy(body) { expanded = false } } } diff --git a/packages/SettingsLib/SpaPrivileged/res/values/strings_ext.xml b/packages/SettingsLib/SpaPrivileged/res/values/strings_ext.xml index 4a867c5a1c206..a5ae0b4cfb331 100644 --- a/packages/SettingsLib/SpaPrivileged/res/values/strings_ext.xml +++ b/packages/SettingsLib/SpaPrivileged/res/values/strings_ext.xml @@ -1,5 +1,7 @@ - Installed: %1$s - Updated: %1$s + installed: %1$s + updated: %1$s + app certificate SHA-256 digest: + has Play Store source stamp diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index 3cfce4bc8431c..f2b5bf089e9f8 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -36,6 +36,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import com.android.settingslib.development.DevelopmentSettingsEnabler import com.android.settingslib.spa.framework.compose.rememberDrawablePainter @@ -131,7 +136,7 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { if (footer.isBlank()) return if (!isSpaExpressiveEnabled) HorizontalDivider() Box(modifier = Modifier.padding(SettingsDimension.footerPadding)) { - CopyableBody(footer) + CopyableBody(footer, showDropdownTitle = false) } } @@ -147,7 +152,12 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { get() = BidiFormatter.getInstance().unicodeWrap(versionName) } - private fun getFooterText(ctx: Context): String { + enum class FormattingCommand { + MonospaceTextStart, + MonospaceTextEnd, + } + + private fun getFooterText(ctx: Context): AnnotatedString { val pi = packageInfo val dateFormat = android.text.format.DateFormat.getMediumDateFormat(ctx) @@ -160,17 +170,17 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { val appInfo = pi.applicationInfo - val lines = mutableListOf() + val lines = mutableListOf() + lines.add(pi.packageName) pi.versionNameBidiWrapped?.let { lines.add(ctx.getString(R.string.version_text, it)) - lines.add("") } - lines.add(pi.packageName) lines.add("versionCode ${pi.getLongVersionCode()}") - lines.add("") if (appInfo != null) { - lines.add("targetSdk ${appInfo.targetSdkVersion}") - lines.add("minSdk ${appInfo.minSdkVersion}") + lines.add("targetSdk ${appInfo.targetSdkVersion} | minSdk ${appInfo.minSdkVersion}") + if (appInfo.hasPlayStoreSourceStamp()) { + lines.add(ctx.getString(R.string.app_info_has_play_store_source_stamp)) + } } // some system apps report being installed in January 2009, skip showing install time for them @@ -188,13 +198,65 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { if (pi.lastUpdateTime != pi.firstInstallTime) { if (!addedBlankLineBeforeTime) { lines.add("") - addedBlankLineBeforeTime = true } val s = formatDate(pi.lastUpdateTime, dateFormat, timeFormat) lines.add(ctx.getString(R.string.app_info_update_time, s)) } - return lines.joinToString(separator = System.lineSeparator()) + val signingInfo = pi.signingInfo + if (signingInfo != null) { + val certs = if (signingInfo.hasMultipleSigners()) { + signingInfo.apkContentsSigners + } else { + val certHistory = signingInfo.signingCertificateHistory + if (certHistory != null) { + arrayOf(certHistory.last()) + } else { + null + } + } + if (certs != null) { + lines.add("") + val hexFormat = HexFormat { + bytes { + bytesPerGroup = 2 + groupSeparator = " " + } + } + certs.forEach { + lines.add(ctx.getString(R.string.app_info_apk_cert_digest)) + val digest = it.sha256Digest + check(digest.size == 32) + lines.add(FormattingCommand.MonospaceTextStart) + lines.add(digest.copyOfRange(0, 16).toHexString(hexFormat)) + lines.add(digest.copyOfRange(16, 32).toHexString(hexFormat)) + lines.add(FormattingCommand.MonospaceTextEnd) + } + } + } + + return buildAnnotatedString { + var start = -1 + lines.forEachIndexed { idx, obj -> + when { + obj is String -> { + if (length != 0) { + append('\n') + } + append(obj) + } + obj == FormattingCommand.MonospaceTextStart -> { + start = length + } + obj == FormattingCommand.MonospaceTextEnd -> { + addStyle(SpanStyle(fontFamily = FontFamily.Monospace), start, length) + } + else -> { + error("unknown command $obj") + } + } + } + } } } diff --git a/proto/src/apk_parser_config.proto b/proto/src/apk_parser_config.proto new file mode 100644 index 0000000000000..5a5d8e4eca9d8 --- /dev/null +++ b/proto/src/apk_parser_config.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package com.android.internal.pm.parsing; +option java_multiple_files = true; + +message ApcPackageConfig { // Apc is short for "APK parser config" + enum Flag { + // enables bidirectional AppsFilter isolation from GmsCore and Finsky (Play Store) + FLAG_ISOLATE_FROM_GMSCORE_AND_FINSKY = 0; + // enables bidirectional AppsFilter isolation from all user apps + FLAG_ISOLATE_FROM_USER_APPS = 1; + // makes adevtool include priv-app packages as regular unprivileged packages + FLAG_INCLUDE_AS_UNTRUSTED_APP = 2; + } + + repeated string uses_permissions_to_ignore = 1; + int64 flags = 2; +} + +message PackageInstallRequirements { + string cert_sha256 = 1; + int64 min_version = 2; +} + +message ApkParserConfig { + // permission name -> package name + map permission_owners = 1; + // permission group name -> package name + map permission_group_owners = 2; + // content provider authority -> package name + map content_provider_authority_owners = 3; + // packages from stock OS that are not allowed to be installed + repeated string non_installable_packages = 4; + // map keys are package names + map parsing_configs = 5; + // packages from stock OS that can be installed if requirements are met; map keys are package names + map installable_packages = 6; +} + diff --git a/services/core/java/com/android/server/ext/DropBoxMonitor.java b/services/core/java/com/android/server/ext/DropBoxMonitor.java index 04742b15b9243..0aacfb1f31a19 100644 --- a/services/core/java/com/android/server/ext/DropBoxMonitor.java +++ b/services/core/java/com/android/server/ext/DropBoxMonitor.java @@ -301,7 +301,7 @@ void handleFsckEntry(DropBoxManager.Entry e) { i.putExtra(LogViewerApp.EXTRA_ERROR_TYPE, "fsck_error"); i.putExtra(LogViewerApp.EXTRA_SHOW_REPORT_BUTTON, true); - SystemJournalNotif.show(context, e.getTimeMillis(), context.getString(R.string.fsck_error_notif_title), i); + SystemJournalNotif.show(e.getTimeMillis(), context.getString(R.string.fsck_error_notif_title), i); } } diff --git a/services/core/java/com/android/server/ext/SystemErrorNotification.java b/services/core/java/com/android/server/ext/SystemErrorNotification.java index 9c6e29202feaf..2dd4d339fd5d1 100644 --- a/services/core/java/com/android/server/ext/SystemErrorNotification.java +++ b/services/core/java/com/android/server/ext/SystemErrorNotification.java @@ -2,40 +2,68 @@ import android.annotation.CurrentTimeMillisLong; import android.annotation.Nullable; +import android.annotation.StringRes; import android.content.Context; import android.content.Intent; import android.ext.LogViewerApp; import android.os.Binder; import android.util.Slog; +import java.util.function.Function; + public class SystemErrorNotification { static final String TAG = SystemErrorNotification.class.getSimpleName(); public @CurrentTimeMillisLong long when = System.currentTimeMillis(); public final String type; - public final String title; - public final String message; + public final Function textSupplier; public boolean showReportButton = true; + public static class Text { + public final CharSequence title; + public final String message; + + public Text(CharSequence title, String message) { + this.title = title; + this.message = message; + } + } + public SystemErrorNotification(String type, String message) { this(type, type, message); } public SystemErrorNotification(String type, String title, String message) { + this(type, ctx -> new Text(title, message)); + } + + public SystemErrorNotification(String type, @StringRes int title, String message) { + this(type, ctx -> new Text(ctx.getString(title), message)); + } + + public SystemErrorNotification(String type, Function textSupplier) { this.type = type; - this.title = title; - this.message = message; + this.textSupplier = textSupplier; + } + + // kept for compatibility with the previous callers, context is intentionally ignored + public void show(@Nullable Context context) { + show(); } - public void show(@Nullable Context ctx) { - var i = LogViewerApp.createBaseErrorReportIntent(message); - i.putExtra(Intent.EXTRA_TITLE, title); - i.putExtra(LogViewerApp.EXTRA_ERROR_TYPE, type); - i.putExtra(LogViewerApp.EXTRA_SHOW_REPORT_BUTTON, showReportButton); - Slog.e(TAG, type + ", title: " + title + ", message: " + message); + public void show() { + Function notifDataSupplier = ctx -> { + Text text = this.textSupplier.apply(ctx); + var intent = LogViewerApp.createBaseErrorReportIntent(text.message); + intent.putExtra(Intent.EXTRA_TITLE, text.title); + intent.putExtra(LogViewerApp.EXTRA_ERROR_TYPE, type); + intent.putExtra(LogViewerApp.EXTRA_SHOW_REPORT_BUTTON, showReportButton); + Slog.e(TAG, type + ", title: " + text.title + ", message: " + text.message); + return new SystemJournalNotif.NotificationData(text.title, intent); + }; final long identity = Binder.clearCallingIdentity(); try { - SystemJournalNotif.show(ctx, when, title, i); + SystemJournalNotif.show(when, notifDataSupplier); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/ext/SystemJournalNotif.java b/services/core/java/com/android/server/ext/SystemJournalNotif.java index 591b8361ce5c7..7f22e451a8e83 100644 --- a/services/core/java/com/android/server/ext/SystemJournalNotif.java +++ b/services/core/java/com/android/server/ext/SystemJournalNotif.java @@ -20,6 +20,7 @@ import com.android.internal.os.BackgroundThread; import java.util.ArrayList; +import java.util.function.Function; public class SystemJournalNotif { @@ -34,29 +35,45 @@ static void showCrash(Context ctx, String progName, String errorReport, i.putExtra(LogViewerApp.EXTRA_TEXT_TOMBSTONE_LAST_MODIFIED_TIME, textTombstoneFileSpec.second.longValue()); } - show(ctx, crashTimestamp, ctx.getString(R.string.process_crash_notif_title, progName), i); + show(crashTimestamp, ctx.getString(R.string.process_crash_notif_title, progName), i); } - // If ctx is null then default system context will be used as a fallback - static void show(@Nullable Context ctx, @CurrentTimeMillisLong long when, String notifTitle, Intent mainIntent) { + public static class NotificationData { + public final CharSequence title; + public final Intent mainIntent; + + public NotificationData(CharSequence title, Intent mainIntent) { + this.title = title; + this.mainIntent = mainIntent; + } + } + + static void show(@CurrentTimeMillisLong long when, Function notifDataSupplier) { synchronized (pendingActions) { if (!isSystemServerInited) { // NotificationManagerService isn't ready yet, delay this notification until after // system_server init completion - pendingActions.add(() -> showInner(ctx, when, notifTitle, mainIntent)); + pendingActions.add(() -> { + Context ctx = ActivityThread.currentActivityThread().getSystemContext(); + showInner(ctx, when, notifDataSupplier); + }); return; } } - showInner(ctx, when, notifTitle, mainIntent); + Context ctx = ActivityThread.currentActivityThread().getSystemContext(); + showInner(ctx, when, notifDataSupplier); } - private static void showInner(@Nullable Context ctx, @CurrentTimeMillisLong long when, String notifTitle, Intent mainIntent) { - if (ctx == null) { - ctx = ActivityThread.currentActivityThread().getSystemContext(); - } + static void show(@CurrentTimeMillisLong long when, String notifTitle, Intent mainIntent) { + show(when, ctx -> new NotificationData(notifTitle, mainIntent)); + } + + private static void showInner(Context ctx, @CurrentTimeMillisLong long when, + Function notifDataSupplier) { + NotificationData notifData = notifDataSupplier.apply(ctx); var b = new Notification.Builder(ctx, SystemNotificationChannels.SYSTEM_JOURNAL); b.setSmallIcon(R.drawable.ic_error); - b.setContentTitle(notifTitle); + b.setContentTitle(notifData.title); b.setContentText(ctx.getText(R.string.notif_text_tap_to_see_details)); b.setAutoCancel(true); b.setWhen(when); @@ -64,7 +81,7 @@ private static void showInner(@Nullable Context ctx, @CurrentTimeMillisLong long UserHandle user = UserHandle.of(ActivityManager.getCurrentUser()); - var pi = PendingIntent.getActivityAsUser(ctx, 0, mainIntent, + var pi = PendingIntent.getActivityAsUser(ctx, 0, notifData.mainIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT, null, user); b.setContentIntent(pi); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 201d99aa9e9af..8cb7a6aebec03 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -234,6 +234,7 @@ import com.android.server.pm.Settings.VersionInfo; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.DynamicCodeLogger; +import com.android.server.pm.ext.PackageIdOwnershipChecks; import com.android.server.pm.local.PackageManagerLocalImpl; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageInfoUtils; @@ -243,7 +244,6 @@ import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; -import com.android.server.pm.permission.SpecialRuntimePermUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.PackageStateInternal; @@ -2358,10 +2358,13 @@ public Set getInstallConstraintsAllowlist() { .getBoolean(R.bool.config_stopSystemPackagesByDefault); final int[] userIds = mUserManager.getUserIds(); + com.android.internal.pm.parsing.PackageParserConfig.init(); PackageParser2 packageParser = mInjector.getScanningCachingPackageParser(); mOverlayConfig = mInitAppsHelper.initSystemApps(packageParser, packageSettings, userIds, startTime); + PackageParser2.Callback.enableIdOwnershipChecks(); mInitAppsHelper.initNonSystemApps(packageParser, userIds, startTime); + PackageIdOwnershipChecks.setInitialPackageScanCompleted(); packageParser.close(); mRequiredVerifierPackages = getRequiredButNotReallyRequiredVerifiersLPr(computer); diff --git a/services/core/java/com/android/server/pm/ext/EuiccGoogleHooks.java b/services/core/java/com/android/server/pm/ext/EuiccGoogleHooks.java deleted file mode 100644 index a9a3e2b127aa0..0000000000000 --- a/services/core/java/com/android/server/pm/ext/EuiccGoogleHooks.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.android.server.pm.ext; - -import android.Manifest; -import android.content.pm.PackageManager; -import android.ext.PackageId; - -import com.android.internal.pm.pkg.component.ParsedUsesPermission; -import com.android.internal.pm.pkg.parsing.PackageParsingHooks; -import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.PackageStateInternal; - -class EuiccGoogleHooks extends PackageHooks { - - static class ParsingHooks extends PackageParsingHooks { - - @Override - public int overrideDefaultPackageEnabledState() { - return PackageManager.COMPONENT_ENABLED_STATE_DISABLED; - } - - @Override - public boolean shouldSkipUsesPermission(ParsedUsesPermission p) { - switch (p.getName()) { - // Carrier apps aren't shipped on GrapheneOS, these permissions are needed only to - // install/enable them - case Manifest.permission.INSTALL_EXISTING_PACKAGES: - case Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE: - return true; - } - - return false; - } - } - - @Override - public boolean shouldBlockPackageVisibility(int userId, PackageStateInternal otherPkg) { - // EuiccGoogle is a privileged app, block it from interacting with unprivileged - // parts of sandboxed Google Play - switch (otherPkg.getPackageName()) { - case PackageId.GSF_NAME: - case PackageId.GMS_CORE_NAME: - case PackageId.PLAY_STORE_NAME: - return true; - } - - // Some third-party carrier apps need to interact with EuiccGoogle for eSIM activation - return false; - } -} diff --git a/services/core/java/com/android/server/pm/ext/EuiccGoogleParsingHooks.java b/services/core/java/com/android/server/pm/ext/EuiccGoogleParsingHooks.java new file mode 100644 index 0000000000000..784ad14b709ab --- /dev/null +++ b/services/core/java/com/android/server/pm/ext/EuiccGoogleParsingHooks.java @@ -0,0 +1,13 @@ +package com.android.server.pm.ext; + +import android.content.pm.PackageManager; + +import com.android.internal.pm.pkg.parsing.PackageParsingHooks; + +class EuiccGoogleParsingHooks extends PackageParsingHooks { + + @Override + public int overrideDefaultPackageEnabledState() { + return PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + } +} diff --git a/services/core/java/com/android/server/pm/ext/EuiccSupportPixelHooks.java b/services/core/java/com/android/server/pm/ext/EuiccSupportPixelHooks.java deleted file mode 100644 index 6f44dd2ef1226..0000000000000 --- a/services/core/java/com/android/server/pm/ext/EuiccSupportPixelHooks.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.android.server.pm.ext; - -import android.Manifest; - -import com.android.internal.pm.pkg.parsing.PackageParsingHooks; -import com.android.server.pm.pkg.PackageStateInternal; -import com.android.internal.pm.pkg.component.ParsedUsesPermission; - -class EuiccSupportPixelHooks extends PackageHooks { - - static class ParsingHooks extends PackageParsingHooks { - @Override - public boolean shouldSkipUsesPermission(ParsedUsesPermission p) { - // EuiccSupportPixel uses INTERNET perm only as part of its dev mode - return Manifest.permission.INTERNET.equals(p.getName()); - } - } - - @Override - public boolean shouldBlockPackageVisibility(int userId, PackageStateInternal otherPkg) { - // EuiccSupportPixel is a privileged package, block it from interacting with unprivileged - // parts of GMS - return isUserInstalledPkg(otherPkg); - } -} diff --git a/services/core/java/com/android/server/pm/ext/PackageExtInit.java b/services/core/java/com/android/server/pm/ext/PackageExtInit.java index 9eeff7579f48c..77f660cc374e1 100644 --- a/services/core/java/com/android/server/pm/ext/PackageExtInit.java +++ b/services/core/java/com/android/server/pm/ext/PackageExtInit.java @@ -9,6 +9,8 @@ import android.os.Bundle; import android.util.Slog; +import com.android.internal.pm.parsing.PackageParserConfig; +import com.android.internal.pm.parsing.nano.PackageInstallRequirements; import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; @@ -43,8 +45,29 @@ public ParseResult getSigningDetailsParseResult() { public void run() { int packageId = getPackageId(); + String pkgName = pkg.getPackageName(); if (packageId != UNKNOWN) { - Slog.d(TAG, "set packageId of " + pkg.getPackageName() + " to " + packageId); + Slog.d(TAG, "set packageId of " + pkgName + " to " + packageId); + } + + if (pkg.areIdOwnershipChecksEnabled()) { + PackageInstallRequirements pir = PackageParserConfig.get().getPackageInstallRequirements(pkgName); + if (pir != null) { + long pkgVersion = pkg.getLongVersionCode(); + if (pkgVersion < pir.minVersion) { + pkg.recordIdOwnershipViolation("package version (" + pkgVersion + + ") is lower than the minimum allowed version (" + pir.minVersion + ")"); + } + // hasCertificateSha256() intentionally checks past certificates in addition to the + // current certificate + if (!hasCertificateSha256(pir.certSha256)) { + pkg.recordIdOwnershipViolation("package is not signed with the " + + "expected certificate (" + pir.certSha256 + ")"); + } + } + if (PackageParserConfig.get().isInstallationBlocked(pkgName)) { + pkg.recordIdOwnershipViolation("package name " + pkgName + " is reserved"); + } } var ext = new PackageExt(packageId, getExtFlags()); @@ -123,7 +146,14 @@ private int validate(int packageId, long minVersionCode, String... validCertific " pkgVersion: " + pkg.getLongVersionCode()); return PackageId.UNKNOWN; } + if (hasCertificateSha256(validCertificatesSha256)) { + return packageId; + } + Slog.w(TAG, pkg.getPackageName() + " isn't signed with any expected certificate"); + return PackageId.UNKNOWN; + } + private boolean hasCertificateSha256(String... validCertificatesSha256) { SigningDetails signingDetails = pkg.getSigningDetails(); if (signingDetails == SigningDetails.UNKNOWN) { @@ -134,7 +164,7 @@ private int validate(int packageId, long minVersionCode, String... validCertific Slog.e(TAG, "unable to parse SigningDetails for " + parsingPackage.getPackageName() + "; code " + result.getErrorCode() + "; msg " + result.getErrorMessage(), result.getException()); - return PackageId.UNKNOWN; + return false; } signingDetails = result.getResult(); @@ -143,12 +173,9 @@ private int validate(int packageId, long minVersionCode, String... validCertific for (String certSha256String : validCertificatesSha256) { byte[] validCertSha256 = HexEncoding.decode(certSha256String); if (signingDetails.hasSha256Certificate(validCertSha256)) { - return packageId; + return true; } } - - Slog.d(TAG, "SigningDetails of " + pkg.getPackageName() + " don't contain any of known certificates"); - - return PackageId.UNKNOWN; + return false; } } diff --git a/services/core/java/com/android/server/pm/ext/PackageHooks.java b/services/core/java/com/android/server/pm/ext/PackageHooks.java index 5f42cfad02603..f51450bb1dc32 100644 --- a/services/core/java/com/android/server/pm/ext/PackageHooks.java +++ b/services/core/java/com/android/server/pm/ext/PackageHooks.java @@ -3,12 +3,15 @@ import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.ext.PackageId; import android.util.ArraySet; +import com.android.internal.pm.parsing.nano.ApcPackageConfig; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.PackageUserStateInternal; + +import static com.android.internal.pm.parsing.PackageParserConfig.hasApcFlag; public class PackageHooks { static final PackageHooks DEFAULT = new PackageHooks(); @@ -75,8 +78,29 @@ private static boolean shouldBlockPackageVisibilityInner( boolean isSelfToOther) { AndroidPackage pkg = pkgSetting.getPkg(); if (pkg != null) { - return PackageExt.get(pkg).hooks() - .shouldBlockPackageVisibility(pkgUserId, otherPkgSetting, isSelfToOther); + if (PackageExt.get(pkg).hooks() + .shouldBlockPackageVisibility(pkgUserId, otherPkgSetting, isSelfToOther)) { + return true; + } + + ApcPackageConfig config = pkg.getApcPackageConfig(); + if (config != null) { + if (hasApcFlag(config, ApcPackageConfig.FLAG_ISOLATE_FROM_USER_APPS)) { + if (isUserInstalledPkg(otherPkgSetting)) { + return true; + } + } + if (hasApcFlag(config, ApcPackageConfig.FLAG_ISOLATE_FROM_GMSCORE_AND_FINSKY)) { + String otherPkgName = otherPkgSetting.getPackageName(); + // Finsky is the internal name of the Play Store + switch (otherPkgName) { + case PackageId.GSF_NAME: + case PackageId.GMS_CORE_NAME: + case PackageId.PLAY_STORE_NAME: + return true; + } + } + } } return false; diff --git a/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java b/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java index 2f37f07576543..c3ba7396fcacf 100644 --- a/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java +++ b/services/core/java/com/android/server/pm/ext/PackageHooksRegistry.java @@ -14,9 +14,7 @@ public static PackageParsingHooks getParsingHooks(String pkgName) { return switch (pkgName) { case PackageId.GSF_NAME -> new GsfParsingHooks(); - case PackageId.EUICC_SUPPORT_PIXEL_NAME -> new EuiccSupportPixelHooks.ParsingHooks(); - case PackageId.G_EUICC_LPA_NAME -> new EuiccGoogleHooks.ParsingHooks(); - case PackageId.PIXEL_CAMERA_SERVICES_NAME -> new PixelCameraServicesHooks.ParsingHooks(); + case PackageId.G_EUICC_LPA_NAME -> new EuiccGoogleParsingHooks(); case PackageId.PIXEL_HEALTH_NAME -> new PixelHealthHooks.ParsingHooks(); default -> PackageParsingHooks.DEFAULT; }; @@ -24,11 +22,8 @@ public static PackageParsingHooks getParsingHooks(String pkgName) { public static PackageHooks getHooks(int packageId) { return switch (packageId) { - case PackageId.EUICC_SUPPORT_PIXEL -> new EuiccSupportPixelHooks(); case PackageId.G_CARRIER_SETTINGS -> new GCarrierSettingsHooks(); - case PackageId.G_EUICC_LPA -> new EuiccGoogleHooks(); case PackageId.ANDROID_AUTO -> new AndroidAutoHooks(); - case PackageId.PIXEL_CAMERA_SERVICES -> new PixelCameraServicesHooks(); case PackageId.PIXEL_HEALTH -> new PixelHealthHooks(); case PackageId.GMS_CORE -> new GmsCoreHooks(); default -> PackageHooks.DEFAULT; diff --git a/services/core/java/com/android/server/pm/ext/PackageIdOwnershipChecks.java b/services/core/java/com/android/server/pm/ext/PackageIdOwnershipChecks.java new file mode 100644 index 0000000000000..f49380e2955e3 --- /dev/null +++ b/services/core/java/com/android/server/pm/ext/PackageIdOwnershipChecks.java @@ -0,0 +1,100 @@ +package com.android.server.pm.ext; + +import android.annotation.Nullable; +import android.app.ActivityThread; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.os.BackgroundThread; +import com.android.internal.pm.parsing.PackageInfoCommonUtils; +import com.android.internal.pm.parsing.pkg.AndroidPackageInternal; +import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.server.ext.SystemErrorNotification; + +public class PackageIdOwnershipChecks { + private static final String TAG = "PackageIdOwnershipChecks"; + + private static volatile boolean initialPackageScanCompleted; + + public static void setInitialPackageScanCompleted() { + initialPackageScanCompleted = true; + } + + @Nullable + public static ParseResult maybeOverridePackageParserResult(ParseInput input, ParsingPackage pkg) { + String[] violations = pkg.getIdOwnershipViolations(); + if (violations.length == 0) { + return null; + } + String pkgName = pkg.getPackageName(); + for (String text : violations) { + Slog.w(TAG, "ID ownership violation for " + pkgName + ": " + text); + } + + if (!initialPackageScanCompleted) { + // Keep packages that were installed before introduction of PackageIdOwnershipChecks. + // + // TODO: notify the user about such packages + Slog.w(TAG, "initial package scan is in progress, keeping " + pkgName); + return null; + } + + if (android.os.Flags.isDevBuild()) { + if (SystemProperties.getBoolean("persist.disable_package_id_ownership_checks", false)) { + return null; + } + } + + String msg; + if (violations.length == 1) { + msg = "Package " + pkgName + " violates an ID ownership requirement: " + violations[0]; + } else { + msg = "Package " + pkgName + + " violates the following ID ownership requirements:\n• " + + String.join("\n• ", violations); + } + if (android.os.Flags.isDevBuild()) { + msg += "\n\nTo disable this check, run 'setprop persist.disable_package_id_ownership_checks 1'"; + } + + final int maxSize = 20_000; + final String finalMessage = msg.length() > maxSize ? msg.substring(0, maxSize) : msg; + + String appLabel = null; + try { + Context ctx = ActivityThread.currentActivityThread().getSystemContext(); + // the package will be discarded after this methods returns, modifying it is safe + AndroidPackageInternal pkgFinal = pkg.hideAsParsed().hideAsFinal(); + PackageInfo pkgInfo = PackageInfoCommonUtils.generate(pkgFinal, 0L, UserHandle.USER_SYSTEM); + appLabel = pkgInfo.applicationInfo.loadSafeLabel(ctx.getPackageManager(), PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, + TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_SINGLE_LINE).toString(); + } catch (Exception|OutOfMemoryError|StackOverflowError e) { + Slog.e(TAG, "", e); + // don't crash system_server, loading an app label isn't that important + } + if (appLabel == null) { + appLabel = pkgName; + } + final String finalAppLabel = appLabel; + + BackgroundThread.getHandler().post(() -> { + var notif = new SystemErrorNotification("attempt to claim a reserved name", ctx -> { + String title = ctx.getString(com.android.internal.R.string.pkg_id_violation_blocked, + finalAppLabel); + return new SystemErrorNotification.Text(title, finalMessage); + }); + notif.showReportButton = false; + notif.show(); + }); + + return input.error(PackageManager.INSTALL_FAILED_INVALID_APK, finalMessage); + } +} diff --git a/services/core/java/com/android/server/pm/ext/PixelCameraServicesHooks.java b/services/core/java/com/android/server/pm/ext/PixelCameraServicesHooks.java deleted file mode 100644 index 8d4b5fb7b0295..0000000000000 --- a/services/core/java/com/android/server/pm/ext/PixelCameraServicesHooks.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.android.server.pm.ext; - -import android.Manifest; -import android.ext.PackageId; - -import com.android.internal.pm.pkg.component.ParsedUsesPermission; -import com.android.internal.pm.pkg.parsing.PackageParsingHooks; -import com.android.server.pm.pkg.PackageStateInternal; - -public class PixelCameraServicesHooks extends PackageHooks { - - static class ParsingHooks extends PackageParsingHooks { - @Override - public boolean shouldSkipUsesPermission(ParsedUsesPermission p) { - switch (p.getName()) { - // Pixel Camera Services currently doesn't use any of these permissions. - // [1] states that PCS (Pixel Camera Servicss) is planned "to be able act as a media - // app and use Exoplayer for playing recorded video files". This functionality is - // not implemented in the current release as of January 2024. - // - // [1] https://android.googlesource.com/device/google/gs-common/+/46d6a8821196ab313a5f28c009ce9b2548ea2569 - case Manifest.permission.BLUETOOTH_CONNECT: - case Manifest.permission.BLUETOOTH_SCAN: - case Manifest.permission.CAPTURE_AUDIO_OUTPUT: - case Manifest.permission.MODIFY_AUDIO_ROUTING: - // added in 14 QPR3: - case Manifest.permission.ACCESS_FINE_LOCATION: - case Manifest.permission.INTERACT_ACROSS_USERS: - // needed for the Connected Cameras feature that is currently unsupported on GrapheneOS: - // https://support.google.com/pixelphone/answer/15985851?hl=en - case Manifest.permission.MANAGE_USB: - case Manifest.permission.MODIFY_AUDIO_SETTINGS: - return true; - } - - return false; - } - } - - @Override - public boolean shouldBlockPackageVisibility(int userId, PackageStateInternal otherPkg) { - // Pixel Camera Services is a privileged app, block it from interacting with unprivileged - // parts of sandboxed Google Play. - // - // Pixel Camera Services currently uses GmsCore only for telemetry (perf data reporting), - // and doesn't use Play Store, at least directly. - // - // Also, Play Store would be unable to update Pixel Camera Services if it was allowed to see - // it due to the fs-verity requirements for system package updates. - switch (otherPkg.getPackageName()) { - case PackageId.GSF_NAME: - case PackageId.GMS_CORE_NAME: - case PackageId.PLAY_STORE_NAME: - return true; - } - - return false; - } -} diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 5229ae998bb58..be244bdd0a615 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -291,7 +291,23 @@ private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) { if (signingDetails != SigningDetails.UNKNOWN) { // only return a valid SigningInfo if there is signing information to report - info.signingInfo = new SigningInfo(signingDetails); + var si = new SigningInfo(signingDetails); + info.signingInfo = si; + if ((flags & PackageManager.CACHE_SIGNING_CERTIFICATE_DIGESTS) != 0) { + Signature[] certs = si.getApkContentsSigners(); + if (certs != null) { + for (Signature cert : certs) { + // the cached value is kept during parceling + cert.getSha256Digest(); + } + } + Signature[] certHistory = si.getSigningCertificateHistory(); + if (certHistory != null) { + for (Signature cert : certHistory) { + cert.getSha256Digest(); + } + } + } } else { info.signingInfo = null; } @@ -420,6 +436,14 @@ public static Signature[] getDeprecatedSignatures(SigningDetails signingDetails, private static void updateApplicationInfo(ApplicationInfo ai, long flags, PackageUserState state, AndroidPackage pkg, int userId) { + if ((flags & PackageManager.GET_PLAY_STORE_SOURCE_STAMP_STATE) != 0) { + boolean res = false; + if (pkg != null) { + res = pkg.hasPlayStoreSourceStamp(); + } + ai.setPlayStoreSourceStampPresent(res); + } + if ((flags & PackageManager.GET_META_DATA) == 0) { ai.metaData = null; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index d34efa7e49ffb..70800a86328e8 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -242,6 +242,7 @@ import com.android.server.pm.ext.GmsCompatPkgParsingHooks; import com.android.server.pm.ext.PackageExtInit; import com.android.server.pm.ext.PackageHooksRegistry; +import com.android.server.pm.ext.PackageIdOwnershipChecks; import com.android.server.pm.permission.PermissionMigrationHelper; import com.android.server.pm.permission.PermissionMigrationHelperImpl; import com.android.server.pm.verify.domain.DomainVerificationService; @@ -736,6 +737,7 @@ public static void main(String[] args) { PackageImpl.packageParsingHooksSupplier = PackageHooksRegistry::getParsingHooks; ParsingPackageUtils.packageExtInitSupplier = PackageExtInit::new; ParsingPackageUtils.gmsCompatClientServiceSupplier = GmsCompatPkgParsingHooks::maybeCreateClientService; + ParsingPackageUtils.packageIdOwnershipChecks = PackageIdOwnershipChecks::maybeOverridePackageParserResult; } public SystemServer() { diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/ParsingUtils.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/ParsingUtils.java index a9eac95dfd871..7ff2405d7e12d 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/ParsingUtils.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/ParsingUtils.java @@ -59,6 +59,7 @@ public boolean hasFeature(String feature) { @Override public ParsingPackage startParsingPackage( + int flags, @NonNull String packageName, @NonNull String baseApkPath, @NonNull String path, diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index 2f2d661e8b7b9..113c5f8400425 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -343,7 +343,7 @@ static int32_t GetInt(BinaryPrimitive& bp) { static bool GetBool(BinaryPrimitive& bp) { CHECK(bp.value.dataType == android::Res_value::TYPE_INT_BOOLEAN); - return static_cast(bp.value.data); + return bp.value.data != 0u; } static bool GetBoolAttr(xml::Attribute& attr) { @@ -358,11 +358,19 @@ static int32_t GetIntAttr(xml::Attribute& attr) { return GetInt(*attr_prim); } +static xml::Attribute* GetName(xml::Element* element) { + xml::Attribute* name = element->FindAttribute(xml::kSchemaAndroid, "name"); + CHECK(name != nullptr && !name->value.empty()); + return name; +} + int DumpBriefPackageInfo::Action(const std::vector& args) { if (args.size() != 1) { diag_->Error(android::DiagMessage() << "should specify exactly one apk."); return 1; } + sdk_version_ = std::stoi(sdk_version_str_); + CHECK(sdk_version_ > 0); const std::string &apk_path = args[0]; android::Source source(apk_path); @@ -389,6 +397,7 @@ int DumpBriefPackageInfo::Action(const std::vector& args) { CHECK(attr != nullptr); CHECK(!attr->value.empty()); bpi.set_package_name(attr->value); + bpi.set_target_sdk(sdk_version_); } { xml::Attribute* ver_code_major_attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor"); @@ -399,19 +408,17 @@ int DumpBriefPackageInfo::Action(const std::vector& args) { xml::Attribute* ver_code_attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); CHECK(ver_code_attr != nullptr); int32_t ver_code = GetIntAttr(*ver_code_attr); - bpi.set_version_code((static_cast(ver_code_major) << 32) | ver_code); + bpi.set_version_code((static_cast(ver_code_major) << 32) | static_cast(ver_code)); } // initialized lazily, not needed in the vast majority of cases std::unique_ptr loaded_apk = nullptr; for (xml::Element* manifest_child : manifest_el->GetChildElements()) { if (manifest_child->name == "permission" || manifest_child->name == "permission-tree") { - xml::Attribute* name = manifest_child->FindAttribute(xml::kSchemaAndroid, "name"); - CHECK(!name->value.empty()); + xml::Attribute* name = GetName(manifest_child); bpi.add_permission(name->value); } else if (manifest_child->name == "permission-group") { - xml::Attribute* name = manifest_child->FindAttribute(xml::kSchemaAndroid, "name"); - CHECK(!name->value.empty()); + xml::Attribute* name = GetName(manifest_child); bpi.add_permission_group(name->value); } else if (manifest_child->name == "uses-permission" || manifest_child->name == "uses-permission-sdk-23" || manifest_child->name == "uses-permission-sdk-m") { xml::Attribute* max_sdk_attr = manifest_child->FindAttribute(xml::kSchemaAndroid, "maxSdkVersion"); @@ -425,7 +432,7 @@ int DumpBriefPackageInfo::Action(const std::vector& args) { loaded_apk = LoadedApk::LoadApkFromPath(apk_path, diag_); } ConfigDescription config; - config.sdkVersion = std::stoi(sdk_version_); + config.sdkVersion = sdk_version_; Value* val = FindValueById(loaded_apk->GetResourceTable(), res_id, config); CHECK(val != nullptr); auto *bp = ValueCast(val); @@ -435,24 +442,27 @@ int DumpBriefPackageInfo::Action(const std::vector& args) { max_sdk = GetIntAttr(*max_sdk_attr); } - if (max_sdk < std::stoi(sdk_version_)) { + if (max_sdk < sdk_version_) { continue; } } - xml::Attribute* name = manifest_child->FindAttribute(xml::kSchemaAndroid, "name"); - CHECK(!name->value.empty()); + xml::Attribute* name = GetName(manifest_child); bpi.add_uses_permission(name->value); } else if (manifest_child->name == "uses-sdk") { xml::Attribute* target_sdk = manifest_child->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion"); - if (target_sdk == nullptr) { - bpi.set_target_sdk(std::stoi(sdk_version_)); - } else { + if (target_sdk != nullptr) { bpi.set_target_sdk(GetIntAttr(*target_sdk)); + } else { + xml::Attribute* min_sdk = manifest_child->FindAttribute(xml::kSchemaAndroid, "minSdkVersion"); + if (min_sdk != nullptr) { + bpi.set_target_sdk(GetIntAttr(*min_sdk)); + } } } else if (manifest_child->name == "application") { for (xml::Element* app_child : manifest_child->GetChildElements()) { if (app_child->name == "provider") { xml::Attribute* authorities = app_child->FindAttribute(xml::kSchemaAndroid, "authorities"); + CHECK(authorities != nullptr); // authorities can be specified with a string resource reference auto* ref = ValueCast(authorities->compiled_value.get()); if (ref == nullptr) { @@ -467,18 +477,18 @@ int DumpBriefPackageInfo::Action(const std::vector& args) { loaded_apk = LoadedApk::LoadApkFromPath(apk_path, diag_); } ConfigDescription config; - config.sdkVersion = std::stoi(sdk_version_); + config.sdkVersion = sdk_version_; Value* val = FindValueById(loaded_apk->GetResourceTable(), res_id, config); CHECK(val != nullptr); auto str = ValueCast(val); + CHECK(str != nullptr); for (std::string part : util::Split(*str->value, ';')) { bpi.add_content_provider_authority(part); } } } else if (app_child->name == "uses-library") { - xml::Attribute* name = app_child->FindAttribute(xml::kSchemaAndroid, "name"); - CHECK(!name->value.empty()); + xml::Attribute* name = GetName(app_child); bool required = true; xml::Attribute* required_attr = app_child->FindAttribute(xml::kSchemaAndroid, "required"); if (required_attr != nullptr) { @@ -491,15 +501,17 @@ int DumpBriefPackageInfo::Action(const std::vector& args) { bpi.add_optional_uses_library(name->value); } } else if (app_child->name == "library") { - xml::Attribute* name = app_child->FindAttribute(xml::kSchemaAndroid, "name"); - CHECK(!name->value.empty()); + xml::Attribute* name = GetName(app_child); bpi.add_library(name->value); } } } } - bpi.SerializeToOstream(&std::cout); - return 0; + if (!bpi.SerializeToOstream(&std::cout)) { + return 1; + } + std::cout.flush(); + return std::cout.good() ? 0 : 1; } int DumpPackageNameCommand::Dump(LoadedApk* apk) { diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h index 132911544ce7b..c291687d40606 100644 --- a/tools/aapt2/cmd/Dump.h +++ b/tools/aapt2/cmd/Dump.h @@ -176,13 +176,14 @@ class DumpBriefPackageInfo : public Command { : Command("brief-package-info"), diag_(diag) { SetDescription("Print BriefPackageInfo protobuf."); AddRequiredFlag("--sdk-version", - "ro.build.version.sdk value. Some package IDs are version-specific.", &sdk_version_); + "ro.build.version.sdk value. Some package IDs are version-specific.", &sdk_version_str_); } int Action(const std::vector& args) override; private: - std::string sdk_version_; + std::string sdk_version_str_; + int32_t sdk_version_; android::IDiagnostics* diag_; };