diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index f0c2f3ba03cf7..6abd504fe6c0c 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -125,6 +125,8 @@ package android.content { public class Intent implements Cloneable android.os.Parcelable { field public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED"; + field public static final String ACTION_USER_STARTED = "android.intent.action.USER_STARTED"; + field public static final String ACTION_USER_STOPPED = "android.intent.action.USER_STOPPED"; field public static final String EXTRA_SETTING_NAME = "setting_name"; field public static final String EXTRA_SETTING_NEW_VALUE = "new_value"; field public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value"; @@ -146,6 +148,7 @@ package android.content.pm { } public abstract class PackageManager { + method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract android.content.pm.PackageInfo getPackageInfoAsUser(@NonNull String, int, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public String getSdkSandboxPackageName(); method @RequiresPermission(android.Manifest.permission.MAKE_UID_VISIBLE) public void makeUidVisible(int, int); field public static final String EXTRA_VERIFICATION_ROOT_HASH = "android.content.pm.extra.VERIFICATION_ROOT_HASH"; @@ -157,6 +160,7 @@ package android.content.pm { package android.ext { public class ConnectivityUtil { + method public static boolean isAppStrictLeakBlockingEnabled(@NonNull android.content.Context, int, @NonNull android.content.pm.ApplicationInfo, @NonNull android.content.pm.GosPackageState); method public static boolean isRegularAppWithLockdownVpnEnabled(@NonNull android.content.Context, int); method public static boolean isSystem(@NonNull android.content.Context, int); } @@ -629,6 +633,7 @@ package android.os { public final class UserHandle implements android.os.Parcelable { method public static int getUid(int, int); method public static int getUserId(int); + method public static boolean isCore(int); } public class UserManager { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c0df87a641bb3..f19c1446b23ce 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5867,6 +5867,17 @@ package android.ext { field public static final int UNKNOWN = 0; // 0x0 } + public class SettingsIntents { + method @NonNull public static android.content.Intent createAppIntent(@NonNull android.content.Context, @NonNull String, @NonNull String, boolean); + field public static final String APP_HARDENED_MALLOC = "android.settings.OPEN_APP_HARDENED_MALLOC_SETTINGS"; + field public static final String APP_MANAGE_PLAY_INTEGRITY_API = "android.settings.OPEN_APP_MANAGE_PLAY_INTEGRITY_API_SETTINGS"; + field public static final String APP_MEMORY_DYN_CODE_LOADING = "android.settings.OPEN_APP_MEMORY_DYN_CODE_LOADING_SETTINGS"; + field public static final String APP_MEMTAG = "android.settings.OPEN_APP_MEMTAG_SETTINGS"; + field public static final String APP_NATIVE_DEBUGGING = "android.settings.OPEN_APP_NATIVE_DEBUGGING_SETTINGS"; + field public static final String APP_STORAGE_DYN_CODE_LOADING = "android.settings.OPEN_APP_STORAGE_DYN_CODE_LOADING_SETTINGS"; + field public static final String APP_STRICT_LEAK_BLOCKING = "android.settings.OPEN_APP_STRICT_LEAK_BLOCKING"; + } + } package android.ext.cscopes { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 5ca94259fe348..60f323ec1f824 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4237,6 +4237,7 @@ public static Intent createChooser(Intent target, CharSequence title, IntentSend * the current state of the user. * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String ACTION_USER_STARTED = "android.intent.action.USER_STARTED"; @@ -4302,6 +4303,7 @@ public static Intent createChooser(Intent target, CharSequence title, IntentSend * @hide */ @TestApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String ACTION_USER_STOPPED = "android.intent.action.USER_STOPPED"; diff --git a/core/java/android/content/pm/GosPackageState.java b/core/java/android/content/pm/GosPackageState.java index 58b0a00af6961..9c0e6bbf65453 100644 --- a/core/java/android/content/pm/GosPackageState.java +++ b/core/java/android/content/pm/GosPackageState.java @@ -17,6 +17,8 @@ import android.util.Log; import android.util.SparseArray; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.reflect.Field; import java.util.Arrays; import java.util.Objects; @@ -402,11 +404,18 @@ public Editor setNotifyUidAfterApply(boolean v) { public boolean apply() { try { return ActivityThread.getPackageManager().setGosPackageState(packageName, userId, - new GosPackageState(flagStorage1, packageFlagStorage, storageScopes, contactScopes), - editorFlags); + toState(), editorFlags); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + + /** @hide */ + @VisibleForTesting + @NonNull + public GosPackageState toState() { + return new GosPackageState(flagStorage1, packageFlagStorage, storageScopes, + contactScopes); + } } } diff --git a/core/java/android/content/pm/GosPackageStateFlag.java b/core/java/android/content/pm/GosPackageStateFlag.java index 79c05d16711b3..e4d12cfed954b 100644 --- a/core/java/android/content/pm/GosPackageStateFlag.java +++ b/core/java/android/content/pm/GosPackageStateFlag.java @@ -6,7 +6,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** @hide */ +/** + * GosPackageStateFlag defines the valid flags for GosPackageState. When adding a new flag, its + * permissions must also be set in GosPackageStatePermissions. + * + * @hide + */ @SystemApi public interface GosPackageStateFlag { /* SysApi */ int STORAGE_SCOPES_ENABLED = 0; @@ -38,6 +43,9 @@ public interface GosPackageStateFlag { /** @hide */ int BLOCK_PLAY_INTEGRITY_API = 28; /** @hide */ int USE_EXEC_SPAWNING_NON_DEFAULT = 29; /** @hide */ int USE_EXEC_SPAWNING = 30; + /** @hide */ int STRICT_LEAK_BLOCKING_NON_DEFAULT = 31; + /** @hide */ int STRICT_LEAK_BLOCKING = 32; + /** @hide */ int STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF = 33; /** @hide */ @IntDef(value = { @@ -68,6 +76,9 @@ public interface GosPackageStateFlag { BLOCK_PLAY_INTEGRITY_API, USE_EXEC_SPAWNING_NON_DEFAULT, USE_EXEC_SPAWNING, + STRICT_LEAK_BLOCKING_NON_DEFAULT, + STRICT_LEAK_BLOCKING, + STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF, }) @Retention(RetentionPolicy.SOURCE) @interface Enum {} diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 4360a8a45d117..bcf29cd5e54c1 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -6157,6 +6157,8 @@ public PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage, @SuppressWarnings("HiddenAbstractMethod") @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) @UnsupportedAppUsage + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @NonNull public abstract PackageInfo getPackageInfoAsUser(@NonNull String packageName, int flags, @UserIdInt int userId) throws NameNotFoundException; diff --git a/core/java/android/ext/ConnectivityUtil.java b/core/java/android/ext/ConnectivityUtil.java index 75777dcb00988..34b80264e9fbf 100644 --- a/core/java/android/ext/ConnectivityUtil.java +++ b/core/java/android/ext/ConnectivityUtil.java @@ -4,7 +4,9 @@ import android.annotation.SystemApi; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; import android.content.pm.PackageManager; +import android.ext.settings.app.AswStrictLeakBlocking; import android.os.UserHandle; import android.provider.Settings; @@ -43,4 +45,9 @@ public static boolean isRegularAppWithLockdownVpnEnabled(@NonNull Context contex Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0, UserHandle.getUserId(uid)); return lockdownVpnEnabled == 1 && !isSystem(context, uid); } + + public static boolean isAppStrictLeakBlockingEnabled(@NonNull Context context, int uid, + @NonNull ApplicationInfo ai, @NonNull GosPackageState ps) { + return AswStrictLeakBlocking.I.get(context, uid, ai, ps); + } } diff --git a/core/java/android/ext/SettingsIntents.java b/core/java/android/ext/SettingsIntents.java index 956b1144b0e4d..01c34b1392f53 100644 --- a/core/java/android/ext/SettingsIntents.java +++ b/core/java/android/ext/SettingsIntents.java @@ -1,10 +1,13 @@ package android.ext; +import android.annotation.NonNull; +import android.annotation.SystemApi; import android.content.Context; import android.content.Intent; import android.net.Uri; /** @hide */ +@SystemApi public class SettingsIntents { public static final String APP_NATIVE_DEBUGGING = "android.settings.OPEN_APP_NATIVE_DEBUGGING_SETTINGS"; @@ -13,12 +16,19 @@ public class SettingsIntents { public static final String APP_MEMORY_DYN_CODE_LOADING = "android.settings.OPEN_APP_MEMORY_DYN_CODE_LOADING_SETTINGS"; public static final String APP_STORAGE_DYN_CODE_LOADING = "android.settings.OPEN_APP_STORAGE_DYN_CODE_LOADING_SETTINGS"; public static final String APP_MANAGE_PLAY_INTEGRITY_API = "android.settings.OPEN_APP_MANAGE_PLAY_INTEGRITY_API_SETTINGS"; + public static final String APP_STRICT_LEAK_BLOCKING = "android.settings.OPEN_APP_STRICT_LEAK_BLOCKING"; - public static Intent getAppIntent(Context ctx, String action, String pkgName) { + // This constructor keeps the linter happy. + private SettingsIntents() {} + + public static @NonNull Intent createAppIntent(@NonNull Context ctx, @NonNull String action, + @NonNull String pkgName, boolean newTask) { var i = new Intent(action); i.setData(Uri.fromParts("package", pkgName, null)); i.setPackage(KnownSystemPackages.get(ctx).settings); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + if (newTask) { + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + } return i; } } diff --git a/core/java/android/ext/settings/app/AswStrictLeakBlocking.java b/core/java/android/ext/settings/app/AswStrictLeakBlocking.java new file mode 100644 index 0000000000000..bfa20e1f30dfb --- /dev/null +++ b/core/java/android/ext/settings/app/AswStrictLeakBlocking.java @@ -0,0 +1,41 @@ +package android.ext.settings.app; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.GosPackageState; +import android.content.pm.GosPackageStateFlag; + +/** @hide */ +public class AswStrictLeakBlocking extends AppSwitch { + public static final AswStrictLeakBlocking I = new AswStrictLeakBlocking(); + + private AswStrictLeakBlocking() { + gosPsFlag = GosPackageStateFlag.STRICT_LEAK_BLOCKING; + gosPsFlagNonDefault = GosPackageStateFlag.STRICT_LEAK_BLOCKING_NON_DEFAULT; + gosPsFlagSuppressNotif = GosPackageStateFlag.STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF; + } + + @Override + public Boolean getImmutableValue(Context ctx, int userId, ApplicationInfo appInfo, + GosPackageState ps, StateInfo si) { + if (appInfo.isSystemApp()) { + si.immutabilityReason = IR_IS_SYSTEM_APP; + // This is overly permissive because not all system apps have permissions that allow + // them to bypass lockdown VPNs (netd PERMISSION_SYSTEM). With that said, it's plausible + // that a system app without PERMISSION_SYSTEM could have a valid reason to use + // functionality gated behind strict leak blocking. GrapheneOS ships proprietary core + // components and system apps that could fall under that category. + // TODO: Determine which core components and system apps actually need this to be false + // (this will be difficult to maintain). + return false; + } + + return null; + } + + @Override + protected boolean getDefaultValueInner(Context ctx, int userId, ApplicationInfo appInfo, + GosPackageState ps, StateInfo si) { + return true; + } +} diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 08f8315985e04..b29be440eb175 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -285,6 +285,7 @@ public static boolean isApp(int uid) { * Whether a UID belongs to a system core component or not. * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static boolean isCore(int uid) { if (uid >= 0) { final int appId = getAppId(uid); diff --git a/core/res/res/values/string_ext.xml b/core/res/res/values/string_ext.xml index 12fbc7f26e3b6..c6379ca01e8b7 100644 --- a/core/res/res/values/string_ext.xml +++ b/core/res/res/values/string_ext.xml @@ -29,6 +29,9 @@ %1$s tried to use native code debugging + %1$s tried to bypass strict leak blocking + + System journal diff --git a/services/api/current.txt b/services/api/current.txt index 00cff22d3f179..d67d471250119 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -45,6 +45,7 @@ package com.android.server.am { method public boolean canStartForegroundService(int, int, @NonNull String); method @FlaggedApi("com.android.server.am.get_package_names_for_pid_api") @NonNull public String[] getPackageNamesForPid(int, int); method public void killSdkSandboxClientAppProcess(@NonNull android.os.IBinder); + method public void showAppStrictLeakBlockingNotification(int, int); method @FlaggedApi("com.android.server.am.fgs_delegate_system_api") public boolean startForegroundServiceDelegate(@NonNull com.android.server.am.ForegroundServiceDelegationParams, @Nullable android.content.ServiceConnection); method @Nullable public android.content.ComponentName startSdkSandboxService(@NonNull android.content.Intent, int, @NonNull String, @NonNull String) throws android.os.RemoteException; method @FlaggedApi("com.android.server.am.fgs_delegate_system_api") public void stopForegroundServiceDelegate(@NonNull com.android.server.am.ForegroundServiceDelegationParams); diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java index c44746e04c5b5..e72edf882fd33 100644 --- a/services/core/java/com/android/server/am/ActivityManagerLocal.java +++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java @@ -224,4 +224,6 @@ boolean startForegroundServiceDelegate( @FlaggedApi(Flags.FLAG_FGS_DELEGATE_SYSTEM_API) void stopForegroundServiceDelegate( @NonNull ForegroundServiceDelegationParams params); + + void showAppStrictLeakBlockingNotification(int uid, int pid); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b2da89210e1e9..7694482b1bc69 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -525,6 +525,7 @@ import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.crashrecovery.CrashRecoveryHelper; import com.android.server.criticalevents.CriticalEventLog; +import com.android.server.ext.AppStrictLeakBlockingUtil; import com.android.server.ext.DynCodeLoadingUtils; import com.android.server.ext.PackageManagerHooks; import com.android.server.firewall.IntentFirewall; @@ -19410,6 +19411,11 @@ public void dispatchGosPackageStateCallbacks(int uid) { public ProcessRecordSnapshot getProcessRecordByPid(int pid) { return mProcessList.getProcessRecordByPid(pid); } + + @Override + public void showAppStrictLeakBlockingNotification(int uid, int pid) { + AppStrictLeakBlockingUtil.showNotification(mContext, uid, pid); + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) { diff --git a/services/core/java/com/android/server/ext/AppStrictLeakBlockingUtil.java b/services/core/java/com/android/server/ext/AppStrictLeakBlockingUtil.java new file mode 100644 index 0000000000000..7051861165527 --- /dev/null +++ b/services/core/java/com/android/server/ext/AppStrictLeakBlockingUtil.java @@ -0,0 +1,48 @@ +package com.android.server.ext; + +import android.app.ActivityManagerInternal; +import android.content.Context; +import android.content.pm.GosPackageStateFlag; +import android.content.pm.PackageManagerInternal; +import android.ext.SettingsIntents; +import android.util.Slog; + +import com.android.internal.R; +import com.android.server.LocalServices; +import com.android.server.pm.Computer; + +public class AppStrictLeakBlockingUtil { + private static final String TAG = AppStrictLeakBlockingUtil.class.getSimpleName(); + + public static void showNotification(Context ctx, int uid, int pid) { + String firstPackageName = null; + + var ami = LocalServices.getService(ActivityManagerInternal.class); + ActivityManagerInternal.ProcessRecordSnapshot prs = ami.getProcessRecordByPid(pid); + if (prs != null && prs.appInfo != null) { + firstPackageName = prs.appInfo.packageName; + } else { + var pmi = LocalServices.getService(PackageManagerInternal.class); + Computer snapshot = (Computer)pmi.snapshot(); + String[] packages = snapshot.getPackagesForUid(uid); + if (packages != null && packages.length == 1) { + firstPackageName = packages[0]; + } + } + + if (firstPackageName == null) { + Slog.d(TAG, "firstPackageName is null for uid " + uid); + return; + } + + var n = AppSwitchNotification.maybeCreate(ctx, firstPackageName, uid, + SettingsIntents.APP_STRICT_LEAK_BLOCKING); + if (n == null) { + return; + } + + n.titleRes = R.string.notif_app_strict_leak_blocking_title; + n.gosPsFlagSuppressNotif = GosPackageStateFlag.STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF; + n.maybeShow(); + } +} diff --git a/services/core/java/com/android/server/ext/AppSwitchNotification.java b/services/core/java/com/android/server/ext/AppSwitchNotification.java index 66f93579779e3..3123d0f05dcf1 100644 --- a/services/core/java/com/android/server/ext/AppSwitchNotification.java +++ b/services/core/java/com/android/server/ext/AppSwitchNotification.java @@ -175,7 +175,7 @@ public void maybeShow() { nb.setContentTitle(ctx.getString(titleRes, appLabel)); } { - var intent = SettingsIntents.getAppIntent(ctx, settingsIntentAction, pkgName); + var intent = SettingsIntents.createAppIntent(ctx, settingsIntentAction, pkgName, true); var pi = PendingIntent.getActivityAsUser(ctx, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, userHandle); nb.setContentIntent(pi); diff --git a/services/core/java/com/android/server/pm/GosPackageStatePermissions.java b/services/core/java/com/android/server/pm/GosPackageStatePermissions.java index 8dc9b77d2f85d..5eb70ac301e6b 100644 --- a/services/core/java/com/android/server/pm/GosPackageStatePermissions.java +++ b/services/core/java/com/android/server/pm/GosPackageStatePermissions.java @@ -22,6 +22,9 @@ import java.util.Objects; import static android.content.pm.GosPackageStateFlag.ALLOW_ACCESS_TO_OBB_DIRECTORY; +import static android.content.pm.GosPackageStateFlag.STRICT_LEAK_BLOCKING; +import static android.content.pm.GosPackageStateFlag.STRICT_LEAK_BLOCKING_NON_DEFAULT; +import static android.content.pm.GosPackageStateFlag.STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF; import static android.content.pm.GosPackageStateFlag.BLOCK_NATIVE_DEBUGGING; import static android.content.pm.GosPackageStateFlag.BLOCK_NATIVE_DEBUGGING_NON_DEFAULT; import static android.content.pm.GosPackageStateFlag.BLOCK_NATIVE_DEBUGGING_SUPPRESS_NOTIF; @@ -145,6 +148,9 @@ static void init(PackageManagerService pm) { USE_EXEC_SPAWNING_NON_DEFAULT, USE_EXEC_SPAWNING, ENABLE_EXPLOIT_PROTECTION_COMPAT_MODE, + STRICT_LEAK_BLOCKING_NON_DEFAULT, + STRICT_LEAK_BLOCKING, + STRICT_LEAK_BLOCKING_SUPPRESS_NOTIF, }; builder() .readWriteFlags(settingsReadWriteFlags)