轉(zhuǎn)自:https://www.cnblogs.com/ntiger/p/12141932.html
Android系統(tǒng)為每個(gè)應(yīng)用程序提供了一個(gè)安全的運(yùn)行環(huán)境,不同程序間相互隔離拷恨,應(yīng)用程序的數(shù)據(jù)等私有資源,外界無(wú)法訪問(wèn)。這個(gè)安全的運(yùn)行環(huán)境由Android的權(quán)限系統(tǒng)(可稱為沙箱系統(tǒng))來(lái)提供督笆。本文簡(jiǎn)單記錄Android權(quán)限系統(tǒng)的基本組成模塊和實(shí)現(xiàn)機(jī)制中的關(guān)鍵代碼喳整。
主要模塊
可以將Android權(quán)限系統(tǒng)分為4個(gè)模塊:
- 基于用戶ID的權(quán)限系統(tǒng)
- Capability權(quán)限系統(tǒng)
- Android Permission系統(tǒng)
- SELinux權(quán)限系統(tǒng)
基于用戶ID的權(quán)限系統(tǒng)
該權(quán)限系統(tǒng)基于進(jìn)程的UID來(lái)控制進(jìn)程對(duì)文件等資源的訪問(wèn)權(quán)限尔苦。簡(jiǎn)單來(lái)說(shuō),系統(tǒng)中每個(gè)進(jìn)程有一個(gè)UID和一個(gè)或多個(gè)GID屬性又官;每個(gè)文件具有一個(gè)UID和一個(gè)GID屬性,并且有三組權(quán)限位旅掂,分別表示和自己相同的UID進(jìn)程赏胚、相同的GID進(jìn)程,以及其他不相關(guān)進(jìn)程對(duì)文件的讀商虐、寫和執(zhí)行訪問(wèn)權(quán)限觉阅。內(nèi)核以UID作為權(quán)限管理的基本粒度單位。關(guān)于進(jìn)程對(duì)文件的具體訪問(wèn)權(quán)限規(guī)則秘车,可以查閱UNIX/Linux手冊(cè)或一些書籍典勇。(《UNIX環(huán)境高級(jí)編程》4.5節(jié))。
在典型的UNIX/Linux多用戶系統(tǒng)中叮趴,系統(tǒng)為每個(gè)登錄用戶分配一個(gè)UID割笙,所以權(quán)限控制的粒度是單個(gè)用戶。Android系統(tǒng)沒有傳統(tǒng)意義登錄用戶的概念眯亦,而是將UID分配給每個(gè)應(yīng)用程序伤溉,所以權(quán)限管理的粒度是單個(gè)應(yīng)用程序。具體運(yùn)行過(guò)程如下妻率。
-
應(yīng)用程序安裝時(shí)乱顾,系統(tǒng)為應(yīng)用程序分配一個(gè)UID。PackageManagerService默認(rèn)為每個(gè)應(yīng)用程序分配一個(gè)新的UID和GID宫静。如果應(yīng)用程序申請(qǐng)了某些特殊的運(yùn)行時(shí)權(quán)限走净,則為其分配(實(shí)際是將其加入)一組額外的GID Group券时。同一個(gè)開發(fā)者開發(fā)的兩個(gè)應(yīng)用(簽名相同),可以共享UID和GID伏伯,只需要在AndroidManifest中聲明同樣的
android:sharedUserId
屬性橘洞。應(yīng)用程序的UID/GID和其他屬性一起寫入packages.list
和packages.xml
文件中。// PMS分配UID代碼: // PackageManagerService.java if (newPkgSettingCreated) { if (originalPkgSetting != null) { mSettings.addRenamedPackageLPw(pkg.packageName, originalPkgSetting.name); } // THROWS: when we can't allocate a user id. add call to check if there's // enough space to ensure we won't throw; otherwise, don't modify state mSettings.addUserToSettingLPw(pkgSetting);
-
啟動(dòng)應(yīng)用程序進(jìn)程時(shí)说搅,ActivityManagerService向PackageManagerService查詢應(yīng)用程序的UID/GID等信息炸枣,并將這些信息作為參數(shù)傳遞給Zygote進(jìn)程。Zygote進(jìn)程為應(yīng)用程序fork出子進(jìn)程蜓堕,并按照參數(shù)設(shè)置子進(jìn)程的UID/GID抛虏,這樣應(yīng)用程序進(jìn)程就以自己所屬UID的身份運(yùn)行了。
// AMS 傳遞參數(shù)給Zygote // ActivityManagerService.java private ProcessStartResult startProcess(String hostingType, String entryPoint, ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal, String seInfo, String requiredAbi, String instructionSet, String invokeWith, long startTime) { try { ... } else { startResult = Process.start(entryPoint, app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, invokeWith, new String[] {PROC_START_SEQ_IDENT + app.startSeq}); } ... } // Zygote 根據(jù)參數(shù)設(shè)置進(jìn)程UID屬性 // com_android_internal_os_Zygote.cpp static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids, jint runtime_flags, jobjectArray javaRlimits, jlong permittedCapabilities, jlong effectiveCapabilities, jint mount_external, jstring java_se_info, jstring java_se_name, bool is_system_server, jintArray fdsToClose, jintArray fdsToIgnore, bool is_child_zygote, jstring instructionSet, jstring dataDir) { ... pid_t pid = fork(); if (pid == 0) { ... if (!SetGids(env, javaGids, &error_msg)) { fail_fn(error_msg); } ... int rc = setresgid(gid, gid, gid); ... rc = setresuid(uid, uid, uid); ... }
-
設(shè)置應(yīng)用程序的文件權(quán)限
a. 設(shè)置APK文件權(quán)限為所有用戶可讀套才,這樣系統(tǒng)或者別的應(yīng)用程序才可以訪問(wèn)應(yīng)用程序的代碼迂猴。
b. 系統(tǒng)為應(yīng)用程序創(chuàng)建的數(shù)據(jù)目錄,設(shè)置為其他用戶可執(zhí)行(搜索)背伴。如果不設(shè)置為可執(zhí)行沸毁,則用戶的任何數(shù)據(jù)文件不能共享給其他應(yīng)用程序。// ContextImpl.java public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { checkMode(mode); final boolean append = (mode&MODE_APPEND) != 0; File f = makeFilename(getFilesDir(), name); ... File parent = f.getParentFile(); parent.mkdir(); FileUtils.setPermissions( parent.getPath(), FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, -1, -1);
c. 應(yīng)用通過(guò)Context.openFileOutput等Android接口創(chuàng)建的數(shù)據(jù)文件默認(rèn)為其他用戶不可讀寫傻寂。如果用戶指定了
MODE_WORLD_READABLE
或者MODE_WORLD_WRITEABLE
息尺,則設(shè)置其他用戶可讀或者可寫。新版本這兩個(gè)mode已經(jīng)廢除疾掰。// ContextImpl.java public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { checkMode(mode); final boolean append = (mode&MODE_APPEND) != 0; File f = makeFilename(getFilesDir(), name); ... setFilePermissionsFromMode(f.getPath(), mode, 0); return fos; } static void setFilePermissionsFromMode(String name, int mode, int extraPermissions) { int perms = FileUtils.S_IRUSR|FileUtils.S_IWUSR |FileUtils.S_IRGRP|FileUtils.S_IWGRP |extraPermissions; if ((mode&MODE_WORLD_READABLE) != 0) { perms |= FileUtils.S_IROTH; } if ((mode&MODE_WORLD_WRITEABLE) != 0) { perms |= FileUtils.S_IWOTH; } if (DEBUG) { Log.i(TAG, "File " + name + ": mode=0x" + Integer.toHexString(mode) + ", perms=0x" + Integer.toHexString(perms)); } FileUtils.setPermissions(name, perms, -1, -1); }
d. 應(yīng)用通過(guò)File.createNewFile()等Java接口創(chuàng)建的數(shù)據(jù)文件默認(rèn)只有自己可讀寫搂誉。這種方式創(chuàng)建的文件權(quán)限與當(dāng)前進(jìn)程的umask設(shè)置相關(guān)。Android系統(tǒng)的init進(jìn)程在創(chuàng)建系統(tǒng)服務(wù)(包括zygote)時(shí)静檬,設(shè)置了umask為077炭懊,應(yīng)用程序繼承了zygote的umask,所以也是077拂檩,表示只保留相同UID的訪問(wèn)權(quán)限侮腹,僅允許相同UID的進(jìn)程(也就是自己)訪問(wèn)。
``C++ // system/core/init/service.cpp Result<Success> Service::Start() { ... pid = fork(); if (pid == 0) { umask(077); ... }
Capability 機(jī)制
基于UID的權(quán)限管理機(jī)制中稻励,有一個(gè)特殊的UID 0父阻,即所謂root用戶,具有超級(jí)權(quán)限望抽,不受權(quán)限機(jī)制的約束加矛。而有些系統(tǒng)資源和能力,只有root用戶才可以使用煤篙。所以系統(tǒng)中很多核心服務(wù)斟览,如adbd,zygote以root身份運(yùn)行舰蟆,而這些系統(tǒng)服務(wù)又頻繁與應(yīng)用程序交互趣惠,這些服務(wù)中存在的安全漏洞,很容易被惡意應(yīng)用程序利用身害,進(jìn)行提權(quán)操作味悄,突破系統(tǒng)權(quán)限管控。所以Android進(jìn)一步使用capability機(jī)制來(lái)限制UID 0的權(quán)限塌鸯。
Capability機(jī)制將只有root用戶可以訪問(wèn)的權(quán)限進(jìn)一步細(xì)分為一組能力侍瑟。每個(gè)線程有四組比特位來(lái)表示自身所擁有的權(quán)限:
$ adb shell cat /proc/<pid>/status
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000000000
其中CapInh表示執(zhí)行execve會(huì)保留的權(quán)限;CapEff表示線程當(dāng)前權(quán)限丙猬;CapPrm表示CapInh和CapEff的最大限制涨颜;CapBnd表示線程能夠獲得的最大權(quán)限。
Android系統(tǒng)中茧球,adbd和zygote是為應(yīng)用創(chuàng)建進(jìn)程的服務(wù)庭瑰。zygote服務(wù)在創(chuàng)建了子進(jìn)程,將子進(jìn)程返回給系統(tǒng)前抢埋,將CapBnd清除弹灭,這樣即使子進(jìn)程利用系統(tǒng)漏洞獲取了root uid,仍然沒有任何超級(jí)權(quán)限揪垄。adbd則在執(zhí)行完必須的特權(quán)任務(wù)后穷吮,清除CapBnd,將自己權(quán)限降低饥努。
// Zygote 設(shè)置子進(jìn)程CapBnd
// com_android_internal_os_Zygote.cpp
static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids,
jint runtime_flags, jobjectArray javaRlimits,
jlong permittedCapabilities, jlong effectiveCapabilities,
jint mount_external,
jstring java_se_info, jstring java_se_name,
bool is_system_server, jintArray fdsToClose,
jintArray fdsToIgnore, bool is_child_zygote,
jstring instructionSet, jstring dataDir) {
...
pid_t pid = fork();
if (pid == 0) {
...
if (!DropCapabilitiesBoundingSet(&error_msg)) {
fail_fn(error_msg);
}
...
Android Permission系統(tǒng)
應(yīng)用默認(rèn)只能訪問(wèn)自己的文件和非常少量的系統(tǒng)資源捡鱼。想要獲取更多系統(tǒng)和其他應(yīng)用的資源,需要使用權(quán)限機(jī)制酷愧。
資源/服務(wù)提供者通過(guò)AndroidManifest顯式要求調(diào)用者的權(quán)限; 應(yīng)用在manifest中申請(qǐng)權(quán)限驾诈,系統(tǒng)在安裝或運(yùn)行時(shí)確定授予哪些權(quán)限。
Permission的定義
Android Permission可以分為三種類型伟墙,每種類型permission的定義方式如下:
-
Builtin permission
系統(tǒng)在
/etc/permissions/*.xml
中定義翘鸭。每個(gè)權(quán)限對(duì)應(yīng)一個(gè)GID。// /etc/permissions/platform.xml <permission name="android.permission.INTERNET" > <group gid="inet" /> </permission> <permission name="android.permission.WRITE_MEDIA_STORAGE" > <group gid="media_rw" /> </permission>
系統(tǒng)每授予應(yīng)用一個(gè)內(nèi)置權(quán)限, 就給應(yīng)用添加一個(gè)對(duì)應(yīng)的GID到應(yīng)用的添加組ID groups中
// PermissionsState.java private int grantPermission(BasePermission permission, int userId) { if (hasPermission(permission.getName(), userId)) { return PERMISSION_OPERATION_FAILURE; } final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId)); final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS; ...
-
Normal
Normal permission是系統(tǒng)(android package)戳葵,系統(tǒng)應(yīng)用以及第三方應(yīng)用在自己的AndroidManifest中定義的權(quán)限就乓。例如
READ_CONTACTS
等系統(tǒng)權(quán)限是在framework-res.apk的AndroidManifest中定義。// frameworks/base/core/res/AndroidManifest.xml <permission-group android:name="android.permission-group.CONTACTS" android:icon="@drawable/perm_group_contacts" android:label="@string/permgrouplab_contacts" android:description="@string/permgroupdesc_contacts" android:request="@string/permgrouprequest_contacts" android:priority="100" /> <!-- Allows an application to read the user's contacts data. <p>Protection level: dangerous --> <permission android:name="android.permission.READ_CONTACTS" android:permissionGroup="android.permission-group.CONTACTS" android:label="@string/permlab_readContacts" android:description="@string/permdesc_readContacts" android:protectionLevel="dangerous" />
-
Dynamic
可以動(dòng)態(tài)添加定義的權(quán)限拱烁。參考android developer
Permission 校驗(yàn)
-
Buildtin permission在內(nèi)核函數(shù)中顯式校驗(yàn)生蚁,或者通過(guò)基于UID的權(quán)限機(jī)制進(jìn)行校驗(yàn)。
內(nèi)核中對(duì)INTERNET權(quán)限的校驗(yàn)
// // af_inet.c #ifdef CONFIG_ANDROID_PARANOID_NETWORK #include <linux/android_aid.h> static inline int current_has_network(void) { return in_egroup_p(AID_INET) || capable(CAP_NET_RAW); } static int inet_create(struct net *net, struct socket *sock, int protocol, int kern) { ... if (!current_has_network()) return -EACCES;
基于UID的權(quán)限校驗(yàn)戏自。以sdcard讀寫權(quán)限為例邦投,sdcard目錄權(quán)限設(shè)置為sdcard_rw組可讀寫(每個(gè)進(jìn)程看到的權(quán)限不一樣,這里以shell用戶為例)擅笔。只有獲得了
WRITE_MEDIA_STORAGE
權(quán)限志衣,才能獲得sdcard_rw
GID屯援,才能訪問(wèn)sdcard目錄。adb shell ls -l /sdcard/ total 112 drwxrwx--x 2 root sdcard_rw 4096 2008-12-31 21:31 Alarms drwxrwx--x 3 root sdcard_rw 4096 2008-12-31 21:31 Android drwxrwx--x 2 root sdcard_rw 4096 2008-12-31 21:31 DCIM
-
Normal和dynamic權(quán)限的校驗(yàn)
分兩種情況念脯。對(duì)于Activity狞洋,Service等程序組件的權(quán)限訪問(wèn),由AMS調(diào)用權(quán)限檢查函數(shù)判斷是否具有合法權(quán)限绿店。
對(duì)于對(duì)外提供服務(wù)的系統(tǒng)Service或者應(yīng)用service吉懊,可以在功能函數(shù)中自己調(diào)用權(quán)限檢查函數(shù)檢查調(diào)用者是否具有權(quán)限。
PackageManager.checkPermission() Context.checkPermission()
SELinux
SELinux在8.0及以后為了兼容treble假勿,做了較大的改動(dòng)借嗽,這里僅總結(jié)記錄一下8.0之前官方文檔中所描述的一些概念和原理。
基本概念
-
強(qiáng)制訪問(wèn)控制 MAC
- SELinux是Linux系統(tǒng)上的一個(gè)強(qiáng)制訪問(wèn)控制系統(tǒng)转培,相對(duì)于已經(jīng)熟悉的DAC(自主訪問(wèn)控制)
- 自主訪問(wèn)控制中恶导,每個(gè)資源具有屬主,即資源所有者浸须,屬主可以控制資源的訪問(wèn)權(quán)限甲锡。這通常是粗粒度的并且易于導(dǎo)致錯(cuò)誤的權(quán)限擴(kuò)散
- MAC集中管理資源的訪問(wèn)權(quán)限,不存在DAC的問(wèn)題
- SELinux實(shí)現(xiàn)為L(zhǎng)SM的一部分
-
Enforcement levels
- 工作模式
- Permissive - Only logged
- Enforcing - Enforced and logged
- Policy type
- Unconfined - 非常輕量級(jí)的策略羽戒,限制很少缤沦,適用于開發(fā)階段
- Confined - 定制策略
- 工作模式
-
標(biāo)簽(labels),規(guī)則(rules)和域(domains)
- SELinux中易稠,文件和進(jìn)程等任何資源都有一個(gè)標(biāo)簽缸废,標(biāo)簽和策略一起決定了那些行為是允許的。
- 標(biāo)簽形如
user:rule:type:mls_level
驶社,其中type為主要部分企量。 - 資源對(duì)象被映射為類,對(duì)每個(gè)類的訪問(wèn)由permission表示
- rule形如
allow domain types:classes permissions;
亡电,其中各部分含義:- domain - 進(jìn)程的label
- type - 資源對(duì)象的label
- class - 資源對(duì)象的具體類別
- permission - 執(zhí)行的訪問(wèn)操作
背景和基本原理
- android 4.3開始届巩,SELinux用于加強(qiáng)應(yīng)用沙盒
- SELinux對(duì)所有進(jìn)程執(zhí)行強(qiáng)制訪問(wèn)控制,包括root進(jìn)程
- Enforcing模式下份乒,任何試圖違反SELinux安全策略的行為被記錄在logcat和dmesg中
- 以默認(rèn)拒絕方式工作恕汇,即任何沒有顯示允許的行為都被拒絕
- 兩種工作模式:permissive vs. enforcing
- 支持per-domain permissive模式
- 實(shí)施過(guò)程:
- android 4.3 permissive
- android 4.4 partial enforcing
- android 5.0 full enforcing
關(guān)鍵文件
- SELinux策略文件在
system/sepolicy
目錄。 - 一般不需要直接修改
system/sepolicy
或辖,而是在/device/manufacturer/device-name/sepolicy目錄下定義設(shè)備相關(guān)的策略文件 - 實(shí)現(xiàn)SELinux需要修改或創(chuàng)建的文件:
- 新的策略源文件(*.te) - 定義域及其標(biāo)簽
- 更新BoardConfig.mk - 使編譯系統(tǒng)包含新創(chuàng)建的sepolicy目錄
-
file_contexts
- 定義文件的標(biāo)簽瘾英。必須重新編譯文件系統(tǒng)或者執(zhí)行restorecon
命令使其生效。系統(tǒng)升級(jí)會(huì)自動(dòng)更新系統(tǒng)和用戶分區(qū)颂暇。在init.board.rc文件中添加restorecon_recursive
可以自動(dòng)更新其他分區(qū)缺谴。 -
genfs_contexts
- 為proc, vfat等不支持?jǐn)U展屬性的文件系統(tǒng)設(shè)置文件標(biāo)簽。此配置文件作為內(nèi)核策略的一部分加載耳鸯。但是需要重啟或者卸載并重新裝載才能對(duì)已經(jīng)創(chuàng)建的節(jié)點(diǎn)生效湿蛔。 -
property_contexts
- 設(shè)置Android 系統(tǒng)property的標(biāo)簽膀曾。此文件由init在系統(tǒng)啟動(dòng)以及selinux.reload_policy設(shè)置為1是加載 -
service_contexts
- 設(shè)置Android binder服務(wù)的標(biāo)簽,此文件由servicemanager在系統(tǒng)啟動(dòng)以及selinux.reload_policy設(shè)置為1時(shí)加載 -
seapp_contexts
- 設(shè)置app進(jìn)程和文件的標(biāo)簽阳啥。由zygote進(jìn)程在app啟動(dòng)以及由installd在系統(tǒng)啟動(dòng)時(shí)和selinux.reload_policy設(shè)置為1時(shí)讀取 -
mac_permissions.xml
- 基于簽名和包名為app設(shè)置seinfo妓肢,seapp_contexts
使用seinfo來(lái)為app設(shè)置標(biāo)簽。system_server
在啟動(dòng)時(shí)讀取此文件
- 編譯系統(tǒng)使用
BOARD_SEPOLICY_DIRS
等變量加入新的策略文件
初始化設(shè)置
-
Init 初始化
- init首次運(yùn)行在kernel domain苫纤。即"u:r:kernel:s0"
- 設(shè)置log和audit回調(diào)函數(shù)
- 加載/sepolicy策略文件
- 設(shè)置工作模式:如果系統(tǒng)配置允許Permissive模式,則設(shè)置為內(nèi)核命令行參數(shù)中指定的模式纲缓,否則Enforcing模式
- 根據(jù)
/file_contexts
卷拘,設(shè)置/init
label。根文件系統(tǒng)不支持?jǐn)U展屬性祝高,所以需要運(yùn)行時(shí)設(shè)置 - init重新執(zhí)行自己栗弟,此時(shí)策略中的轉(zhuǎn)移規(guī)則生效,init開始以init domain執(zhí)行工闺。
- 設(shè)置log和audit回調(diào)函數(shù)
- 加載
/file_contexts
和property_contexts
- 根據(jù)加載的
/file_contexts
乍赫,設(shè)置/dev
,/dev/socket
等文件系統(tǒng)和目錄的label陆蟆。 - 由initrc文件控制雷厂,在系統(tǒng)啟動(dòng)的各個(gè)階段,對(duì)需要的文件系統(tǒng)和目錄執(zhí)行restorecon命令叠殷,設(shè)置文件label
- selinux.reload_policy設(shè)置為1時(shí)改鲫,重新加載策略,包括
/sepolicy
,file_contexts
,property_contexts
-
Binder 初始化
-
servicemanager
服務(wù)啟動(dòng)時(shí)林束,從/service_contexts
文件讀取每個(gè)service對(duì)應(yīng)的context - 打開selinux狀態(tài)查詢接口像棘,用于監(jiān)控是否reload policy。
- 設(shè)置log和audit回調(diào)函數(shù)
- 每次收到binder請(qǐng)求壶冒,檢查policy是否reload過(guò)缕题,如果reload過(guò),則重新加載
/service_contexts
-
-
zygote 初始化
- 每次啟動(dòng)App時(shí)胖腾,zygote在子進(jìn)程中通過(guò)native函數(shù)加載
/seapp_contexts
烟零,計(jì)算app進(jìn)程的context - installd每次收到一個(gè)新的請(qǐng)求,檢查
/seapp_contexts
是否需要更新咸作,如需要?jiǎng)t重新加載/seapp_contexts
文件
- 每次啟動(dòng)App時(shí)胖腾,zygote在子進(jìn)程中通過(guò)native函數(shù)加載
-
system_server
initialization- PMS啟動(dòng)時(shí)從
/etc/mac_permissions.xml
讀取每個(gè)包的seinfo信息(如果有的話)瓶摆。
- PMS啟動(dòng)時(shí)從