Contents

源码级解析安卓SELinux应用权限分配

Contents

在运用了上文的方法(好吧这个上文我还没发)将我的ROM用自己的证书签名以后,我遇到了一个奇怪的bug,开机载入完成后会显示“系统界面已停止运行”,点击“关闭应用”以后会无限循环报错。这里结合安卓7.1.1源码,小记一下因为SELinux导致的这个奇怪问题的解决思路。

首先当然得查看一下log。得亏开了adb,还能logcat进去。在前台报错时,log中蹦出如下消息。

06-30 08:24:21.600  3103  3103 E SELinux : seapp_context_lookup:  No match for app with uid 1001, seinfo default, name com.android.phone
06-30 08:24:21.601  3103  3103 E SELinux : selinux_android_setcontext:  Error setting context for app with uid 1001, seinfo default:privapp: Success
06-30 08:24:21.601  3103  3103 E Zygote  : selinux_android_setcontext(1001, 0, "default:privapp", "com.android.phone") failed
06-30 08:24:21.601  3103  3103 F art     : art/runtime/jni_internal.cc:492] JNI FatalError called: frameworks/base/core/jni/com_android_internal_os_Zygote.cpp:631: selinux_android_setcontext failed
06-30 08:24:21.603  1608  1652 I ActivityManager: Start proc 3103:com.android.phone/1001 for added application com.android.phone
06-30 08:24:21.620  3103  3103 F art     : art/runtime/runtime.cc:422] Runtime aborting...
06-30 08:24:21.620  3103  3103 F art     : art/runtime/runtime.cc:422] Aborting thread:
06-30 08:24:21.620  3103  3103 F art     : art/runtime/runtime.cc:422] "main" prio=5 tid=1 Native
06-30 08:24:21.620  3103  3103 F art     : art/runtime/runtime.cc:422]   | group="" sCount=0 dsCount=0 obj=0x74cc1990 self=0x7fafe96a00
...
06-30 08:24:21.620  3103  3103 F art     : art/runtime/runtime.cc:422]
06-30 08:24:21.621  3103  3103 F libc    : Fatal signal 6 (SIGABRT), code -6 in tid 3103 (main)
06-30 08:24:21.621   458   458 W         : debuggerd: handling request: pid=3103 uid=1001 gid=1001 tid=3103
06-30 08:24:21.629  1608  1652 W DropBoxManagerService: Dropping: system_server_wtf (965 > 0 bytes)
06-30 08:24:21.638  1608  1652 W ActivityManager: Process ProcessRecord{d133a66 2799:com.android.settings/1000} failed to attach
06-30 08:24:21.638  1608  1652 I ActivityManager: Killing 2799:com.android.settings/1000 (adj -10000): start timeout
06-30 08:24:21.685  3108  3108 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
06-30 08:24:21.685  3108  3108 F DEBUG   : LineageOS Version: '14.1-20170629-UNOFFICIAL-oneplus3'
06-30 08:24:21.685  3108  3108 F DEBUG   : Build fingerprint: 'OnePlus/OnePlus3/OnePlus3:7.1.1/NMF26F/05151830:user/release-keys'
06-30 08:24:21.685  3108  3108 F DEBUG   : Revision: '0'
06-30 08:24:21.685  3108  3108 F DEBUG   : ABI: 'arm64'
06-30 08:24:21.685  3108  3108 F DEBUG   : pid: 3103, tid: 3103, name: main  >>> zygote64 <<<
06-30 08:24:21.685  3108  3108 F DEBUG   : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
06-30 08:24:21.688  3108  3108 F DEBUG   : Abort message: 'art/runtime/jni_internal.cc:492] JNI FatalError called: frameworks/base/core/jni/com_android_internal_os_Zygote.cpp:631: selinux_android_setcontext failed'
06-30 08:24:21.689  3108  3108 F DEBUG   :     x0   0000000000000000  x1   0000000000000c1f  x2   0000000000000006  x3   0000000000000008
...
06-30 08:24:21.695  3108  3108 F DEBUG   : backtrace:
06-30 08:24:21.696  3108  3108 F DEBUG   :     #00 pc 000000000006c990  /system/lib64/libc.so (tgkill+8)
...
06-30 08:24:21.696  3108  3108 F DEBUG   :     #10 pc 000000007485fe68  /data/dalvik-cache/arm64/system@framework@boot.oat (offset 0x29af000)
06-30 08:24:21.790   458   458 W         : debuggerd: resuming target 3103
06-30 08:24:21.795   751   751 I Zygote  : Process 3103 exited due to signal (6)

第一眼我是懵逼的……ART运行时爆炸了岂不是整个系统都爆炸了?不过从log很明显能看出,出错是因为SELinux的问题关掉就好了,所以这几天我用的临时解决办法都是,开机时候插上电脑,adb shell su root setenforce 0……也很麻烦所以我很想搞掉它。

开始我以为是Zygote缺乏对应的权限,于是我在dmesg筛出里面audit的部分,喂给编译Android时附赠的audit2allow,结果告诉我它需要好多——

cat ~/dmsg.log | audit2allow -p $OUT/root/sepolicy


#============= audioserver ==============
allow audioserver zygote:binder call;

#============= untrusted_app ==============
allow untrusted_app adbsecure_prop:file { getattr open };
allow untrusted_app adbtcp_prop:file { getattr open };
...

#============= zygote ==============
allow zygote ashmem_device:chr_file execute;
allow zygote audioserver:binder { call transfer };
...
allow zygote sysfs_wake_lock:file { open read write };
allow zygote system_data_file:dir { add_name remove_name write };
allow zygote system_data_file:file { create rename setattr unlink write };
allow zygote system_server:binder { call transfer };
allow zygote system_server:unix_stream_socket { read write };
allow zygote time_daemon:unix_stream_socket connectto;
allow zygote untrusted_app:binder { call transfer };
allow zygote user_profile_data_file:file { getattr lock open read write };
allow zygote wcnss_filter:unix_stream_socket connectto;
allow zygote wcnss_service_exec:file { execute execute_no_trans getattr open read };
allow zygote zygote_tmpfs:file execute;

我的第一反应是感觉不可能……差了这么多权限官方肯定早就搞定了啊!我把这些多出来的权限加到一个规则文件里重新编译,确实不尽如人意。选项排除。

于是目标集中在log前三行中的seapp_context_lookup了。我再次猜测,由于这个调用返回失败直接导致Zygote遇到fatal error退出,因为第三行错误信息的主人是Zygote。

06-30 08:24:21.600  3103  3103 E SELinux : seapp_context_lookup:  No match for app with uid 1001, seinfo default, name com.android.phone
06-30 08:24:21.601  3103  3103 E SELinux : selinux_android_setcontext:  Error setting context for app with uid 1001, seinfo default:privapp: Success
06-30 08:24:21.601  3103  3103 E Zygote  : selinux_android_setcontext(1001, 0, "default:privapp", "com.android.phone") failed

把这行错误粘到谷歌里,第一个结果是安卓源码中抛出错误的这个片段。下文的代码来源皆为同一网站 AndroidXref1,版本为7.1.1r6。跟我的就差一个小版本,没毛病。

这个错误的抛出定位到了 /frameworks/base/core/jni/com_android_internal_os_Zygote.cpp#589 。

rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str);
if (rc == -1) {
  ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid,
        is_system_server, se_info_c_str, se_name_c_str);
  RuntimeAbort(env, __LINE__, "selinux_android_setcontext failed");
}

从报错信息我们能看到,se_info_c_str这个参数被置为default:privapp,传入seapp_context_lookup时被解析为default。从这篇2文章我们又可以得知,context是从安卓根目录下的 /seapp_contexts文件读取的。

oneplus3:/ # cat /seapp_contexts
isSystemServer=true domain=system_server
user=system seinfo=platform domain=system_app type=system_app_data_file
user=bluetooth seinfo=platform domain=bluetooth type=bluetooth_data_file
user=nfc seinfo=platform domain=nfc type=nfc_data_file
user=radio seinfo=platform domain=radio type=radio_data_file
...

系统的uid对应关系在/system/core/include/private/android_filesystem_config.h#46 中,可以确定1001就是radio用户。而正确的seinfo应该是platform才会命中文件中的对应记录。我们继续寻找seinfo是从哪儿传进去的。

以下列表倒序展示调用树,不再赘述。

到这里就断了。因为我们不知道这个Zygote Connection是从哪儿来的……补了资料3以后得知,这个连接是由ActivityManagerService发起的。啊啊啊实在不想看了。

再换一个思路,se_info_c_str中包含的privapp一定是某个函数加上去的,查找此字串定位到一个类SELinuxMMAC.java。它的注释也引人注意:

/**
 * Centralized access to SELinux MMAC (middleware MAC) implementation. This
 * class is responsible for loading the appropriate mac_permissions.xml file
 * as well as providing an interface for assigning seinfo values to apks.
 *
 * {@hide}
 */

也就是说,这个类负责为程序指定他们的seinfo值。很高兴见到你。查找刚才字串(它是这个类的静态成员)的引用,定位到一个方法名为assignSeinfoValue

public static void assignSeinfoValue(PackageParser.Package pkg) {
    synchronized (sPolicies) {
        for (Policy policy : sPolicies) {
            String seinfo = policy.getMatchedSeinfo(pkg);
            if (seinfo != null) {
                pkg.applicationInfo.seinfo = seinfo;
                break;
            }
        }
    }

    if (pkg.applicationInfo.isAutoPlayApp())
        pkg.applicationInfo.seinfo += AUTOPLAY_APP_STR;

    if (pkg.applicationInfo.isPrivilegedApp())
        pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;  // PRIVILEGED_APP_STR = ":privapp";

    if (DEBUG_POLICY_INSTALL) {
        Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
                "seinfo=" + pkg.applicationInfo.seinfo);
    }
}

