Android权限体系介绍

前言

本文主要针对一下几个问题,通过查询资料,和一些自己的思考,梳理的一篇记录

1、什么是应用沙盒? 为什么沙盒可以隔离保护app的数据

2、Android中 权限(网络权限、读写存储权限) 控制的基础是什么?

3、看不见的权限管理(selinux)

一、Linux的用户和用户组

Linux有用户和用户组的概念。在Linux系统中,每次登录系统都必须以一个用户的身份登录,并且登录后的权限也会根据用户身份来确定。 每一个进程在执行时,也会有其用户,该用户也和进程所能控制的资源有关。Linux系统下的每一个目录、文件,都会有其属于的用户和用户组,我们称其为属主和属组。

在Linux系统中,每个用户都有自己的用户ID,称为UID,每个用户组也有自己的用户组ID,称为GID,UID和GID在Linux系统中是不可重复的。Linux系统就是通过UID和GID来对用户和组进行管理的,而对于管理员来说,往往会设置用户名和组名,这样使得用户和用户组的使用管理更人性化。

在Linux系统中,一共有三种类型的用户组。

  • root用户
    root用户时UID和GID都等于0的用户,是Linux系统中的“上帝”,拥有最大的权限。比如:无视Linux对权限的设置而强行读、写、执行文件,切换其他用户登录不需要密码,可以强行切换到已经所用的用户,只有root可以为普通用户修改密码等等。
  • 系统用户
    系统用户通常用于运行服务,但是此用户无home目录,也不能用于登录系统。例如,在yum安装apache、nginx等服务后,就会自动创建apache和nginx的用户和同名用户组。
  • 普通用户
    普通用户只能由root用户创建,该用户拥有home目录,并且可以登录,该用户的权限由root分配。普通用户拥有指定的shell环境。

ls -ls 可以查看某个文件的读写执行权限,共分为三组权限:owner的权限、Group的权限、和其他用户的权限。如下图所示:

权限_ls.png

二、Android 沙盒机制

android集成了Linux用户和组的概念。Linux中通过用户和用户组将文件的权限进行了隔离。Android中为每一个应用赋予了一个用户(owner)和用户组(group),以实现应用各自的文件彼此隔离的目录。这就是应用的沙箱隔离机制。

android 应用程序是沙箱隔离的,每个应用都有一个只有自己具有读写权限的专用数据目录,应用只能访问自己的文件和一些设备上全局可访问的资源。

沙盒_new_1.png

apk安装之后,会被赋予一个uid和gid。uid和gid 通常相同。

通过/data/system/packages.list 查看对应的包名,可以确定apk分配的uid

adb shell cat /data/system/packages.list | grep zappstore                                                                           
com.zuoyebang.iot.watch.zappstore 10045 0 /data/user/0/com.zuoyebang.iot.watch.zappstore platform:targetSdkVersion=27 3003,1007,3006

zappstore的uid为10045,10045-10000=45,uid的字符串形式为u0_a45

查看/data/data目录的zappstore的沙盒文件的访问权限,可以发现

com.zuoyebang.iot.watch.zappstore 目录所属的用户为u0_a45,用户组为u0_a45, owner用户u0_a45 对此目录具有读写执行权限,用户组中其他用户没有任何权限,用户组之外的其他用户也没有任何权限。

sl8541e_1h10_go:/data/data # ls -ls | grep  zappstore                               
3 drwx------ 4 u0_a45    u0_a45    3488 2023-03-27 00:00 com.zuoyebang.iot.watch.zappstore

因此com.zuoyebang.iot.watch.zappstore沙盒目录,只有zappstore的进程才有访问权限,其他的app 无权此沙盒目录的文件。

除了沙箱目录,应用进程相关虚拟文件,也被限制为仅自己的uid可访问

user_id.png

共享UID

两个应用的具有相同的签名文件,并且指令了shareUid为同一个uid,则这两个应用可以方便的访问彼此的私有目录。

沙盒_new_2.png

比较常见的例子是:
Android中所有的系统应用都指定了shareUid 为android.uid.system

android:sharedUserId="android.uid.system"

系统应用apk 分配的uid都是1000,用户名都是system

应用沙盒的所属的owner和group 都是system,因此系统应用之间可以互相访问彼此的应用沙盒。

这就是ShareUid机制

值得注意的是:Android 不支持将一个已安装的应用,从非共享 UID 切换到共享状态,因为改变了已安装应用的 UID,会导致应用失去对自己文件的访问权限(在一些早期 Android 版本中),所以如果使用共享 UID 必须从一开始就设计好。

三、权限的授予

几个重要的配置文件:

  • /data/system/packages.xml
  • /data/system/users/0/runtime-permissions.xml。
  • /etc/permission/platform.xml
  • /system/core/include/private/android_filesystem_config.h

3.1、权限管理

在每个应用安装时,权限就已经赋予了,系统使用包管理服务来管理权限。打开我们系统目录下的 /data/system/packages.xml,可以看到文件包含了所有已定义的权限列表和所有 apk 的包信息,这可以看做是包管理服务维护的一个已安装程序的核心数据库,这个数据库,随着每次应用安装、升级或卸载而进行更新。

package.xml
  • permissions标签内,定义了目前系统中的所有权限,分为系统内置的(package 属性为 android 的)和 apk 自定义的(package 属性为 apk 的包名)
<permissions>
        <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
        <item name="android.permission.ACCESS_CACHE_FILESYSTEM" package="android" protection="18" />
        <item name="android.permission.REMOTE_AUDIO_PLAYBACK" package="android" protection="2" />
        <item name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" package="com.android.providers.downloads" />
        <item name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" package="android" protection="2" />
        <item name="android.permission.INTENT_FILTER_VERIFICATION_AGENT" package="android" protection="18" />
        <item name="android.permission.BIND_INCALL_SERVICE" package="android" protection="18" />
        <item name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" package="android" protection="18" />
        ...
</permissions>
  • package标签, 包含了每个apk 的核心属性。

Android 6.0 及以上 packages.xml

<package name="com.zuoyebang.iot.watch.zappstore" codePath="/system/app/zappstore" nativeLibraryPath="/system/app/zappstore/lib" primaryCpuAbi="armeabi-v7a" publicFlags="675855941" privateFlags="0" ft="11e8f7d4c00" it="11e8f7d4c00" ut="11e8f7d4c00" version="1" userId="10045" isOrphaned="true">
            <sigs count="1">
                <cert index="0" />
            </sigs>
            <perms>
                <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
                <item name="android.permission.RESTART_PACKAGES" granted="true" flags="0" />
                <item name="android.permission.MODIFY_AUDIO_SETTINGS" granted="true" flags="0" />
                <item name="android.permission.SYSTEM_ALERT_WINDOW" granted="true" flags="0" />
                <item name="android.permission.INSTALL_PACKAGES" granted="true" flags="0" />
                <item name="android.permission.CHANGE_NETWORK_STATE" granted="true" flags="0" />
                <item name="android.permission.WRITE_SYNC_SETTINGS" granted="true" flags="0" />
                <item name="android.permission.RECEIVE_BOOT_COMPLETED" granted="true" flags="0" />
                <item name="android.permission.INTERNET" granted="true" flags="0" />
                <item name="android.permission.STOP_APP_SWITCHES" granted="true" flags="0" />
                <item name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" granted="true" flags="0" />
                <item name="android.permission.WRITE_SECURE_SETTINGS" granted="true" flags="0" />
                <item name="android.permission.CHANGE_WIFI_STATE" granted="true" flags="0" />
                <item name="android.permission.DELETE_CACHE_FILES" granted="true" flags="0" />
                <item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" />
                <item name="android.permission.READ_LOGS" granted="true" flags="0" />
                <item name="android.permission.READ_NETWORK_USAGE_HISTORY" granted="true" flags="0" />
                <item name="android.permission.VIBRATE" granted="true" flags="0" />
                <item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />
                <item name="android.permission.MODIFY_PHONE_STATE" granted="true" flags="0" />
                <item name="android.permission.WAKE_LOCK" granted="true" flags="0" />
                <item name="android.permission.DELETE_PACKAGES" granted="true" flags="0" />
            </perms>
            <proper-signing-keyset identifier="1" />
        </package>

Android 6.0以上normal权限 安装之后自动授予, 会枚举在在perms列表中; 被标记为dangrous的权限并不会列举在 <perms>中,如:READ_SMS、READ_EXTERNAL_STORAGE、CALL_PHONE 等。

Android 6.0 之前,权限都是在安装时自动赋予的,不卸载应用的情况下,不能更改或撤销。而 Android 6.0 版本对 permission 的管理做了部分改动,针对 dangerous 级别,不再安装的时候赋予权限,而是在运行时动态申请。

/data/system/packages.xml 里保留的是安装后不会再变更的权限(normal)

运行时权限(dangrous)另外单独地维护在
/data/system/users/0/runtime-permissions.xml中。

 <pkg name="com.example.testpermission">
    <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
    <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
  </pkg>

3.2、权限授予

Android 应用安装时,会被分配一个唯一的 UID,应用启动时会设置新建进程的 UID 和 GID 为应用程序的 UID。如果应用已经被赋予了额外的权限,就把这些权限映射成一组 GID,作为补充 GID 分配给进程。底层就可以依赖于进程的 UID、GID 和补充 GID 来决定是否赋予权限了。

上面流程的重点:

  • 权限是如何映射到 OS 层的 UID、GID 上的呢?
  • 映射完是怎么分配给进程的?
  • 低层是怎么判断是否赋予权限的?

/etc/permission/platform.xml

3.2.1、权限和补充Gid的映射

内置权限到 GID 的映射是定义在/etc/permission/platform.xml中

<permissions>
    ···
    <permission name="android.permission.READ_EXTERNAL_STORAGE" >
        <group gid="sdcard_r" />
    </permission>

    <permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
        <group gid="sdcard_r" />
        <group gid="sdcard_rw" />
    </permission>
    
    <permission name="android.permission.INTERNET" >
        <group gid="inet" />
    </permission>
    ···
</permissions>

READ_EXTERNAL_STORAGE 这种运行时权限,在 Android 6.0 之后已经不会映射到 gid 了。动态赋予,动态申请,也就不需要映射了。

<permission name="android.permission.READ_EXTERNAL_STORAGE" />
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" />

3.2.2、查看进程授予的补充GID

adb shell ps 查看进程的PID

adb shell ps | grep zappstore
u0_a45       10732   245 1230560 131600 SyS_epoll_wait b0cce590 S com.zuoyebang.iot.watch.zappstore

adb shell cat /proc/10732/status 查看进程的状态

sl8541e_1h10_go:/proc/10732 # cat status
Name:   watch.zappstore
State:  S (sleeping)
Tgid:   10732
Ngid:   0
Pid:    10732
PPid:   245
TracerPid:  0
Uid:    10045   10045   10045   10045
Gid:    10045   10045   10045   10045
FDSize: 256
Groups: 1007 3003 3006 9997 20045 50045 

可以看到Uid和Gid 为10045

Groups: 1007 3003 3006 9997 20045 50045

为此app所属的辅助组,通过辅助组可以查看,该进程被赋予了哪些权限

Android源码android_filesystem_config.h 有辅助组的权限定义

\system\core\include\private\android_filesystem_config.h


#define AID_NET_BT_ADMIN 3001 /* bluetooth: create any socket */
#define AID_NET_BT 3002       /* bluetooth: create sco, rfcomm or l2cap sockets */
#define AID_INET 3003         /* can create AF_INET and AF_INET6 sockets */
映射关系.png

3.2.3、进程赋予Gid权限组

当我们安装应用完成,启动应用,应用的进程是如何启动并被赋予进程属性

每个应用都会运行在自己的 Dalvik 虚拟机进程中,但是为了提高启动效率,Android 不会为每个应用都新建一个 Dalvik 进程,而是采用 fork 的形式。每个进程都 fork form zygote 进程。

当 zygote 收到启动新进程的请求时,它会 fork 自身出一个子进程,并对该子进程做特殊化处理。其源代码位于 dalvik/vm/native/dalvik_system_Zygote.c 中。forkAndSpecializeCommon() 的

static pid_t forkAndSpecializeCommon(const u4* args, boolisSystemServer)  
{  
 ...  
 pid = fork();  //创建新进程  
 if (pid == 0)  //判断是否是root,有没有权限修改自己的进程属性
 {  
  setgroupsIntarray(gids);  //设置进程的所有组  
  setrlimitsFromArray(rlimits);  
  setgid(gid);    //设置进程的组ID  
  setuid(uid);    //设置进程的用户ID  
     }  
  ...  
} 

这里设置进程的组 ID 和用户 ID,通过 fork 创建的子进程调用 setgroups Intarray 设置该进程所属的组,这样应用程序就拥有了该组的权限,并且可以通过 setgid() 及 setuid() 确定应用程序的 GID 及 UID 值。

3.2.4、权限检查(权限执行)

1). 系统内核层权限检查

以 android.permission.INTERNET 网络权限为例,Android系统是如何检查一个应用是否有网络访问权限的

Android 的访问控制,和 Linux 是一样的,但 Android 增加了个特有的网络访问安全控制机制,也就是说,创建网络套接字的进程,必须属于 inet 组。

我们看下kernel中的相关代码,kernel/apv4/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);
}
#else
static inline int current_has_network(void)
{
    return 1;
}
#endif



/*
 *  Create an inet socket.
 */

static int inet_create(struct net *net, struct socket *sock, int protocol,
               int kern)
{

    ...


    //判断当前进程是否具有网络权限;没有权限则直接返回
    if (!current_has_network())
        return -EACCES;

   
    ...

}

如上内核代码,current_has_network(void) 方法检查了进程的所在组。如果不在 inet 组,则直接返回错误。所以为了使我们的应用具有访问网络的能力,我们需要在 AndroidManifest.xml 中申请 INTERNET 权限,经过解析,逐步映射到内核层的组 ID 和用户 ID,最终才能通过内核层的检查。

2)非内核层的其他 C/C++ 层,如何拿到进程的所在组信息

在 PMS 初始化所有包信息之后,就会调用 mSettings.writePackageListLPr()将mPackages 中保存的所有包的信息保存到 /data/system/packages.list

frameworks/base/services/core/java/com/android/server/pm/Settings

 void writePackageListLPr(int creatingUserId) {
        // Only derive GIDs for active users (not dying)
        final List<UserInfo> users = UserManagerService.getInstance().getUsers(true);
        int[] userIds = new int[users.size()];
        for (int i = 0; i < userIds.length; i++) {
            userIds[i] = users.get(i).id;
        }
        if (creatingUserId != -1) {
            userIds = ArrayUtils.appendInt(userIds, creatingUserId);
        }

        // Write package list file now, use a JournaledFile.
        File tempFile = new File(mPackageListFilename.getAbsolutePath() + ".tmp");
        JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile);

        final File writeTarget = journal.chooseForWrite();
        FileOutputStream fstr;
        BufferedWriter writer = null;
        try {
            fstr = new FileOutputStream(writeTarget);
            writer = new BufferedWriter(new OutputStreamWriter(fstr, Charset.defaultCharset()));
            FileUtils.setPermissions(fstr.getFD(), 0640, SYSTEM_UID, PACKAGE_INFO_GID);

            StringBuilder sb = new StringBuilder();
            for (final PackageSetting pkg : mPackages.values()) {
                if (pkg.pkg == null || pkg.pkg.applicationInfo == null
                        || pkg.pkg.applicationInfo.dataDir == null) {
                    if (!"android".equals(pkg.name)) {
                        Slog.w(TAG, "Skipping " + pkg + " due to missing metadata");
                    }
                    continue;
                }

                final ApplicationInfo ai = pkg.pkg.applicationInfo;
                final String dataPath = ai.dataDir;
                final boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
                final int[] gids = pkg.getPermissionsState().computeGids(userIds);

                // Avoid any application that has a space in its path.
                if (dataPath.indexOf(' ') >= 0)
                    continue;

                // we store on each line the following information for now:
                //
                // pkgName    - package name
                // userId     - application-specific user id
                // debugFlag  - 0 or 1 if the package is debuggable.
                // dataPath   - path to package's data path
                // seinfo     - seinfo label for the app (assigned at install time)
                // gids       - supplementary gids this app launches with
                //
                // NOTE: We prefer not to expose all ApplicationInfo flags for now.
                //
                // DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS
                // FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES:
                //   frameworks/base/libs/packagelistparser
                //   system/core/run-as/run-as.c
                //
                sb.setLength(0);
                sb.append(ai.packageName);
                sb.append(" ");
                sb.append(ai.uid);
                sb.append(isDebug ? " 1 " : " 0 ");
                sb.append(dataPath);
                sb.append(" ");
                sb.append(ai.seInfo);
                sb.append(" ");
                if (gids != null && gids.length > 0) {
                    sb.append(gids[0]);
                    for (int i = 1; i < gids.length; i++) {
                        sb.append(",");
                        sb.append(gids[i]);
                    }
                } else {
                    sb.append("none");
                }
                sb.append("\n");
                writer.append(sb);
            }
            writer.flush();
            FileUtils.sync(fstr);
            writer.close();
            journal.commit();
        } catch (Exception e) {
            Slog.wtf(TAG, "Failed to write packages.list", e);
            IoUtils.closeQuietly(writer);
            journal.rollback();
        }
    }

packages.list中保存了所有应用申请的权限,C代码只要读这个文件就能判断某个应用是否申请了我们要求的权限。

/data/system/packages.list

com.example.testpermission 10056 1 /data/user/0/com.example.testpermission default:targetSdkVersion=32 3003
com.android.bluetoothmidiservice 10022 0 /data/user/0/com.android.bluetoothmidiservice platform:targetSdkVersion=27 3002
plugin.sprd.vodafonefeatures 10024 0 /data/user/0/plugin.sprd.vodafonefeatures platform:targetSdkVersion=27 none

3) 框架层 PackageManagerService 系统包管理器会负责记录组件的权限.

使用 Binder.getCallingUid()Binder.getCallingPid() 获取调用者的 UID 和 PID,通过 UID 在包管理器中查询到对应应用的权限。


android.content.Context 类中有 checkPermission(String permission, int pid, int uid) 方法 实质上会调用到 PMS 中的 checkUidPermission(String perName, int uid)

  • Android 6.0 以下 PMS 中的 checkUidPermission(String perName, int uid)

    public int checkUidPermission(String permName, int uid) {
        synchronized (mPackages) {
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                GrantedPermissions gp = (GrantedPermissions)obj;
                if (gp.grantedPermissions.contains(permName)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } else {
                HashSet<String> perms = mSystemPermissions.get(uid);
                if (perms != null && perms.contains(permName)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            }
        }
        return PackageManager.PERMISSION_DENIED;
    }
    

Android 6.0 以下的 checkUidPermission() 方法比较简单,首先,基于入参 uid 获取应用的 appId,拿到权限列表对象(也就是 packages.xml 里的 <package> 映射),如果 GrantedPermissions 类中的 grantedPermissions 集合包含目标权限,则检查通过。

  • Android 6.0 及以上 PMS 中的 checkUidPermission(String perName, int uid)
 @Override
    public int checkUidPermission(String permName, int uid) {
        final int callingUid = Binder.getCallingUid();
        final int callingUserId = UserHandle.getUserId(callingUid);
        final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
        final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
        final int userId = UserHandle.getUserId(uid);
        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }

        synchronized (mPackages) {
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                ...
                final SettingBase settingBase = (SettingBase) obj;
                final PermissionsState permissionsState = settingBase.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    if (isUidInstantApp) {
                        BasePermission bp = mSettings.mPermissions.get(permName);
                        if (bp != null && bp.isInstant()) {
                            return PackageManager.PERMISSION_GRANTED;
                        }
                    } else {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
                ...
            } else {
                ArraySet<String> perms = mSystemPermissions.get(uid);
                if (perms != null) {
                    if (perms.contains(permName)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                    ...
                }
            }
        }

        return PackageManager.PERMISSION_DENIED;
    }

6.0 之后 checkPermission() 方法有所改变。多了从 mSettings.mPermissions 去查询权限列表。

关键就在于这个 mSettings 里面保存的这个 SettingBase 对象,它记录了 PermissionsState 也就是权限的授予情况。

// PermissionsState.java
public boolean hasPermission(String name, int userId) {
    enforceValidUserId(userId);

    if (mPermissions == null) {
        return false;
    }

    PermissionData permissionData = mPermissions.get(name);
    return permissionData != null && permissionData.isGranted(userId);
}

3.3、小结

  • /data/system/packages.xml

    安装即授予的权限(normal权限) 会记录在packages.xml 对应package包名下的perms节点 。管理普通权限,且默认granted.

    <package name="com.example.testpermission" codePath="/data/app/com.example.testpermission-JVGWMzpTAQE0xVKRmwYOuw==" nativeLibraryPath="/data/app/com.example.testpermission-JVGWMzpTAQE0xVKRmwYOuw==/lib" publicFlags="944291654" privateFlags="0" ft="1879dad5090" it="1879d8dde0b" ut="1879dad52af" version="1" userId="10056">
        <sigs count="1">
            <cert index="8" />
        </sigs>
        <perms>
            <item name="android.permission.INTERNET" granted="true" flags="0" />
        </perms>
        <proper-signing-keyset identifier="12" />
    </package>
  • /data/system/users/0/runtime-permissions.xml。

管理运行时权限,未requestPermissions()不会有对应app的pkg标签。

<pkg name="com.example.testpermission">
    <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
    <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
  </pkg>
  • /etc/permission/platform.xml

记录了权限和Gid的映射关系(normal 权限有效),仅普通权限会记录在此处

<permissions>
    ···
    <permission name="android.permission.INTERNET" >
        <group gid="inet" />
    </permission>
    ···
</permissions>
  • /system/core/include/private/android_filesystem_config.h

    android_filesystem_config.h 定义了AID的具体内容 和权限组gid相对应。

    
    #define AID_NET_BT_ADMIN 3001 /* bluetooth: create any socket */
    #define AID_NET_BT 3002       /* bluetooth: create sco, rfcomm or l2cap sockets */
    #define AID_INET 3003         /* can create AF_INET and AF_INET6 sockets */
    
  • /data/system/packages.list packages.list中保存了所有应用申请的权限

packages.list保存了应用安装后,分配的uid、gid 和补充组gid

com.android.emergency 10010 0 /data/user_de/0/com.android.emergency platform:privapp:targetSdkVersion=27 none
com.android.location.fused 1000 0 /data/user_de/0/com.android.location.fused platform:privapp:targetSdkVersion=27 2001,1013,3002,1023,1015,3003,3001,1007,3006
com.example.testpermission 10056 1 /data/user/0/com.example.testpermission default:targetSdkVersion=32 3003

四、SELinux

4.1、基本概念

SELinux 全称 Security Enhanced Linux (安全强化 Linux),是MAC (Mandatory Access Control,强制访问控制系统)的一个实现。其目的在于明确的指明某个进程可以访问哪些资源(文件、网络端口等)。Android系统基于Linux实现。针对传统Linux系统,NSA开发了一套安全机制SELinux,用来加强安全性。然而,由于Android系 统有着独特的用户空间运行时,因此SELinux不能完全适用于Android系统。为此,NSA同Google一起针对Android系统,在SELinux基础上开发了 SEAndroid。

1) DAC和MAC的区别:

DAC核心思想:进程理论上所拥有的权限与执行它的用户的权限相同。比如,以root用户启动Browser,那么Browser就有root用户的权限,在Linux系统上能干任何事情。

MAC核心思想:即任何进程想在SELinux系统中干任何事情,都必须先在安全策略配置文件中赋予权限。凡是没有出现在安全策略配置文件中的权限,进程就没有该权限。

2) DAC和MAC的联系:

SELinux 通过MAC的方式来控管程序,控制的主题是进程,客体是该程序能否读写的资源(文件)

  • 主体(subject):进程

  • 目标(Object):被访问的资源(可以是文件、目录、端口、设备等)

  • 政策(policy):访问限制条件

  • 安全上下文(security context)
    主体能不能存取目标除了政策之外,主体与目标的安全性文本必须一致才能够顺利获取

3)DAC、MAC 验证顺序:

Android系统 会首先验证DAC权限 验证通过后,会验证MAC是否授予权限

4.2、Security Context 安全上下文

SEAndroid是一种基于安全策略的MAC 安全机制。这种安全策略又是建立在对象的安全上下文的基础上的。这里所说的对象分为两种类型,一种称 主体(Subject),一种称为客体(Object)。主体通常就是指进程,而客体就是指进程所要访问的资源,例如文件、系统属性等。

4.2.1、什么安全上下文

安全上下文实际上就是一个附加在对象上的标签(label)

进程A 对文件B进行读操作,系统会检查A的安全上下文标签,对B的安全上下文标签 是否具有读权限。

这个标签实际上就是一个字符串,它由四部分内容组成,分别是SELinux用户、SELinux 角色、类型、安全级别,每一个部分都通过一个冒号来分隔,格式为“user:role:type:rank”。

SELinux中 每种东西都被赋予了一个安全属性,称为Security Context (安全上下文),它是一个字符串。 Scontext分为:进程(主体)的scontext 和 文件(客体)的tcontext。两者匹配时,才会允许进程访问文件,若不匹配时则不允许访问。

安全上下文和标签.png

4.2.2、查看进程的安全上下文:

进程的 Security Context 可通过 ps -(A)Z 命令查看
adb shell ps -Z

u:r:hal_wifi_supplicant_default:s0 wifi      1033     1   11488   7592 0                   0 S wpa_supplicant
u:r:radio:s0                   radio         1044   245 1174504 114696 0                   0 S com.android.phone
u:r:system_app:s0              system        1189   245 1179352 103264 0                   0 S

4.2.3、查看文件的安全上下文:

文件的 Secure Context 可以通过 ls (file) -Z 来查看:

# adb shell ls /data/data/ -Z 
u:object_r:system_app_data_file:s0    com.android.inputdevices             u:object_r:system_app_data_file:s0    com.zuoyebang.iot.watch.zalarm         
u:object_r:system_app_data_file:s0 


u :user,SEAndroid 中定义了一个用户
r : role,一个 u 可以属于多个 role,不同的 role 具有不同的权限。在SEAndroid中的role有两个,分别为 r 和 object_r
object_r:role,文件是死的东西,在 SELinux 中,死的东西都用 object_r 来表示它的 role
platform_app :type,表示该进程所属的 type 为 platform_app
adb_data_file :type,表示 adb 文件夹所属的 type 为 adb_data_file
s0:c512 : sensitivity,category ,是 SELinux 为了满足军用和教育行业而设计的 Multi-Level Security(MLS)机制。MLS 将系统的进程和文件进行了分级,不同级别的资源需要对应级别的进程才能访问

在安全上下文中,只有类型(Type)才是最重要的,SELinux用户、SELinux角色和安全级别都几乎可以忽略不计的。正因为如此,SEAndroid安全机制又称为是基于TE(Type Enforcement)策略的安全机制。

4.3、安全策略 TE

安全策略.png

安全策略是在安全上下文的基础上进行描述的,它通过主体和客体的安全上下文,定义主体是否有权限访问客体。SEAndroid安全机制主要是使用安全上下文中的类型来定义安全策略,这种安全策略称为Type EnforceMent,简称TE。所有以.te为后缀的文件经过编译后都会生成一个sepolicy文件。这个policy文件会打包在ROM中,并且保存在设备上。

根据Selinux规范,完整的allow相关语句格式为:
rule_name:source_type:target_type:class perm_set

source_type和target_type 都是查看进程和文件的安全上下文得来的。

安全策略规则_2.png

备注:
SEAndroid 将app分了几类:

  • platform_app.te (具有android platfrom签名,但是没有system权限)
  • system_app.te(具有android platfrom签名 与system权限)
  • untrusted_app.te (第三方app,不具有android platform签名和系统权限)

allow 语句的例子:

- 允许zygote domain 中的进程向init type的进程 (Object class 为process) 发送sigchld信号
    allow zygote init\:procss sigchld;

- 允许zygote域中的进程search 或getattr 类型为appdomain的目录。注意 多个perm\_set用{}括起来
    allow zygote appdomain\:dir{getattr search};

4.4、SELinux 相关设置

强制执行等级
熟悉以下术语,了解如何按不同的强制执行级别实现 SELinux

  • 宽容模式(permissive) - 仅记录但不强制执行 SELinux 安全政策。
  • 强制模式(enforcing) - 强制执行并记录安全政策。如果失败,则显示为 EPERM 错误。

4.4.1、 关闭 SELinux

临时关闭
(1) setenforce
setenforce [ Enforcing | Permissive | 1 | 0 ]
setenforce 命令修改的是 /sys/fs/selinux/enforce 节点的值,是 kernel 意义上的修改 selinux 的策略。断电之后,节点值会复位

(2) getenforce

返回结果有两种:Enforcing和Permissive. Permissive 代表SELinux关闭,不会阻止进程违反SELinux策略访问资源的行为。Enforcing 代表SELinux处于开启状态,会阻止进程违反SELinux策略访问资源的行为。

永久关闭
(1) kernel 关闭 selinux

SECURITY_SELINUX 设置为 false,重新编译 kernel1

4.5、自定义te文件

场景: 为 app_permission.sh 脚本赋予 相应的权限,使其可以开机自动运行

定义apppermission.te 定义apppermission进程type和app_

4.5.1、定义app_permission和app_permission_exec

  • 定义app_permission 和app_permission_exec 的Type类型

android8go_watch_os/device/sprd/sharkle/common/platsepolicy/public/app_permission.te


##定义app_perssion 进程域
type app_permission, domain, mlstrustedsubject;
##定义app_permission_exec 可执行文件类型
type app_permission_exec, exec_type,file_type;

  • 定义app_permission 属性、init_daemon_domain切换进程域

android8go_watch_os/device/sprd/sharkle/common/plat_sepolicy/private/app_permission.te


typeattribute app_permission coredomain;
##切换app_permission 进程域
init_daemon_domain(app_permission)

  • 赋予app_permission进程域类型 相应的selinux权限

    android8go_watch_os/device/sprd/sharkle/common/sepolicy/app_permission.te

    #fanlongjun add for aplog!
    #binder_use(app_permission)
    allow app_permission shell_exec:file {read open execute getattr};
    allow app_permission toolbox_exec:file {read open execute getattr execute_no_trans};
    allow app_permission logcat_exec:file {  getattr execute read open execute_no_trans };   #call "logcat"
    #allow app_permission vendor_toolbox_exec:file {read getattr execute};
    allow app_permission storage_file:dir {search getattr};
    allow app_permission app_permission:capability {chown sys_admin dac_override net_raw sys_nice setuid setgid sys_nice fsetid};
    allow app_permission sdcardfs:dir {create write search add_name read remove_name open rmdir rename reparent setattr};
    allow app_permission sdcardfs:file {read append create open getattr write unlink rename setattr};
    allow app_permission media_rw_data_file:dir {create write search add_name read remove_name open rmdir rename reparent setattr getattr};
    allow app_permission media_rw_data_file:file {read append create open getattr write unlink rename setattr};
    allow app_permission logdr_socket:sock_file {write};
    allow app_permission logd:unix_stream_socket {connectto};
    allow app_permission system_file:file {read open execute getattr execute_no_trans};
    allow app_permission storage_file:lnk_file {read getattr};
    allow app_permission mnt_user_file:dir  {  search read open write add_name remove_name };
    allow app_permission mnt_user_file:lnk_file {  read open write };
    
    allow app_permission system_app_data_file:dir {getattr read};
    allow app_permission system_data_file:file {getattr read};
    allow app_permission app_data_file:dir { read setattr getattr};
    allow app_permission self:capability fowner;
    
    

4.5.2、为app_permission.sh 文件实体 绑定app_permission_exec 安全上下文标签

android8go_watch_os/device/sprd/sharkle/common/plat_sepolicy/private/file_contexts


#为app_permission.sh 绑定安全上下文
/system/bin/app_permission.sh      u:object_r:app_permission_exec:s0

五、参考文章

Android 安全架构

https://www.jianshu.com/p/5284fd388394

Android UID/GID

https://blog.csdn.net/weixin_40366279/article/details/121281882

SEAndroid安全机制框架分析

SELinux用audio2allow生成添加权限的格式及neverallow解决方法

Linux学习 - SELinux/SEAndroid 不错的文章

Android 权限配置文件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容