Android11 framework 禁止三方应用开机自启动

Android11应用自启动限制

大纲

分析

按理说三方应用应该收不到开机启动广播(后文会证实这个说法是假的),但是很神奇的是还是有应用能自启动,体现为比如秋秋,启动后通过ps命令能看到其进程存活,但是静置后进程会启动失败,从而导致被清理

07-18 05:59:02.132367 938 967 I ActivityManager: Start proc 1930:com.tencent.mobileqq:MSF/u0a110 for broadcast {com.tencent.mobileqq/com.tencent.mobileqq.msf.core.NetConnInfoCenter}

log如上

参照这篇博客Android系统层面限制应用开机自启动详解,此时通过Android Studio查看其apk的manifest,其NetConnInfoCenter​内容如下

可以看到其监听了开机广播外,还监听了各种各样的广播,比如TIMEZONE_CHANGED​,看起来是时区变换的广播,这边尝试了一下在没有启动秋秋的情况下,去切换时区,果然打印了启动秋秋的log,虽然没有启动成功,但是之前在这个项目中,自己遇到过秋秋启动成功的情况,所以还是要处理

xml 复制代码
<receiver
            android:name="com.tencent.mobileqq.msf.core.NetConnInfoCenter"
            android:exported="false"
            android:process=":MSF">

            <intent-filter
                android:priority="2147483647">

                <action
                    android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="android.intent.action.MY_PACKAGE_REPLACED" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="android.intent.action.TIME_SET" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="android.intent.action.TIMEZONE_CHANGED" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="com.tencent.mobileqq.rdm.report" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="com.tencent.mobileqq.msf.receiveofflinepush" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="com.tencent.mobileqq.msf.offlinepushclearall" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="com.tencent.mobileqq.msf.receiveofflinepushav" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="com.tencent.mobileqq.msf.offlinepushclearallav" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="com.tencent.mobileqq.msf.startmsf" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="android.intent.action.MEDIA_BAD_REMOVAL" />

                <action
                    android:name="android.intent.action.MEDIA_EJECT" />

                <action
                    android:name="android.intent.action.MEDIA_MOUNTED" />

                <action
                    android:name="android.intent.action.MEDIA_REMOVED" />

                <action
                    android:name="android.intent.action.MEDIA_SCANNER_FINISHED" />

                <action
                    android:name="android.intent.action.MEDIA_SCANNER_STARTED" />

                <action
                    android:name="android.intent.action.MEDIA_SHARED" />

                <action
                    android:name="android.intent.action.MEDIA_UNMOUNTED" />

                <data
                    android:scheme="file" />
            </intent-filter>

            <intent-filter>

                <action
                    android:name="android.net.wifi.WIFI_STATE_CHANGED" />
            </intent-filter>
        </receiver>

验证猜想:Android11 AOSP是否自带禁止三方应用监听BOOT_COMPLETED

因为前面提到Android11三方应用到底能不能监听开机广播,这边写了个demo,仅监听BOOT_COMPLETED​,重启后验证,进程会正常被创建,所以Android11并没有原生的禁止三方监听BOOT_COMPLETED​的策略。至少AOSP没有,国内手机带此功能是手机厂商自己实现的

java 复制代码
public class BootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            Intent mIntent = new Intent(context, MainActivity.class);
            mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(mIntent);          // 启动你的应用程序
        }
    }
}

<uses-permission
        android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<receiver android:name=".BootReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

结论:Android11上普通三方应用是可以接收到开机广播的,此时如果应用去自启动,后台会有进程存活,因此需要做处理

方案

禁止执行非系统应用监听到BOOT_COMPLETED​后的代码逻辑

在处理广播的地方 过滤掉所有的三方应用,并预留白名单

在AMS的broadcastIntentLocked​方法中

java 复制代码
diff --git a/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java b/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
index f26ae929ef..6b906ced4f 100644
--- a/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16736,7 +16736,30 @@ public class ActivityManagerService extends IActivityManager.Stub
        final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
        @Nullable String callerFeatureId, Intent intent, String resolvedType,
        IIntentReceiver resultTo, int resultCode, String resultData,
        Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
        boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
        int realCallingPid, int userId, boolean allowBackgroundActivityStarts,
        @Nullable int[] broadcastWhitelist) {
 	    ...
        if (receivers != null) {
            String skipPackages[] = null;
            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
                    || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
                    || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
                Uri data = intent.getData();
                if (data != null) {
                    String pkgName = data.getSchemeSpecificPart();
                    if (pkgName != null) {
                        skipPackages = new String[] { pkgName };
                    }
                }
             } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
                skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            }
+            // add by xumaoxin start
+            } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+                //Resources res = mContext.getResources();
+                //String[] blacklist = res.getStringArray(com.android.internal.R.array.blacklist_boot_receiver);
+                List<String> skipList = new ArrayList<>();
+                Set<String> whitelist = new ArraySet<>();
+                whitelist.add("com.example.test11");
+
+                for (int i = 0, j = receivers.size(); i < j; i++) {
+                    ResolveInfo curt = (ResolveInfo) receivers.get(i);
+                    String curPkgName = curt.activityInfo.packageName;
+                    if ((curt.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                        //Slog.i("ActivityManager", "xumaoxin: " + curPkgName + " is system app, allow!");
+                    } else if (!whitelist.contains(curPkgName)) {
+                        Slog.i("ActivityManager", "xumaoxin: " + curPkgName + " is not system app and not whitelist, not allow!");
+                        skipList.add(curPkgName);
+                    }else {
+                        Slog.i("ActivityManager", "xumaoxin: " + curPkgName + " is not system app but is whitelist, allow!");
+                    }
+                }
+                skipPackages = skipList.toArray(new String[0]);
+                Slog.i("ActivityManager", "xumaoxin: skipPackages= " + Arrays.toString(skipPackages));
+                //add by xumaoxin end
+            } 
             if (skipPackages != null && (skipPackages.length > 0)) {
                 for (String skipPackage : skipPackages) {
                     if (skipPackage != null) {

判断是不是系统应用

java 复制代码
curt.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 //是系统应用

注意:此处只能禁止执行skipPackages中的应用去监听到BOOT_COMPLETED后的执行逻辑,如果应用只做了这一个监听来自启,比如我前面写的demo,这样就足够了,但是国内三方app一般会想方设法地自启动,这边验证了秋秋还是会启动起来,会有进程存在,继续处理。

在执行启动时判断其启动的广播接收器

注意:同样需要处理秋秋的同学请一定看到最后,这一节的方案不完善

基于log

ActivityManager: Start proc 2962:com.tencent.mobileqq:MSF/u0a112 for broadcast {com.tencent.mobileqq/com.tencent.mobileqq.msf.core.NetConnInfoCenter}

这里秋秋自己的广播接收器叫com.tencent.mobileqq/com.tencent.mobileqq.msf.core.NetConnInfoCenter​,我们先找到这行log打印的地方

ActivityManager为tag的,在server/am下去grep就好,找到其文件

frameworks\base\services\core\java\com\android\server\am\ProcessList.java

java 复制代码
    boolean handleProcessStartedLocked(ProcessRecord app, int pid, boolean usingWrapper,
            long expectedStartSeq, boolean procAttached) {
        mPendingStarts.remove(expectedStartSeq);
        final String reason = isProcStartValidLocked(app, expectedStartSeq);
        if (reason != null) {
            Slog.w(TAG_PROCESSES, app + " start not valid, killing pid=" +
                    pid
                    + ", " + reason);
            app.pendingStart = false;
            killProcessQuiet(pid);
            Process.killProcessGroup(app.uid, app.pid);
            noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
                    ApplicationExitInfo.SUBREASON_INVALID_START, reason);
            return false;
        }
        ......
        StringBuilder buf = mStringBuilder;
        buf.setLength(0);
        buf.append("Start proc ");
        buf.append(pid);
        buf.append(':');
        buf.append(app.processName);
        buf.append('/');
        UserHandle.formatUid(buf, app.startUid);
        if (app.isolatedEntryPoint != null) {
            buf.append(" [");
            buf.append(app.isolatedEntryPoint);
            buf.append("]");
        }
        buf.append(" for ");
        buf.append(app.hostingRecord.getType());
        if (app.hostingRecord.getName() != null) {
            buf.append(" ");
            buf.append(app.hostingRecord.getName());
        }

能看到buf.append("Start proc ");​这里开始的buf记录和我们log是一样的,所以后面的app.hostingRecord.getType()​对应log中的广播接收器broadcast​,而app.hostingRecord.getName()​对应log中的{com.tencent.mobileqq/com.tencent.mobileqq.msf.core.NetConnInfoCenter}

那就好处理了,判断其name,如果是NetConnInfoCenter​,那就不允许启动。

代码往前,能看到原生有一个退出的逻辑,通过final String reason = isProcStartValidLocked(app, expectedStartSeq);​赋值

isProcStartValidLocked​方法中加入秋秋的判断,返回一个reason,这样此次启动能通过原生的逻辑进行拦截,比较安全

java 复制代码
diff --git a/frameworks/base/services/core/java/com/android/server/am/ProcessList.java b/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
index 9d4deb4d5b..271f99138f 100644
--- a/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
+++ b/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
@@ -2462,6 +2462,14 @@ public final class ProcessList {
     @GuardedBy("mService")
     private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
         StringBuilder sb = null;
+        if (app.hostingRecord.getName() != null) {
+            String name = app.hostingRecord.getName();
+            Slog.i("ActivityManager", "xumaoxin: broadcast= " + name);
+            if (name.contains("NetConnInfoCenter")) {
+                if (sb == null) sb = new StringBuilder();
+                sb.append("dont allow qq auto start");
+            }
+        }
         if (app.killedByAm) {
             if (sb == null) sb = new StringBuilder();
             sb.append("killedByAm=true;");

编译push验证,秋秋在设备重启后不会再重启,会被拦截

抑制秋秋自启动成功!同时不会影响用户手动点击图标等方式的启动

应用多了可以写成白名单,并配置到xml中

注意:此方案的弊端就是只能对特定的应用单独加判断逻辑,不能适用于所有应用

一棍子打死方案(慎用)

观察三方应用是否都是通过broadcast​类型进行自启动的,能否判断type​是否是broadcast​来进行通用过滤?风险就是这样一来一棍子打死了,等于不再允许三方应用通过广播方式进行启动

java 复制代码
diff --git a/frameworks/base/services/core/java/com/android/server/am/ProcessList.java b/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
index 9d4deb4d5b..03a4c1ddb2 100644
--- a/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
+++ b/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
@@ -2462,6 +2462,19 @@ public final class ProcessList {
     @GuardedBy("mService")
     private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
         StringBuilder sb = null;
+        /*if (app.hostingRecord.getName() != null) {
+            String name = app.hostingRecord.getName();
+            Slog.i("ActivityManager", "xumaoxin: broadcast= " + name);
+            if (name.contains("NetConnInfoCenter")) {
+                if (sb == null) sb = new StringBuilder();
+                sb.append("dont allow qq auto start");
+            }
+        }*/
+        if (((app.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) && "broadcast".equals(app.hostingRecord.getType())) {
+            String name = app.hostingRecord.getName();
+            if (sb == null) sb = new StringBuilder();
+            sb.append("dont allow thirdapp auto start by broadcast: " + name);
+        }
         if (app.killedByAm) {
             if (sb == null) sb = new StringBuilder();
             sb.append("killedByAm=true;");

编译后验证秋秋确实是没有问题,主要不确定其他的广播场景会不会被拦截下来,毕竟一棍子打死的办法杀伤面有点大

尽量还是使用第一种+第二种,不让三方应用执行开机广播逻辑+特殊应用特殊过滤。第三种方案慎用!

补充!!!!!!!

对于秋秋前面通过广播想启动的只是其一个子进程,在登陆的时候会用到,如果一直不让其自动启动,会导致登陆超时。

所以还需要加入判断,启动的时候秋秋主进程是否存活,如果存活,我们认为秋秋不是在后台想自启动,而是通过正常途径启动子进程。

而进行是否存活的判断调用较频繁,而我们的系统应用不需要进行此判断,所以调用前再加入是否系统应用的判断。实现代码diff如下

java 复制代码
--- a/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
+++ b/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
@@ -2459,14 +2459,27 @@ public final class ProcessList {
         return success ? app : null;
     }
 
+    public boolean isAppRunning(String packageName) {
+        List<ActivityManager.RunningAppProcessInfo> runningProcesses = mService.getRunningAppProcesses();
+        if (runningProcesses.size() > 0) {
+            for (ActivityManager.RunningAppProcessInfo info : runningProcesses) {
+                if (info.processName.equals(packageName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     @GuardedBy("mService")
     private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
         StringBuilder sb = null;
         // add by sprocomm start
-        if (app.hostingRecord.getName() != null) {
+		 //非系统应用执行if中的逻辑
+        if (((app.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) && app.hostingRecord.getName() != null) {
             String name = app.hostingRecord.getName();
-            Slog.i("ActivityManager", "xumaoxin: broadcast= " + name);
-            if (name.contains("NetConnInfoCenter")) {
+			 //秋秋是否存活,这个包名,可以动态到白名单中,和NetConnInfoCenter这个组件名放一起
+            boolean isRunning = isAppRunning("com.tencent.mobileqq");
+            Slog.i("ActivityManager", "xumaoxin: com.tencent.mobileqq isRunning= "+ isRunning + ", broadcast= " + name);
+            if (!isRunning && name.contains("NetConnInfoCenter")) {
                 if (sb == null) sb = new StringBuilder();
                 sb.append("dont allow qq auto start");
             }
相关推荐
2401_8576100315 分钟前
Spring Boot框架:电商系统的技术优势
java·spring boot·后端
帅次18 分钟前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
希忘auto31 分钟前
详解MySQL安装
java·mysql
冰淇淋烤布蕾43 分钟前
EasyExcel使用
java·开发语言·excel
拾荒的小海螺1 小时前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
Jakarta EE1 小时前
正确使用primefaces的process和update
java·primefaces·jakarta ee
枯骨成佛1 小时前
Android中Crash Debug技巧
android
马剑威(威哥爱编程)1 小时前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
java—大象2 小时前
基于java+springboot+layui的流浪动物交流信息平台设计实现
java·开发语言·spring boot·layui·课程设计
杨哥带你写代码2 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端