Gotcha。Policy类也在这个文件中,那我们找着他的getMatchedSeinfo

public String getMatchedSeinfo(PackageParser.Package pkg) {
    // Check for exact signature matches across all certs.
    Signature[] certs = mCerts.toArray(new Signature[0]);
    if (!Signature.areExactMatch(certs, pkg.mSignatures)) {
        return null;
    }

    // Check for inner package name matches given that the
    // signature checks already passed.
    String seinfoValue = mPkgMap.get(pkg.packageName);
    if (seinfoValue != null) {
        return seinfoValue;
    }

    // Return the global seinfo value.
    return mSeinfo;
}

它将policy.mCerts与pkg.mSignatures两个数组中的成员进行比较,如果签名匹配则从policy.mPkgMap中取出对应的seinfo值,若mPkgMap中没有则返回符合这个policy的默认seinfo。mPkgMap是在读取XML格式的SELinux的policy文件(包括/system/etc/security/mac_permissions.xml等)时,对单独的软件包指定的Context。比如淘宝和支付宝都有自己的context。

又由于pkg.applicationInfo.seinfo的默认值是default(ApplicationInfo.java#618),那我们就可以推得一定是由于policy.mCerts和pkg.mSignatures两个数组的元素不同导致platform签名的应用的权限被降到default。继续查找这两个数组的来源。

policy.mCerts是每个policy对象所含的所有签名证书,通过PolicyBuilder.addSignature(cert);添加到mCerts数组中。而这个函数又是由readInstallPolicy里面解析signer标签的函数调用的。readInstallPolicy读取的文件为/system/etc/security/mac_permissions.xml(SELinuxMMAC.java#62),我们得到了mCerts数据的来源。

pkg.mSignatures对象的内容也是从XML中读取来的,创建pkg.mSignatures对象的PackageSignatures.java#readXml方法在最外层由Settings.java#readLPwPackageManagerService初始化时调用,读取的文件硬编码在Settings对象的属性mSettingsFilename中,即/data/system/packages.xml。好啦mSignatures的来源我们也有了。

最后总结一下,系统在第一次初始化PackageManagerService的实例时(这个Service可能由任何其他Service启动,如OtaDexoptService),读取了packages.xml载入所有应用和他们的签名等等各种信息,在运行程序时ActivityManagerService告诉Zygote来fork并启动一个程序,Zygote让SELinux读取mac_permissions.xml(和/system/etc/permission/下面的一大票规则)并授予合适的运行权限,如果老铁没毛病,就上去干。

那修我的bug就容易了。

在mac_permissions.xml里面找到正确的程序签名,在此例中为seinfo为platform的signature,并复制它。

<?xml version="1.0" encoding="iso-8859-1"?>
<!-- AUTOGENERATED FILE DO NOT MODIFY -->
<policy>
    <signer signature=“308203...">
        <seinfo value="platform"/>
    </signer>
    <signer signature=“308202...">
        <package name="com.eg.android.AlipayGphone">
            <seinfo value="alipay"/>
        </package>
    </signer>
    <signer signature=“308202...">
        <package name="com.taobao.taobao">
            <seinfo value="taobao"/>
        </package>
    </signer>
    <signer signature=“308204...">
        <allow-all/>
        <seinfo value="release"/>
    </signer>
    <signer signature=“308202...">
        <allow-all/>
        <seinfo value="release"/>
    </signer>
    <signer signature=“308203...">
        <package name="com.cyanogenmod.updater">
            <seinfo value="cmupdater"/>
        </package>
    </signer>
    <signer signature=“308203...>
        <package name="org.cyanogenmod.themeservice">
            <seinfo value="themeservice"/>
        </package>
    </signer>
</policy>

在packages.xml里面com.android.phone这个package上找到我们要修改的cert。安卓为了省地方,一个cert只存一次,所以就像这样,我们可能在别的包的信息里才能找到我们要替换的cert:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
    <version sdkVersion="25" databaseVersion="3" fingerprint="OnePlus/OnePlus3/OnePlus3:7.1.1/NMF26F/05151830:user/release-keys" />
    <package name="com.android.cts.priv.ctsshim" codePath="/system/priv-app/CtsShimPrivPrebuilt” ... userId="10007" isOrphaned="true">
        <sigs count="1">
            <cert index="1" key=“308203…………" />
        </sigs>
        <proper-signing-keyset identifier="3" />
    </package>
    <package name="com.android.phone" codePath="/system/priv-app/TeleService" ... sharedUserId="1001" isOrphaned="true">
        <sigs count="1">
            <cert index="1" />
        </sigs>
        <perms>
            <item name="android.permission.SEND_RECEIVE_STK_INTENT" granted="true" flags="0" />
            ...

最后用我们刚复制的signature覆盖cert的key字段,问题解决。

就这么个问题?哇。我真特么是闲的!

参考资料: