Skip to content

Instantly share code, notes, and snippets.

@5ec1cff
Created March 12, 2022 15:48
Show Gist options
  • Save 5ec1cff/af38e9abcef4a821a0e43606787966c3 to your computer and use it in GitHub Desktop.
Save 5ec1cff/af38e9abcef4a821a0e43606787966c3 to your computer and use it in GitHub Desktop.
消失的 window service

Window Manager Service 是 Android 的重要服务,各种窗口(Activity, toast, Dialog, 系统 UI 等)都通过这个服务注册和管理。

经常玩系统隐藏 API 的都知道这个实际上是一个名为 window 的 Binder 服务,通过 ServiceManager.getService("window") 就能拿到它的 BinderProxy ,进而转为 android.view.IWindowManager 直接调用 API

但是当你在 app 的 shell 下尝试获取这个服务,却会发现根本无法找到(以下在 Termux 中测试):

image

对比 activity 服务:

image

类似的,也可以写一个 java 程序来 getService ,直接用 app_process 在普通用户下跑,得到的同样是 null ,换句话说,似乎普通应用根本没法拿到这个服务。

不过 root 或者 adb 仍然是能访问的,并且常用的 dumpsys 显然就是要获取这个服务才能进行的。

image

这就非常奇怪了:应用既然无法获取 window 服务,怎么和 WMS 通信呢?

经过一番源码搜索,发现 ServiceManager 中,影响 checkService 的结果取决于两个因素:

  1. 服务是否允许 isolated
  2. selinux 检查 (主动调用 selinux_check_access )
Android > 10: frameworks/native/cmds/servicemanager/ServiceManager.cpp
Android <=10: frameworks/native/cmds/servicemanager/service_manager.c
Status ServiceManager::checkService(const std::string& name, sp<IBinder>* outBinder) {
    *outBinder = tryGetService(name, false);
    // returns ok regardless of result for legacy reasons
    return Status::ok();
}

sp<IBinder> ServiceManager::tryGetService(const std::string& name, bool startIfNotFound) {
    auto ctx = mAccess->getCallingContext();

    sp<IBinder> out;
    Service* service = nullptr;
    if (auto it = mNameToService.find(name); it != mNameToService.end()) {
        service = &(it->second);

        if (!service->allowIsolated) {
            uid_t appid = multiuser_get_app_id(ctx.uid);
            bool isIsolated = appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END;

            if (isIsolated) {
                return nullptr;
            }
        }
        out = service->binder;
    }

    if (!mAccess->canFind(ctx, name)) {
        return nullptr;
    }

    if (!out && startIfNotFound) {
        tryStartService(name);
    }

    if (out) {
        // Setting this guarantee each time we hand out a binder ensures that the client-checking
        // loop knows about the event even if the client immediately drops the service
        service->guaranteeClient = true;
    }

    return out;
}

而 WMS 虽然不允许 isolated 进程访问(显然的),但好像和我们的普通应用也没什么关系。

// frameworks/base/services/java/com/android/server/SystemServer.java: startOtherServices
            wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
                    new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
            ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);

因此问题可能就是 SELinux 导致的。尝试 setenforce 0 ,果然在应用用户下就能 check 到了。

实际上每个 Binder 服务都有自己的 selinux type ,叫 ${service name}_service ,在 system/sepolicy/public/service.te 可以看到:

type activity_service, app_api_service, ephemeral_app_api_service, system_server_service, service_manager_type;
type window_service, system_api_service, system_server_service, service_manager_type;

ams 是 app_api_service ,而 wms 是 system_api_service (attribute)

image

反编译并搜索 sepolicy 可以发现 app 不能访问后者。(具有 find 权限才能从 servicemanager 得到这个 binder)

// system/sepolicy/private/untrusted_app_all.te 规定了 untrusted app 能访问的 service
allow untrusted_app_all app_api_service:service_manager find;

// system/sepolicy/private/priv_app.te
// priv app 就可以访问
allow priv_app system_api_service:service_manager find;

// system/sepolicy/public/shell.te
// shell 可访问绝大多数的 binder service 并 dump
# allow shell access to services
allow shell servicemanager:service_manager list;
# don't allow shell to access GateKeeper service
# TODO: why is this so broad? Tightening candidate? It needs at list:
# - dumpstate_service (so it can receive dumpstate progress updates)
allow shell {
  service_manager_type
  -apex_service
  -dnsresolver_service
  -gatekeeper_service
  -incident_service
  -installd_service
  -iorapd_service
  -mdns_service
  -netd_service
  -system_suspend_control_internal_service
  -system_suspend_control_service
  -virtual_touchpad_service
  -vold_service
  -default_android_service
}:service_manager find;
allow shell dumpstate:binder call;

那么 app 怎么获取 wms 的呢?实际上奥秘藏在 ServiceManager.java 和 AMS :

java 层的 ServiceManager 类有一个叫 sCache 的 Map ,存储已知的服务:

// frameworks/base/core/java/android/os/ServiceManager.java
    public static void initServiceCache(Map<String, IBinder> cache) {
        if (sCache.size() != 0) {
            throw new IllegalStateException("setServiceCache may only be called once");
        }
        sCache.putAll(cache);
    }

而当应用进程创建,bindApplication 的时候会调用这个方法,其中包含了从 AMS 来的服务。

// frameworks/base/core/java/android/app/ActivityThread.java:IApplicationThread

        @Override
        public final void bindApplication(String processName, ApplicationInfo appInfo,
                /* ... */ Map services, /* ... */) {
            if (services != null) {
                // Setup the service cache in the ServiceManager
                ServiceManager.initServiceCache(services);
            }


// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
// 服务从这里来
    /**
     * Initialize the application bind args. These are passed to each
     * process when the bindApplication() IPC is sent to the process. They're
     * lazily setup to make sure the services are running when they're asked for.
     */
    private ArrayMap<String, IBinder> getCommonServicesLocked(boolean isolated) {
        // Isolated processes won't get this optimization, so that we don't
        // violate the rules about which services they have access to.
        if (isolated) {
        // ...
        }

        if (mAppBindArgs == null) {
            mAppBindArgs = new ArrayMap<>();

            // Add common services.
            // IMPORTANT: Before adding services here, make sure ephemeral apps can access them too.
            // Enable the check in ApplicationThread.bindApplication() to make sure.
            addServiceToMap(mAppBindArgs, "package");
            addServiceToMap(mAppBindArgs, "permissionmgr");
            addServiceToMap(mAppBindArgs, Context.WINDOW_SERVICE);
        // ...

其中就有 WMS 。所以,应用得到的 wms binder 其实是 AMS 喂到它的缓存里面的,这就解释了为什么应用虽然没权限从 ServiceManager 拿 wms ,但还是能用 wms 的现象。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment