Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ public class PackageParsingPerfTest {
override fun hasFeature(feature: String) = true

override fun startParsingPackage(
flags: Int,
packageName: String,
baseApkPath: String,
path: String,
Expand Down
4 changes: 2 additions & 2 deletions core/java/android/app/ActivityThreadHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 12 additions & 1 deletion core/java/android/app/ApplicationPackageManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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;
}

Expand Down
90 changes: 90 additions & 0 deletions core/java/android/app/InstallSourceSpoofingHooks.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
40 changes: 19 additions & 21 deletions core/java/android/content/pm/ApplicationInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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<String>();
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;
}
}
8 changes: 8 additions & 0 deletions core/java/android/content/pm/PackageManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand All @@ -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 {}
Expand Down Expand Up @@ -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
//-------------------------------------------------------------------------
Expand Down
18 changes: 18 additions & 0 deletions core/java/android/content/pm/Signature.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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<Signature> CREATOR
Expand All @@ -307,6 +324,7 @@ public void writeToXmlAttributeBytesHex(@NonNull TypedXmlSerializer out,

private Signature(Parcel source) {
mSignature = source.createByteArray();
mSha256Digest = source.createByteArray();
}

/**
Expand Down
5 changes: 2 additions & 3 deletions core/java/android/content/pm/SigningDetails.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions core/java/android/provider/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 13 additions & 4 deletions core/java/com/android/internal/app/PairipHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
17 changes: 13 additions & 4 deletions core/java/com/android/internal/pm/parsing/PackageParser2.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
Loading