Android系統爲每一個應用程序提供了一個安全的運行環境,不一樣程序間相互隔離,應用程序的數據等私有資源,外界沒法訪問。這個安全的運行環境由Android的權限系統(可稱爲沙箱系統)來提供。本文簡單記錄Android權限系統的基本組成模塊和實現機制中的關鍵代碼。html
能夠將Android權限系統分爲4個模塊:java
該權限系統基於進程的UID來控制進程對文件等資源的訪問權限。簡單來講,系統中每一個進程有一個UID和一個或多個GID屬性;每一個文件具備一個UID和一個GID屬性,而且有三組權限位,分別表示和本身相同的UID進程、相同的GID進程,以及其餘不相關進程對文件的讀、寫和執行訪問權限。內核以UID做爲權限管理的基本粒度單位。關於進程對文件的具體訪問權限規則,能夠查閱UNIX/Linux手冊或一些書籍。(《UNIX環境高級編程》4.5節)。linux
在典型的UNIX/Linux多用戶系統中,系統爲每一個登陸用戶分配一個UID,因此權限控制的粒度是單個用戶。Android系統沒有傳統意義登陸用戶的概念,而是將UID分配給每一個應用程序,因此權限管理的粒度是單個應用程序。具體運行過程以下。android
應用程序安裝時,系統爲應用程序分配一個UID。PackageManagerService默認爲每一個應用程序分配一個新的UID和GID。若是應用程序申請了某些特殊的運行時權限,則爲其分配(實際是將其加入)一組額外的GID Group。同一個開發者開發的兩個應用(簽名相同),能夠共享UID和GID,只須要在AndroidManifest中聲明一樣的android:sharedUserId
屬性。應用程序的UID/GID和其餘屬性一塊兒寫入packages.list
和packages.xml
文件中。shell
// 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);
啓動應用程序進程時,ActivityManagerService向PackageManagerService查詢應用程序的UID/GID等信息,並將這些信息做爲參數傳遞給Zygote進程。Zygote進程爲應用程序fork出子進程,並按照參數設置子進程的UID/GID,這樣應用程序進程就以本身所屬UID的身份運行了。編程
// AMS 傳遞參數給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 根據參數設置進程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); ... }
設置應用程序的文件權限安全
a. 設置APK文件權限爲全部用戶可讀,這樣系統或者別的應用程序才能夠訪問應用程序的代碼。b. 系統爲應用程序建立的數據目錄,設置爲其餘用戶可執行(搜索)。若是不設置爲可執行,則用戶的任何數據文件不能共享給其餘應用程序。app
// 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. 應用經過Context.openFileOutput等Android接口建立的數據文件默認爲其餘用戶不可讀寫。若是用戶指定了MODE_WORLD_READABLE
或者 MODE_WORLD_WRITEABLE
,則設置其餘用戶可讀或者可寫。新版本這兩個mode已經廢除。dom
// 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. 應用經過File.createNewFile()等Java接口建立的數據文件默認只有本身可讀寫。這種方式建立的文件權限與當前進程的umask設置相關。Android系統的init進程在建立系統服務(包括zygote)時,設置了umask爲077,應用程序繼承了zygote的umask,因此也是077,表示只保留相同UID的訪問權限,僅容許相同UID的進程(也就是本身)訪問。socket
``C++ // system/core/init/service.cpp Result<Success> Service::Start() { ... pid = fork(); if (pid == 0) { umask(077); ... }
基於UID的權限管理機制中,有一個特殊的UID 0,即所謂root用戶,具備超級權限,不授權限機制的約束。而有些系統資源和能力,只有root用戶纔可使用。因此係統中不少核心服務,如adbd,zygote以root身份運行,而這些系統服務又頻繁與應用程序交互,這些服務中存在的安全漏洞,很容易被惡意應用程序利用,進行提權操做,突破系統權限管控。因此Android進一步使用capability機制來限制UID 0的權限。
Capability機制將只有root用戶能夠訪問的權限進一步細分爲一組能力。每一個線程有四組比特位來表示自身所擁有的權限:
$ adb shell cat /proc/<pid>/status CapInh: 0000000000000000 CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 0000000000000000
其中CapInh表示執行execve會保留的權限;CapEff表示線程當前權限;CapPrm表示CapInh和CapEff的最大限制;CapBnd表示線程可以得到的最大權限。
Android系統中,adbd和zygote是爲應用建立進程的服務。zygote服務在建立了子進程,將子進程返回給系統前,將CapBnd清除,這樣即便子進程利用系統漏洞獲取了root uid,仍然沒有任何超級權限。adbd則在執行完必須的特權任務後,清除CapBnd,將本身權限下降。
// Zygote 設置子進程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); } ...
應用默認只能訪問本身的文件和很是少許的系統資源。想要獲取更多系統和其餘應用的資源,須要使用權限機制。
資源/服務提供者經過AndroidManifest顯式要求調用者的權限; 應用在manifest中申請權限,系統在安裝或運行時肯定授予哪些權限。
Android Permission能夠分爲三種類型,每種類型permission的定義方式以下:
Builtin permission
系統在/etc/permissions/*.xml
中定義。每一個權限對應一個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>
系統每授予應用一個內置權限, 就給應用添加一個對應的GID到應用的添加組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是系統(android package),系統應用以及第三方應用在本身的AndroidManifest中定義的權限。例如READ_CONTACTS
等系統權限是在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
能夠動態添加定義的權限。參考android developer
Buildtin permission在內核函數中顯式校驗,或者經過基於UID的權限機制進行校驗。
內核中對INTERNET權限的校驗
// // 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的權限校驗。以sdcard讀寫權限爲例,sdcard目錄權限設置爲sdcard_rw組可讀寫(每一個進程看到的權限不同,這裏以shell用戶爲例)。只有得到了WRITE_MEDIA_STORAGE
權限,才能得到sdcard_rw
GID,才能訪問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權限的校驗
分兩種狀況。對於Activity,Service等程序組件的權限訪問,由AMS調用權限檢查函數判斷是否具備合法權限。
對於對外提供服務的系統Service或者應用service,能夠在功能函數中本身調用權限檢查函數檢查調用者是否具備權限。
PackageManager.checkPermission() Context.checkPermission()
SELinux在8.0及之後爲了兼容treble,作了較大的改動,這裏僅總結記錄一下8.0以前官方文檔中所描述的一些概念和原理。
強制訪問控制 MAC
Enforcement levels
標籤(labels),規則(rules)和域(domains)
user:rule:type:mls_level
,其中type爲主要部分。allow domain types:classes permissions;
,其中各部分含義:
system/sepolicy
目錄。system/sepolicy
,而是在/device/manufacturer/device-name/sepolicy目錄下定義設備相關的策略文件file_contexts
- 定義文件的標籤。必須從新編譯文件系統或者執行restorecon
命令使其生效。系統升級會自動更新系統和用戶分區。在init.board.rc文件中添加restorecon_recursive
能夠自動更新其餘分區。genfs_contexts
- 爲proc, vfat等不支持擴展屬性的文件系統設置文件標籤。此配置文件做爲內核策略的一部分加載。可是須要重啓或者卸載並從新裝載才能對已經建立的節點生效。property_contexts
- 設置Android 系統property的標籤。此文件由init在系統啓動以及selinux.reload_policy設置爲1是加載service_contexts
- 設置Android binder服務的標籤,此文件由servicemanager在系統啓動以及selinux.reload_policy設置爲1時加載seapp_contexts
- 設置app進程和文件的標籤。由zygote進程在app啓動以及由installd在系統啓動時和selinux.reload_policy設置爲1時讀取mac_permissions.xml
- 基於簽名和包名爲app設置seinfo,seapp_contexts
使用seinfo來爲app設置標籤。system_server
在啓動時讀取此文件BOARD_SEPOLICY_DIRS
等變量加入新的策略文件Init 初始化
/file_contexts
,設置/init
label。根文件系統不支持擴展屬性,因此須要運行時設置/file_contexts
和property_contexts
/file_contexts
,設置/dev
,/dev/socket
等文件系統和目錄的label。/sepolicy
,file_contexts
,property_contexts
Binder 初始化
servicemanager
服務啓動時,從/service_contexts
文件讀取每一個service對應的context/service_contexts
zygote 初始化
/seapp_contexts
,計算app進程的context/seapp_contexts
是否須要更新,如須要則從新加載/seapp_contexts
文件system_server
initialization
/etc/mac_permissions.xml
讀取每一個包的seinfo信息(若是有的話)。