Android 权限控制代码分析


前在文章介绍过android系统管理层次: ,这里就核心代码分析一下

android系统充分利用了linux的用户权限管理方法,所以如果需要移植到其它系统,这一块也是一个相当不小的工作量。那么android系统到底是如何使用这些的有利因素呢?

首先需要知道linux权限的两个基本知识:

1、 一个用户可以属于多个组.

2、 一个文件只能属于某个组。

这里主要是在AndroidManifest.xml中声明权限,主要是通过在AndroidManifest.xml中显示地声明应用程序需要的权限,防止应用程序错误的使用服务,不恰当访问资源。

Android中每种权限都用一个独立的标签表示.如:

<uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

当在安装(Install) 应用程序时,Android就会给予一个UID。这个UID可连结到该应用程序的 AndroidManifest.xml档案的内容。所User在安装你的应用程序时,在屏幕上的窗口里可以检视这个 AndroidManifest.xml档案的内容。在检视时,用户会看到你对应用程序的目的、权限等说明。当你接受这支程序的意图、权限说明之后,Android就安装它,并给它一个UID。万一在你的应用程序执行期间有越轨(企图做出非权限范围)的行为时,用户将会得到Android的警告讯息。

下面是两个安装程序安装时的界面



android networkSecurityConfig添加域名 android网络权限配置_配置

android networkSecurityConfig添加域名 android网络权限配置_权限_02





这两个应用其实代码是一样的,唯一的不同就是它们的AndroidManifest.xml,图2的AndroidManifest.xml中多了如下内容:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

即需要使用存储设备和录音设备,在安装的时候,就会提示用户它需要的权限。Android里面是怎么去控制的呢?


在安装apk的时候,会解析这个AndroidManifest.xml,把相应的信息保存起来。

代码路径:frameworks\base\core\java\android\content\pm\PackageParser.java

最终调用的是private Package parsePackage(

Resources res, XmlResourceParser parser, int flags, String[] outError)
这个函数,在里面对AndroidManifest.xml进行了解析,其中就有
    private Package parsePackage(
         Resources res, XmlResourceParser parser, int flags, String[] outError)
         throws XmlPullParserException, IOException {       ... 
      String tagName = parser.getName();
       if (tagName.equals("application")) {
        ...
       } else if (tagName.equals("permission-group")) {
           if (parsePermissionGroup(pkg, res, parser, attrs, outError) == null) {
               return null;
           }
       } else if (tagName.equals("permission")) {
           if (parsePermission(pkg, res, parser, attrs, outError) == null) {
               return null;
           }
       } else if (tagName.equals("permission-tree")) {
           if (parsePermissionTree(pkg, res, parser, attrs, outError) == null) {
               return null;
           }
       } else if (tagName.equals("uses-permission")) {
           sa = res.obtainAttributes(attrs,
                   com.android.internal.R.styleable.AndroidManifestUsesPermission);

           // Note: don't allow this value to be a reference to a resource
           // that may change.
           String name = sa.getNonResourceString(
                   com.android.internal.R.styleable.AndroidManifestUsesPermission_name);

           sa.recycle();

           if (name != null && !pkg.requestedPermissions.contains(name)) {
               pkg.requestedPermissions.add(name.intern());
           }

           XmlUtils.skipCurrentTag(parser);
       }   }

这里对它使用的权限进行了解析。

这里保存的都是"android.permission.WRITE_EXTERNAL_STORAGE"这样的字符串,在解析完后,会调用grantPermissionsLP函数获取对应的group_id,

代码路径:frameworks\base\services\java\com\android\server\PackageManagerService.java


private void grantPermissionsLP(PackageParser.Package pkg, boolean replace) {
...
     if (allowed) {
         if (!gp.grantedPermissions.contains(perm)) {
             changedPermission = true;
             gp.grantedPermissions.add(perm);
             gp.gids = appendInts(gp.gids, bp.gids);
         } else if (!ps.haveGids) {
             gp.gids = appendInts(gp.gids, bp.gids);
         }
     } else {
         Slog.w(TAG, "Not granting permission " + perm
                 + " to package " + pkg.packageName
                 + " because it was previously installed without");
     }        
...
 }

这里把相应的组都保存到了gids中。

当应用程序启动的过程中会调用

private final void startProcessLocked(ProcessRecord app,

            String hostingType, String hostingNameStr)

代码路径:frameworks\base\services\java\com\android\server\am\ActivityManagerService.java

private final void startProcessLocked(ProcessRecord app,
         String hostingType, String hostingNameStr) {
...
     try {
         int uid = app.info.uid;
         int[] gids = null;
         try {
            gids = mContext.getPackageManager().getPackageGids(
                     app.info.packageName);
         } catch (PackageManager.NameNotFoundException e) {
             Slog.w(TAG, "Unable to retrieve gids", e);
         }      
...
    int pid = Process.start("android.app.ActivityThread",
           mSimpleProcessManagement ? app.processName : null, uid, uid,
           gids, debugFlags, null);        
 }




这里就是获取前面保存的gids,再后面调用创建了一个新的进程,这里传的参数就有gids

创建新进程利用jni最终调用 forkAndSpecializeCommon 函数 (路径:\dalvik\vm\native\dalvik_system_Zygote.c)

static pid_t forkAndSpecializeCommon(const u4* args, bool isSystemServer)
 {
     pid_t pid;
     uid_t uid = (uid_t) args[0];
     gid_t gid = (gid_t) args[1];
     ArrayObject* gids = (ArrayObject *)args[2];
     u4 debugFlags = args[3];
     ArrayObject *rlimits = (ArrayObject *)args[4];
     int64_t permittedCapabilities, effectiveCapabilities;
...
     pid = fork();
     if (pid == 0) {
         int err;
         /* The child process */
     
        err = setgroupsIntarray(gids);

         if (err < 0) {
             LOGE("cannot setgroups(): %s", strerror(errno));
             dvmAbort();
         }
        err = setrlimitsFromArray(rlimits);

         if (err < 0) {
             LOGE("cannot setrlimit(): %s", strerror(errno));
             dvmAbort();
         }
        err = setgid(gid);
         if (err < 0) {
             LOGE("cannot setgid(%d): %s", gid, strerror(errno));
             dvmAbort();
         }

        err = setuid(uid);
         if (err < 0) {
             LOGE("cannot setuid(%d): %s", uid, strerror(errno));
             dvmAbort();
         }        
         ...
 }



我们看到在子进程里调用setgroupsIntarray设置该进程所属的组,这样它就拥有了该组的权限。也通过setgid及setuid决定了应用程序的uid及gid值。

举个例子:

我们新建一Android工程,读取其应用的uid/gid值,贴入如下代码:

try{
        java.lang.Process process = Runtime.getRuntime().exec("id");
        InputStream input = process.getInputStream();
        byte[] bytes = new byte[1204];
        int len;
        while((len = (input.read(bytes))) > 0)
        {
System.out.print(new String(bytes, 0, len)); 
        }
        input.close();

这里运行运行linux的id命令的代码,id命令的功能是输出当前用户的uid,主要的group id和所在的group

在其AndroidManifest.xml添加如下权限:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

运行后看log里面有:

android networkSecurityConfig添加域名 android网络权限配置_详解_03

这里面就有groups=1015


再看下android_filesystem_config.h里面

代码路径:

system\core\include\private

有如下代码

再看下platform.xml

#define AID_ROOT             0  /* traditional unix root user */

 #define AID_SYSTEM        1000  /* system server */

 #define AID_RADIO         1001  /* telephony subsystem, RIL */
 #define AID_BLUETOOTH     1002  /* bluetooth subsystem */
 #define AID_GRAPHICS      1003  /* graphics devices */
 
 
...
#define AID_WIFI          1010  /* wifi subsystem */
 #define AID_ADB           1011  /* android debug bridge (adbd) */
 #define AID_INSTALL       1012  /* group for installing packages */
 #define AID_MEDIA         1013  /* mediaserver process */
 #define AID_DHCP          1014  /* dhcp client */
 #define AID_SDCARD_RW     1015  /* external storage write access */

路径:frameworks\base\data\etc

有如下配置

<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
        <group gid="sdcard_rw" />
</permission>

这样就把android.permission.WRITE_EXTERNAL_STORAGE 、"sdcard_rw"、以及 1015组关联起来了


我们再看下当sd卡挂载上去后目录的权限:

ls -l
drwxr-xr-x root     system            1970-01-01 08:00 obb
drwxr-xr-x root     system            1970-01-01 08:00 asec
drwx------ root     root              1970-01-01 08:00 secure
d---rwxr-x system   sdcard_rw          2012-03-29 17:11 sdcard

可以看到对于sd卡,组用户具有读写权限,而我们的应用也加入了这个组,这样它就可以操作sdcard了。


当一个应用需要操作sdcard而没有在AndroidManifest.xml添加相应的权限时,就不能成功完成。

以下是同一个程序,一个有在AndroidManifest.xml添加WRITE_EXTERNAL_STORAGE权限,一个没有,它们的对比,可以看到由于没有权限程序运行异常了。

android networkSecurityConfig添加域名 android网络权限配置_配置_04


对于管理权限的xml文件补充说明一下:

源代码中权限文件位于: frameworks\base\data\etc 下面,镜像生成在 system\etc\permissions\platform.xml中

文件权限读取点:

readPermissions @ PackageManagerService.java
    void readPermissions() {
        // Read permissions from .../etc/permission directory.
         File libraryDir = new File(Environment.getRootDirectory(), "etc/permissions");
         if (!libraryDir.exists() || !libraryDir.isDirectory()) {
             Slog.w(TAG, "No directory " + libraryDir + ", skipping");
             return;
         }
         if (!libraryDir.canRead()) {
             Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
             return;
         }

         // Iterate over the files in the directory and scan .xml files
         for (File f : libraryDir.listFiles()) {
            // We'll read platform.xml last
             if (f.getPath().endsWith("etc/permissions/platform.xml")) {
                 continue;
             }


             if (!f.getPath().endsWith(".xml")) {
                 Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
                 continue;
             }
             if (!f.canRead()) {
                 Slog.w(TAG, "Permissions library file " + f + " cannot be read");
                 continue;
             }

             readPermissionsFromXml(f);
         }

        // Read permissions from .../etc/permissions/platform.xml last so it will take precedence
         final File permFile = new File(Environment.getRootDirectory(),
                 "etc/permissions/platform.xml");
         readPermissionsFromXml(permFile);
     }

platform.xml 的重要性在于:

<!-- This file is used to define the mappings between lower-level system
      user and group IDs and the higher-level permission names managed
      by the platform.


      Be VERY careful when editing this file!  Mistakes made here can open
      big security holes.
 -->


 

Android的系统权限不是由用户控制,而是由开发者根据开发的需要控制相关权限的开放与否,权限控制主要放置在AndroidManifest.xml文件中。

将如下的权限控制属性写入AndroidManifest.xml文件就可以获取相应的系统权限。如果在开发中遇到一些调试的问题很可以就是权限的原因。


<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES" ></uses-permission>
 允许读写访问"properties"表在checkin数据库中,改值可以修改上传
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" ></uses-permission>
 允许一个程序访问CellID或WiFi热点来获取粗略的位置
 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" ></uses-permission>
 允许一个程序访问精良位置(如GPS)
 <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ></uses-permission>
 允许应用程序访问额外的位置提供命令
 <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" ></uses-permission>
 允许程序创建模拟位置提供用于测试
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" ></uses-permission>
 允许程序访问有关GSM网络信息
 <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" ></uses-permission>
 允许程序使用SurfaceFlinger底层特性
 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" ></uses-permission>
 允许程序访问Wi-Fi网络状态信息
 <uses-permission android:name="android.permission.ADD_SYSTEM_SERVICE" ></uses-permission>
 允许程序发布系统级服务
 <uses-permission android:name="android.permission.BATTERY_STATS" ></uses-permission>
 允许程序更新手机电池统计信息
 <uses-permission android:name="android.permission.BLUETOOTH" ></uses-permission>
 允许程序连接到已配对的蓝牙设备
 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" ></uses-permission>
 允许程序发现和配对蓝牙设备
 <uses-permission android:name="android.permission.BRICK" ></uses-permission>
 请求能够禁用设备
 <uses-permission android:name="android.permission.BROADCAST_PACKAGE_REMOVED" ></uses-permission>
 允许程序广播一个提示消息在一个应用程序包已经移除后
 <uses-permission android:name="android.permission.BROADCAST_STICKY" ></uses-permission>
 允许一个程序广播常用intents
 <uses-permission android:name="android.permission.CALL_PHONE" ></uses-permission>
 允许一个程序初始化一个电话拨号不需通过拨号用户界面需要用户确认
 <uses-permission android:name="android.permission.CALL_PRIVILEGED" ></uses-permission>
 允许一个程序拨打任何号码,包含紧急号码无需通过拨号用户界面需要用户确认
 <uses-permission android:name="android.permission.CAMERA" ></uses-permission>
 请求访问使用照相设备
 <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" ></uses-permission>
 允许一个程序是否改变一个组件或其他的启用或禁用
 <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" ></uses-permission>
 允许一个程序修改当前设置,如本地化
 <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" ></uses-permission>
 允许程序改变网络连接状态
 <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" ></uses-permission>
 允许程序改变Wi-Fi连接状态
 <uses-permission android:name="android.permission.CLEAR_APP_CACHE" ></uses-permission>
 允许一个程序清楚缓存从所有安装的程序在设备中
 <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" ></uses-permission>
 允许一个程序清除用户设置
 <uses-permission android:name="android.permission.CONTROL_LOCATION_UPDATES" ></uses-permission>
 允许启用禁止位置更新提示从无线模块
 <uses-permission android:name="android.permission.DELETE_CACHE_FILES" ></uses-permission>
 允许程序删除缓存文件
 <uses-permission android:name="android.permission.DELETE_PACKAGES" ></uses-permission>
 允许一个程序删除包
 <uses-permission android:name="android.permission.DEVICE_POWER" ></uses-permission>
 允许访问底层电源管理
 <uses-permission android:name="android.permission.DIAGNOSTIC" ></uses-permission>
 允许程序RW诊断资源
 <uses-permission android:name="android.permission.DISABLE_KEYGUARD" ></uses-permission>
 允许程序禁用键盘锁
 <uses-permission android:name="android.permission.DUMP" ></uses-permission>
 允许程序返回状态抓取信息从系统服务
 android.permission.EXPAND_STATUS_BAR
 允许一个程序扩展收缩状态栏
 android.permission.FACTORY_TEST
 作为一个工厂测试程序,运行在root用户
 android.permission.FLASHLIGHT
 访问闪光灯
 android.permission.FORCE_BACK
 允许程序强行一个后退操作是否在顶层activities
 android.permission.FOTA_UPDATE android
  一个预留权限
 android.permission.GET_ACCOUNTS
 访问一个帐户列表在Accounts Service中
 android.permission.GET_PACKAGE_SIZE
 允许一个程序获取任何package占用空间容量
 android.permission.GET_TASKS
 允许一个程序获取信息有关当前或最近运行的任务,一个缩略的任务状态,是否活动等等
 android.permission.HARDWARE_TEST
 允许访问硬件
 android.permission.INJECT_EVENTS
 允许一个程序截获用户事件如按键、触摸、轨迹球等等到一个时间流
 android.permission.INSTALL_PACKAGES
 允许一个程序安装packages
 android.permission.INTERNAL_SYSTEM_WINDOW
 允许打开窗口使用系统用户界面
 android.permission.INTERNET
 允许程序打开网络套接字
 android.permission.MANAGE_APP_TOKENS
 允许程序管理(创建、催后、z- order默认向z轴推移)程序引用在窗口管理器中
 android.permission.MASTER_CLEAR
 恢复出厂设置权限,清除一切用户数据
 android.permission.MODIFY_AUDIO_SETTINGS
 允许程序修改全局音频设置
 android.permission.MODIFY_PHONE_STATE
 允许修改话机状态,如电源,人机接口等
 android.permission.MOUNT_UNMOUNT_FILESYSTEMS
 允许挂载和反挂载文件系统可移动存储
 android.permission.PERSISTENT_ACTIVITY
 允许一个程序设置他的activities显示
 android.permission.PROCESS_OUTGOING_CALLS
 允许程序监视、修改有关播出电话
 android.permission.READ_CALENDAR
 允许程序读取用户日历数据
 android.permission.READ_CONTACTS
 允许程序读取用户联系人数据
 android.permission.READ_FRAME_BUFFER
 允许程序屏幕波或和更多常规的访问帧缓冲数据
 android.permission.READ_INPUT_STATE
 允许程序读取底层系统日志文件
 android.permission.READ_OWNER_DATA
 允许程序读取所有者数据
 android.permission.READ_SMS
 允许程序读取短信息
 android.permission.READ_SYNC_SETTINGS
 允许程序读取同步设置
 android.permission.READ_SYNC_STATS
 允许程序读取同步状态
 android.permission.REBOOT
 请求能够重新启动设备
 android.permission.RECEIVE_BOOT_COMPLETED
 允许一个程序接收到 ACTION_BOOT_COMPLETED广播在系统完成启动
 android.permission.RECEIVE_MMS
 允许一个程序监控将收到MMS彩信,记录或处理
 android.permission.RECEIVE_SMS
 允许程序监控一个将收到短信息,记录或处理
 android.permission.RECEIVE_WAP_PUSH
 允许程序监控将收到WAP PUSH信息
 android.permission.RECORD_AUDIO
 允许程序录制音频
 android.permission.REORDER_TASKS
 允许程序改变Z轴排列任务
 android.permission.RESTART_PACKAGES
 允许程序重新启动其他程序
 android.permission.SEND_SMS
 允许程序发送SMS短信
 android.permission.SET_ACTIVITY_WATCHER
 允许程序监控或控制activities已经启动全局系统中
 android.permission.SET_ALWAYS_FINISH
 允许程序控制是否活动间接完成在处于后台时
 android.permission.SET_ANIMATION_SCALE
 修改全局信息比例
 android.permission.SET_DEBUG_APP
 配置一个程序用于调试
 android.permission.SET_ORIENTATION
 允许底层访问设置屏幕方向和实际旋转
 android.permission.SET_PREFERRED_APPLICATIONS
 允许一个程序修改列表参数PackageManager.addPackageToPreferred() 和PackageManager.removePackageFromPreferred()方法
 android.permission.SET_PROCESS_FOREGROUND
 允许程序当前运行程序强行到前台
 android.permission.SET_PROCESS_LIMIT
 允许设置最大的运行进程数量
 android.permission.SET_TIME_ZONE
 允许程序设置时间区域
 android.permission.SET_WALLPAPER
 允许程序设置壁纸
 android.permission.SET_WALLPAPER_HINTS
 允许程序设置壁纸hits
 android.permission.SIGNAL_PERSISTENT_PROCESSES
 允许程序请求发送信号到所有显示的进程中
 android.permission.STATUS_BAR
 允许程序打开、关闭或禁用状态栏及图标
 android.permission.SUBSCRIBED_FEEDS_READ
 允许一个程序访问订阅RSS Feed内容提供
 android.permission.SUBSCRIBED_FEEDS_WRITE
 系统暂时保留改设置
 android.permission.SYSTEM_ALERT_WINDOW
 允许一个程序打开窗口使用 TYPE_SYSTEM_ALERT,显示在其他所有程序的顶层
 android.permission.VIBRATE
 允许访问振动设备
 android.permission.WAKE_LOCK
 允许使用PowerManager的 WakeLocks保持进程在休眠时从屏幕消失
 android.permission.WRITE_APN_SETTINGS
 允许程序写入API设置
 android.permission.WRITE_CALENDAR
 允许一个程序写入但不读取用户日历数据
 android.permission.WRITE_CONTACTS
 允许程序写入但不读取用户联系人数据
 android.permission.WRITE_GSERVICES
 允许程序修改Google服务地图
 android.permission.WRITE_OWNER_DATA
 允许一个程序写入但不读取所有者数据
 android.permission.WRITE_SETTINGS
 允许程序读取或写入系统设置
 android.permission.WRITE_SMS
 允许程序写短信
 android.permission.WRITE_SYNC_SETTINGS
 允许程序写入同步设置