0%

Android system_server 执行命令无权限问题

最近有个需求:有一个系统应用,在某个业务时执行了一些 shell 命令,操作系统中的网络接口。随着版本迭代需求演进,要把这些 shell 命令迁移到 framework 中执行。

想当然的认为没什么难度,但属实是踩了一坑。

什么命令

难道是这些命令过于特殊?那到底执行的什么不得了的命令?

命令不复杂,都是常见的指令:

ifconfig eth0 0.0.0.0 up
ifconfig eth1 0.0.0.0 up
brctl addbr br0
brctl addif br0 eth0
brctl addif br0 eth1
ifconfig br0 up
ifconfig br0 10.10.10.10 netmask 255.255.255.0
route add default via 10.10.10.1

brctl delif br0 eth0
brctl delif br0 eth1
ifconfig br0 down
brctl delbr br0

执行之后报错操作无权限。

ifconfig: ioctl 8916: Operation not permitted
brctl: ioctl 89a0: Operation not permitted

邪门

奇怪的是, 这些命令在迁移前是在一个应用内执行的,运行却没有什么问题,可以照常运行。

按照常理,system_server 代表着操作系统本身,应该拥有极高的权限才对。

没办法,既然报错了,那就准备攻破吧。

系统环境

需求是通过代码调用系统命令行,那多半是某些未知的权限不够。

可以通过在系统调用 id -a 尝试分析权限信息,因为都是通过命令行调用,所以有很高的重现几率。

下面是一个示例代码:

	// call system command
	execSystemCommand("id -a");

    public static void execSystemCommand(String command) {
        String TAG = "EXEC";
        try {
            Process process = Runtime.getRuntime().exec(command);
            readInputStream(process.getInputStream(), (line) -> Log.i(TAG, line));
            readInputStream(process.getErrorStream(), (line) -> Log.e(TAG, line));
            int result = process.waitFor();
            Log.i(TAG, "execSystemCommand, command:" + command + ", result:" + result);
        } catch (Exception e) {
            Log.e(TAG, "execSystemCommand exception, command:" + command, e);
        }
    }

    @SuppressLint("NewApi")
    private static void readInputStream(
                java.io.InputStream inputStream,
                java.util.function.Consumer<String> consumer
    ) throws java.io.IOException {
        try (java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
            new java.io.InputStreamReader(inputStream)
        )) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                consumer.accept(line);
            }
        }
    }

分别在给代码不同的角色运行,结果如下:

  • Apps
uid=10059(u0_a59) gid=10059(u0_a59) groups=10059(u0_a59),3003(inet),9997(everybody),20059(u0_a59_cache),50059(all_a59) context=u:r:platform_app:s0:c512,c768
  • System Apps
uid=1000(system) gid=1000(system) groups=1000(system),1007(log),1015(sdcard_rw),1023(media_rw),1065(reserved_disk),3001(net_bt_admin),3002(net_bt),3003(inet),3005(net_admin),3006(net_bw_stats),5507(vendor_installer),9997(everybody),9997(everybody) context=u:r:system_app:s0
  • system_server
uid=1000(system) gid=1000(system) groups=1000(system),1001(radio),1002(bluetooth),1003(graphics),1004(input),1005(audio),1006(camera),1007(log),1008(compass),1009(mount),1010(wifi),1018(usb),1021(gps),1023(media_rw),1024(mtp),1032(package_info),1065(reserved_disk),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3007(net_bw_acct),3009(readproc),3010(wakelock),3011(uhid) context=u:r:system_server:s0

后来发现用命令行就可以快速查看应用运行时权限组信息,不过只输出了 gid 没有名称:

  • 普通应用运行时权限信息
android:/ # cat /proc/$(pidof com.example.app)/status | grep Groups
Groups: 3003 9997 20060 50060
  • 系统服务运行时权限信息
android:/ # cat /proc/$(pidof system_server)/status | grep Groups
Groups: 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1018 1021 1023 1024 1032 1065 3001 3002 3003 3005 3006 3007 3009 3010 3011

对应名称可以在系统源码中找到:
https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h

果然,系统应用对比 system_server 多了一个 3005(net_admin) 的权限,熟悉 Linux 的网络子系统的大佬看这个权限名称基本就知道问题所在。

疑点重重

但是奇怪的地方又来了,程序对应的用户权限怎么和运行着的程序权限不一样?

  • adb shell (root)权限信息
android:/ # id -n
uid=0(root) gid=0(root) groups=0(root),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid) context=u:r:su:s0
  • 系统用户权限信息
android:/ # id -n system
uid=1000(system) gid=1000(system) groups=1000(system), context=u:r:su:s0

也就是 system 这个用户,用户组实际分配的权限只有 groups=1000(system),为什么使用这个账户跑起来的程序的权限组却又五花八门?

真相只有一个,就是有人动态修改程序运行时的权限信息:

在 Android 系统中,应用是通过 AndroidManifst.xml 来申明需要的权限,对于一些敏感权限还需要动态申请。实际原理则是系统(也就是 system_servr)通过判断应用是否包含 android.permission.INTERNET 这样的权限申明,来给进程赋予对应的权限组。

<permission name="android.permission.INTERNET" >
	<group gid="inet" />
</permission>

其中细节可以查看:
https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/data/etc/platform.xml
这里讨论的时 apk 应用,系统内的 native 不在这个范畴;一般情况 native 进程由 rc 拉起,对应的权限信息也由 rc 定义。还有一些 native 是被间接拉起,这时会继承父进程的权限。

真相大白

还得但是!system_server 的权限又是哪里来的呢?

这时就不得不提 Android 系统中大名鼎鼎的 zygote 进程了,system_server 正式由它 frok 出来的。

稍微撸一撸代码,就能找到设置权限组的地方:

    /**
     * Prepare the arguments and forks for the system server process.
     *
     * @return A {@code Runnable} that provides an entrypoint into system_server code in the child
     * process; {@code null} in the parent.
     */
    private static Runnable forkSystemServer(String abiList, String socketName,
            ZygoteServer zygoteServer) {
        long capabilities = posixCapabilitiesAsBits(
                OsConstants.CAP_IPC_LOCK,
                OsConstants.CAP_KILL,
                OsConstants.CAP_NET_ADMIN,
                OsConstants.CAP_NET_BIND_SERVICE,
                OsConstants.CAP_NET_BROADCAST,
                OsConstants.CAP_NET_RAW,
                OsConstants.CAP_SYS_MODULE,
                OsConstants.CAP_SYS_NICE,
                OsConstants.CAP_SYS_PTRACE,
                OsConstants.CAP_SYS_TIME,
                OsConstants.CAP_SYS_TTY_CONFIG,
                OsConstants.CAP_WAKE_ALARM,
                OsConstants.CAP_BLOCK_SUSPEND
        );
        /* Containers run without some capabilities, so drop any caps that are not available. */
        StructCapUserHeader header = new StructCapUserHeader(
                OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
        StructCapUserData[] data;
        try {
            data = Os.capget(header);
        } catch (ErrnoException ex) {
            throw new RuntimeException("Failed to capget()", ex);
        }
        capabilities &= ((long) data[0].effective) | (((long) data[1].effective) << 32);

        /* Hardcoded command line to start the system server */
        String args[] = {
                "--setuid=1000",
                "--setgid=1000",
                "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
                        + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010",
                "--capabilities=" + capabilities + "," + capabilities,
                "--nice-name=system_server",
                "--runtime-args",
                "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
                "com.android.server.SystemServer",
        };
        ZygoteArguments parsedArgs = null;

        int pid;

        try {
            parsedArgs = new ZygoteArguments(args);
            Zygote.applyDebuggerSystemProperty(parsedArgs);
            Zygote.applyInvokeWithSystemProperty(parsedArgs);

            boolean profileSystemServer = SystemProperties.getBoolean(
                    "dalvik.vm.profilesystemserver", false);
            if (profileSystemServer) {
                parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
            }

            /* Request to fork the system server process */
            pid = Zygote.forkSystemServer(
                    parsedArgs.mUid, parsedArgs.mGid,
                    parsedArgs.mGids,
                    parsedArgs.mRuntimeFlags,
                    null,
                    parsedArgs.mPermittedCapabilities,
                    parsedArgs.mEffectiveCapabilities);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }

        /* For child process */
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }

            zygoteServer.closeServerSocket();
            return handleSystemServerProcess(parsedArgs);
        }

        return null;
    }

可以看到,果然没有 3005。而在之后的 Android 13 版本中,源码已经默认给了 3005 的权限组。

所以,在这加上一个 3005 的权限组 gid 就解决了问题。

具体看源码:

底层原理

  • 为什么多了一个 gid 就过了权限认证?selinux 不用考虑吗?
    空了再挖吧!

资料

  • 本文作者: 6x
  • 本文链接: https://6xyun.cn/article/215
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处!