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");
             }
相关推荐
王德博客5 分钟前
【Java课堂笔记】Java 入门基础语法与面向对象三大特性详解
java·开发语言
seventeennnnn13 分钟前
Java大厂面试真题:谢飞机的技术挑战
java·spring boot·面试·aigc·技术挑战·电商场景·内容社区
wkj00123 分钟前
接口实现类向上转型和向上转型解析
java·开发语言·c#
qqxhb24 分钟前
零基础设计模式——行为型模式 - 观察者模式
java·观察者模式·设计模式·go
寒士obj1 小时前
类加载的过程
java·开发语言
无名之逆1 小时前
大三自学笔记:探索Hyperlane框架的心路历程
java·开发语言·前端·spring boot·后端·rust·编程
Chuck1sn1 小时前
我把 Cursor AI 整合到 Ruoyi 中,从此让 Java 脚手架脱离人工!
java·vue.js·后端
敲代码的剑缘一心1 小时前
手把手教你学会写 Gradle 插件
android·gradle
水木石画室1 小时前
Spring Boot 常用注解面试题深度解析
java·spring boot·后端
hweiyu001 小时前
tomcat指定使用的jdk版本
java·开发语言·tomcat