Android權限系統


Android系統為每個應用程序提供了一個安全的運行環境,不同程序間相互隔離,應用程序的數據等私有資源,外界無法訪問。這個安全的運行環境由Android的權限系統(可稱為沙箱系統)來提供。本文簡單記錄Android權限系統的基本組成模塊和實現機制中的關鍵代碼。

主要模塊

可以將Android權限系統分為4個模塊:

  1. 基於用戶ID的權限系統
  2. Capability權限系統
  3. Android Permission系統
  4. SELinux權限系統

基於用戶ID的權限系統

該權限系統基於進程的UID來控制進程對文件等資源的訪問權限。簡單來說,系統中每個進程有一個UID和一個或多個GID屬性;每個文件具有一個UID和一個GID屬性,並且有三組權限位,分別表示和自己相同的UID進程、相同的GID進程,以及其他不相關進程對文件的讀、寫和執行訪問權限。內核以UID作為權限管理的基本粒度單位。關於進程對文件的具體訪問權限規則,可以查閱UNIX/Linux手冊或一些書籍。(《UNIX環境高級編程》4.5節)。

在典型的UNIX/Linux多用戶系統中,系統為每個登錄用戶分配一個UID,所以權限控制的粒度是單個用戶。Android系統沒有傳統意義登錄用戶的概念,而是將UID分配給每個應用程序,所以權限管理的粒度是單個應用程序。具體運行過程如下。

  1. 應用程序安裝時,系統為應用程序分配一個UID。PackageManagerService默認為每個應用程序分配一個新的UID和GID。如果應用程序申請了某些特殊的運行時權限,則為其分配(實際是將其加入)一組額外的GID Group。同一個開發者開發的兩個應用(簽名相同),可以共享UID和GID,只需要在AndroidManifest中聲明同樣的android:sharedUserId屬性。應用程序的UID/GID和其他屬性一起寫入packages.listpackages.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);
    
  2. 啟動應用程序進程時,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);
         ...
       }
    
  3. 設置應用程序的文件權限

    1. 設置APK文件權限為所有用戶可讀,這樣系統或者別的應用程序才可以訪問應用程序的代碼。

    2. 系統為應用程序創建的數據目錄,設置為其他用戶可執行(搜索)。如果不設置為可執行,則用戶的任何數據文件不能共享給其他應用程序。

       // 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);
      
    3. 應用通過Context.openFileOutput等Android接口創建的數據文件默認為其他用戶不可讀寫。如果用戶指定了MODE_WORLD_READABLE或者 MODE_WORLD_WRITEABLE,則設置其他用戶可讀或者可寫。新版本這兩個mode已經廢除。

        // 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);
       }
      
    4. 應用通過File.createNewFile()等Java接口創建的數據文件默認只有自己可讀寫。這種方式創建的文件權限與當前進程的umask設置相關。Android系統的init進程在創建系統服務(包括zygote)時,設置了umask為077,應用程序繼承了zygote的umask,所以也是077,表示只保留相同UID的訪問權限,僅允許相同UID的進程(也就是自己)訪問。

       ``C++
       // system/core/init/service.cpp
       Result<Success> Service::Start() {
       ...
       pid = fork();
       if (pid == 0) {
           umask(077);
           ...
       }
      

Capability 機制

基於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);
    }
    ...

Android Permission系統

應用默認只能訪問自己的文件和非常少量的系統資源。想要獲取更多系統和其他應用的資源,需要使用權限機制。

資源/服務提供者通過AndroidManifest顯式要求調用者的權限; 應用在manifest中申請權限,系統在安裝或運行時確定授予哪些權限。

Permission的定義

Android Permission可以分為三種類型,每種類型permission的定義方式如下:

  1. 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;
         ...
    
  2. 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" />
    
  3. Dynamic

    可以動態添加定義的權限。參考android developer

Permission 校驗

  1. 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
    
  2. Normal和dynamic權限的校驗

    分兩種情況。對於Activity,Service等程序組件的權限訪問,由AMS調用權限檢查函數判斷是否具有合法權限。

    對於對外提供服務的系統Service或者應用service,可以在功能函數中自己調用權限檢查函數檢查調用者是否具有權限。

    PackageManager.checkPermission()
    Context.checkPermission()
    

SELinux

SELinux在8.0及以后為了兼容treble,做了較大的改動,這里僅總結記錄一下8.0之前官方文檔中所描述的一些概念和原理。

基本概念

  1. 強制訪問控制 MAC

    • SELinux是Linux系統上的一個強制訪問控制系統,相對於已經熟悉的DAC(自主訪問控制)
    • 自主訪問控制中,每個資源具有屬主,即資源所有者,屬主可以控制資源的訪問權限。這通常是粗粒度的並且易於導致錯誤的權限擴散
    • MAC集中管理資源的訪問權限,不存在DAC的問題
    • SELinux實現為LSM的一部分
  2. Enforcement levels

    • 工作模式
      • Permissive - Only logged
      • Enforcing - Enforced and logged
    • Policy type
      • Unconfined - 非常輕量級的策略,限制很少,適用於開發階段
      • Confined - 定制策略
  3. 標簽(labels),規則(rules)和域(domains)

    • SELinux中,文件和進程等任何資源都有一個標簽,標簽和策略一起決定了那些行為是允許的。
    • 標簽形如 user:rule:type:mls_level,其中type為主要部分。
    • 資源對象被映射為類,對每個類的訪問由permission表示
    • rule形如allow domain types:classes permissions;,其中各部分含義:
      • domain - 進程的label
      • type - 資源對象的label
      • class - 資源對象的具體類別
      • permission - 執行的訪問操作

背景和基本原理

  • android 4.3開始,SELinux用於加強應用沙盒
  • SELinux對所有進程執行強制訪問控制,包括root進程
  • Enforcing模式下,任何試圖違反SELinux安全策略的行為被記錄在logcat和dmesg中
  • 以默認拒絕方式工作,即任何沒有顯示允許的行為都被拒絕
  • 兩種工作模式:permissive vs. enforcing
  • 支持per-domain permissive模式
  • 實施過程:
    • android 4.3 permissive
    • android 4.4 partial enforcing
    • android 5.0 full enforcing

關鍵文件

  • SELinux策略文件在system/sepolicy目錄。
  • 一般不需要直接修改system/sepolicy,而是在/device/manufacturer/device-name/sepolicy目錄下定義設備相關的策略文件
  • 實現SELinux需要修改或創建的文件:
    • 新的策略源文件(*.te) - 定義域及其標簽
    • 更新BoardConfig.mk - 使編譯系統包含新創建的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等變量加入新的策略文件

初始化設置

  1. Init 初始化

    • init首次運行在kernel domain。即"u:r:kernel:s0"
    • 設置log和audit回調函數
    • 加載/sepolicy策略文件
    • 設置工作模式:如果系統配置允許Permissive模式,則設置為內核命令行參數中指定的模式,否則Enforcing模式
    • 根據/file_contexts,設置/initlabel。根文件系統不支持擴展屬性,所以需要運行時設置
    • init重新執行自己,此時策略中的轉移規則生效,init開始以init domain執行。
    • 設置log和audit回調函數
    • 加載/file_contextsproperty_contexts
    • 根據加載的/file_contexts,設置/dev/dev/socket等文件系統和目錄的label。
    • 由initrc文件控制,在系統啟動的各個階段,對需要的文件系統和目錄執行restorecon命令,設置文件label
    • selinux.reload_policy設置為1時,重新加載策略,包括/sepolicy,file_contexts,property_contexts
  2. Binder 初始化

    • servicemanager服務啟動時,從/service_contexts文件讀取每個service對應的context
    • 打開selinux狀態查詢接口,用於監控是否reload policy。
    • 設置log和audit回調函數
    • 每次收到binder請求,檢查policy是否reload過,如果reload過,則重新加載/service_contexts
  3. zygote 初始化

    • 每次啟動App時,zygote在子進程中通過native函數加載/seapp_contexts,計算app進程的context
    • installd每次收到一個新的請求,檢查/seapp_contexts是否需要更新,如需要則重新加載/seapp_contexts文件
  4. system_server initialization

    • PMS啟動時從/etc/mac_permissions.xml讀取每個包的seinfo信息(如果有的話)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM