源码级解析安卓SELinux应用权限分配
在运用了上文的方法(好吧这个上文我还没发)将我的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是从哪儿传进去的。
以下列表倒序展示调用树,不再赘述。
-
/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp#589
- selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str);
-
/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp#665
- ForkAndSpecializeCommon(env, uid, gid, gids, debug_flags, rlimits, capabilities, capabilities, mount_external, se_info, se_name, false, fdsToClose, instructionSet, appDataDir);
-
/frameworks/base/core/java/com/android/internal/os/Zygote.java#95
- nativeForkAndSpecialize(uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, instructionSet, appDataDir);
-
/frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java#225
- Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet, parsedArgs.appDataDir);
-
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java#850
- peers.get(i).runOnce();
到这里就断了。因为我们不知道这个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#readLPw在PackageManagerService初始化时调用,读取的文件硬编码在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字段,问题解决。
就这么个问题?哇。我真特么是闲的!
参考资料: