前言
本文主要针对一下几个问题,通过查询资料,和一些自己的思考,梳理的一篇记录
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的权限、和其他用户的权限。如下图所示:
二、Android 沙盒机制
android集成了Linux用户和组的概念。Linux中通过用户和用户组将文件的权限进行了隔离。Android中为每一个应用赋予了一个用户(owner)和用户组(group),以实现应用各自的文件彼此隔离的目录。这就是应用的沙箱隔离机制。
android 应用程序是沙箱隔离的,每个应用都有一个只有自己具有读写权限的专用数据目录,应用只能访问自己的文件和一些设备上全局可访问的资源。
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可访问
共享UID
两个应用的具有相同的签名文件,并且指令了shareUid为同一个uid,则这两个应用可以方便的访问彼此的私有目录。
比较常见的例子是:
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 的包信息,这可以看做是包管理服务维护的一个已安装程序的核心数据库,这个数据库,随着每次应用安装、升级或卸载而进行更新。
- 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 */
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。两者匹配时,才会允许进程访问文件,若不匹配时则不允许访问。
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
安全策略是在安全上下文的基础上进行描述的,它通过主体和客体的安全上下文,定义主体是否有权限访问客体。SEAndroid安全机制主要是使用安全上下文中的类型来定义安全策略,这种安全策略称为Type EnforceMent,简称TE。所有以.te为后缀的文件经过编译后都会生成一个sepolicy文件。这个policy文件会打包在ROM中,并且保存在设备上。
根据Selinux规范,完整的allow相关语句格式为:
rule_name:source_type:target_type:class perm_set
source_type和target_type 都是查看进程和文件的安全上下文得来的。
备注:
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
五、参考文章
https://www.jianshu.com/p/5284fd388394
https://blog.csdn.net/weixin_40366279/article/details/121281882
SELinux用audio2allow生成添加权限的格式及neverallow解决方法