某音乐软件在原生Pixel被拦截自启动后导致系统NFC无限崩溃

123 篇文章 7 订阅
57 篇文章 0 订阅

某音乐软件在原生Pixel被拦截自启动后导致系统NFC无限崩溃

本文代码基于Android 12

起因

在调试Pixel的时候,发现每次重启,国内某音乐软件的播放通知栏就会显示在锁屏上,按照以前的逻辑,这应该是接收开机广播拉起的进程,但调查之后却发现事实并没有那么简单,接下来让我们一起去看看这个软件的自启方式(不知道google issue tracker上面有没有对应的issue,希望google能尽早修复这个问题吧)。

某音乐软件如何自启

一般遇到这些应用自启的问题,先从ProcessList#startProcessLocked方法入手,因为所有应用进程创建都会经过这里,把AOSP代码导入Android Studio中,在这个方法打一个断点,当命中断点的时候,输出堆栈log,我们来看看输出的堆栈

05-09 01:34:41.891  3174  3442 E ContentPane: startProcess: name=com.xxxxxx.xxmusic app=null knownToBeDead=true thread=null pid=-1
05-09 01:34:41.891  3174  3442 E ContentPane: java.lang.Throwable: ContentPane
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ProcessList.startProcessLocked(ProcessList.java:2492)
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ActivityManagerService.startProcessLocked(ActivityManagerService.java:2686)
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ActiveServices.bringUpServiceLocked(ActiveServices.java:3845)
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ActiveServices.bindServiceLocked(ActiveServices.java:2819)
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ActivityManagerService.bindIsolatedService(ActivityManagerService.java:12003)
05-09 01:34:41.891  3174  3442 E ContentPane:   at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2606)
05-09 01:34:41.891  3174  3442 E ContentPane:   at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2498)
05-09 01:34:41.891  3174  3442 E ContentPane:   at android.os.Binder.execTransactInternal(Binder.java:1179)
05-09 01:34:41.891  3174  3442 E ContentPane:   at android.os.Binder.execTransact(Binder.java:1143)

一般接收开机广播起来的话都是从BroadcastQueue调用过来,但是可以看到这个进程是通过ActiveServices#bindServiceLocked起来的,也就是应用或者系统调用startServicebindService的时候,发现没有相应进程,而调用ProcessList#startProcessLocked拉起相应组件的进程。

接下来,就是看看是谁拉起的进程,我们接着在ActivityManagerService#bindIsolatedService方法,通过Binder.getCallingPidBinder.getCallingUid都打印出来。

05-09 01:34:41.691  3174  3442 D ContentPane: service is Intent { act=android.nfc.cardemulation.action.HOST_APDU_SERVICE cmp=com.xxxxxx.xxmusic/com.xxxxxx.xx.plugin.appbrand.jsapi.nfc.hce.HCEService } callingPackage is com.android.nfc uid=1027 pid=4043

发现是nfc通过action拉起的服务,此时的我明白了一些问题,原来是这个音乐软件利用系统服务或者系统应用初始化时或者执行某些流程时需要绑定一些第三方应用的action的特性,来把自己的进程拉起来。

如何通过ifw拦截某音乐软件启动

接下来问题就很好解决了,一切的component或者aciton都可以被IntentFirewall拦截下来,只需要编写一些简单的规则,就可以禁止该component或者action的启动,不了解IntentFirewall的同学可以看IFW 是什么,而规则的编写可以在github上搜索规则的关键字component-filter或者intent-filter,它们分别代表通过组件过滤启动、通过action过滤启动。而我就编写了一个规则

<rules>
    <service block="true" log="true">
        <component-filter name="com.xxxxxx.xxmusic/com.xxxxxx.xx.plugin.appbrand.jsapi.nfc.hce.HCEService" />
    </service>
</rules>

这个规则的意思就是告诉AMS如果有组件名长这样的,就把它拦截下来不允许启动,把这个写好的规则保存为xml并push到路径data/system/ifw下,因为IntentFirewall通过FileObserver监测这个文件夹文件的这些操作,所以规则是马上生效的。

private static final int MONITORED_EVENTS = FileObserver.CREATE|FileObserver.MOVED_TO|
        FileObserver.CLOSE_WRITE|FileObserver.DELETE|FileObserver.MOVED_FROM;

但在这里,我们只有重启才能复现现象,所以我们重启手机,果然该音乐应用没有启动,没有显示在锁屏界面上,ps进程也没发现该音乐应用的进程启动,说明我们的规则是生效了。

拦截某音乐软件启动后手机莫名发热

这时候我就没有继续看该应用的自启问题,转而去看其他应用的自启问题,但过了一会发现手机一直发热,电量也在快速往下掉,我打印event log发现nfc应用一直在crash且重新创建进程,然后我执行adb logcat -s AndroidRuntime发现这个crash堆栈一直在刷

05-09 05:03:38.341  1619  1619 E AndroidRuntime: java.lang.SecurityException: Not allowed to bind to service Intent { act=android.nfc.cardemulation.action.HOST_APDU_SERVICE cmp=com.xxxxxx.xxmusic/com.xxxxxx.xx.plugin.appbrand.jsapi.nfc.hce.HCEService }
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1984)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.app.ContextImpl.bindServiceAsUser(ContextImpl.java:1919)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.content.ContextWrapper.bindServiceAsUser(ContextWrapper.java:829)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at com.android.nfc.cardemulation.HostEmulationManager.bindPaymentServiceLocked(HostEmulationManager.java:382)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at com.android.nfc.cardemulation.HostEmulationManager.lambda$onPreferredPaymentServiceChanged$0$HostEmulationManager(HostEmulationManager.java:118)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at com.android.nfc.cardemulation.HostEmulationManager$$ExternalSyntheticLambda0.run(Unknown Source:4)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.os.Handler.handleCallback(Handler.java:938)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:99)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.os.Looper.loopOnce(Looper.java:201)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.os.Looper.loop(Looper.java:288)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at android.app.ActivityThread.main(ActivityThread.java:7839)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at java.lang.reflect.Method.invoke(Native Method)
05-09 05:03:38.341  1619  1619 E AndroidRuntime:        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(Runt

通过这个堆栈可以看出,因为无法启动这个服务,所以在ContextImpl中抛出了SecurityException异常,而且com.android.nfc本来是一个persist应用,所以会一直创建进程,一直crash,一直杀进程,这样手机不发热才怪呢。。

frameworks/base/core/java/android/app/ContextImpl.java

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
            String instanceName, Handler handler, Executor executor, UserHandle user) {
        // ...
        try {
            // ...
            int res = ActivityManager.getService().bindIsolatedService(
                mMainThread.getApplicationThread(), getActivityToken(), service,
                service.resolveTypeIfNeeded(getContentResolver()),
                sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
            if (res < 0) {
                throw new SecurityException(
                        "Not allowed to bind to service " + service);
            }
            return res != 0;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

修复Nfc无限崩溃问题

发现这个问题后,我马上删除了ifw的规则文件,但这样又无法管控住这个应用的自启动,我尝试转换思路,只要我在nfc代码中catch住这个SecurityException异常,那是不是nfc就不会crash了,通过堆栈,我们很快定位到HostEmulationManager.java这个文件的报错,我们看下报错的代码

376      void bindPaymentServiceLocked(int userId, ComponentName service) {
377          unbindPaymentServiceLocked();
378  
379          Intent intent = new Intent(HostApduService.SERVICE_INTERFACE);
380          intent.setComponent(service);
381          mLastBoundPaymentServiceName = service;
382          if (mContext.bindServiceAsUser(intent, mPaymentConnection,
383                  Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
384                  new UserHandle(userId))) {
385            mPaymentServiceBound = true;
386          } else {
387              Log.e(TAG, "Could not bind (persistent) payment service.");
388          }
389      }

可以看到382行代码,正是我们一直crash的地方,执行着bindService的操作,但没有catch SecurityException的代码,所以当可以找到组件但无法启动这个service的时候,这里就会发生crash。有趣的是,我在这个类HostEmulationManager.java发现另外一个方法,这里会对bindServiceAsUser进行try catch,但看方法名后缀是IfNeeded,这在系统源码的一些约定俗成的规则代表可能执行也可能不执行,所以用try catch进行异常的捕捉。

Messenger bindServiceIfNeededLocked(ComponentName service) {
    if (mPaymentServiceName != null && mPaymentServiceName.equals(service)) {
        Log.d(TAG, "Service already bound as payment service.");
        return mPaymentService;
    } else if (mServiceName != null && mServiceName.equals(service)) {
        Log.d(TAG, "Service already bound as regular service.");
        return mService;
    } else {
        Log.d(TAG, "Binding to service " + service);
        unbindServiceIfNeededLocked();
        Intent aidIntent = new Intent(HostApduService.SERVICE_INTERFACE);
        aidIntent.setComponent(service);
        try {
            mServiceBound = mContext.bindServiceAsUser(aidIntent, mConnection,
                    Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                    UserHandle.CURRENT);
            if (!mServiceBound) {
                Log.e(TAG, "Could not bind service.");
            }
        } catch (SecurityException e) {
            Log.e(TAG, "Could not bind service due to security exception.");
        }
        return null;
    }
}

而我的workaround则是模仿这个方法,对bindServiceAsUser进行try catch,但没仔细分析如果这个PaymentService不绑定的话会对系统nfc有多大的影响,但这个音乐软件没安装之前,也没有对应action的PaymentService,我猜对nfc影响应该不大。

void bindPaymentServiceLocked(int userId, ComponentName service) {
    unbindPaymentServiceLocked();

    Intent intent = new Intent(HostApduService.SERVICE_INTERFACE);
    intent.setComponent(service);
    mLastBoundPaymentServiceName = service;
    try {
        mPaymentServiceBound = mContext.bindServiceAsUser(intent, mPaymentConnection,
                Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                new UserHandle(userId));
        if (!mPaymentServiceBound) {
            Log.e(TAG, "Could not bind service.");
        }
    } catch (SecurityException e) {
        Log.e(TAG, "Could not bind (persistent) payment service due to security exception.");
    }
}

修改后,我重新编译了Nfc的系统App,push到Nfc的扫描路径下、把规则重新push到路径data/system/ifw后重启,某音乐软件没有自启,且Nfc也不再崩溃了。

某音乐软件如何让自己被Nfc拉起

这里简单分析下音乐软件如何让自己被Nfc拉起,通过dumpsys package,发现以下信息

Service Resolver Table:
  Non-Data Actions:
      android.nfc.cardemulation.action.HOST_APDU_SERVICE:
        13200b3 com.xxxxxx.xxmusic/com.xxxxxx.xx.plugin.appbrand.jsapi.nfc.hce.HCEService filter bf30670 permission android.permission.BIND_NFC_SERVICE
          Action: "android.nfc.cardemulation.action.HOST_APDU_SERVICE"
          Category: "android.intent.category.DEFAULT"

通过在AndroidManifest.xml中把自己的服务HCEServiceAction: "android.nfc.cardemulation.action.HOST_APDU_SERVICE"进行绑定,用户重启手机的时候,nfc进程也会启动,nfc进程启动的时候会寻找android.nfc.cardemulation.action.HOST_APDU_SERVICE对应的组件进行绑定,这样就给第三方应用进程拉起来了。

说在后面

对于上面这样的情况,就算通过ifw拦截了该组件,如果不修改nfc应用的代码,nfc进程无限崩溃,电量肉眼可见地掉下来,这也是不可接受的,而nfc系统软件是不可卸载的,因为是persist应用,自然也不能通过更新解决,而普通用户可以通过执行adb shell pm disable com.xxxxxx.xxmusic/com.xxxxxx.xx.plugin.appbrand.jsapi.nfc.hce.HCEService指令把该服务disable而不会造成nfc crash和防止自启动。可以说这个自启方法让我见识到了App自启的"决心",和ROM厂商在防止App自启上与App的斗智斗勇,只能说国内软件生态还需继续加油。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值