知识图库
Java知识库
JDK线程池实现原理
Java中的强、软、弱、虚引用
深入拆解Java虚拟机
01 开篇词 | 为什么我们要学习Java虚拟机?
02 Java代码是怎么运行的?
03 Java的基本类型
04 Java虚拟机是如何加载Java类的?
05 JVM是如何执行方法调用的?(上)
06 JVM是如何执行方法调用的?(下)
7 JVM是如何处理异常的?
Java面试常见问题整理
Java面试常见问题-Java 基础篇
Java面试常见问题-Jvm篇
Java面试常见问题-并发篇
Android知识库
Kotlin编程第一课
1 开篇词 | 入门Kotlin有多容易,精通Kotlin就有多难
2 Kotlin基础语法:正式开启学习之旅
3 面向对象:理解Kotlin设计者的良苦用心
4 Kotlin原理:编译器在幕后干了哪些“好事”?
5 实战:构建一个Kotlin版本的四则运算计算器
6 object关键字:你到底有多少种用法?
7 扩展:你的能力边界到底在哪里?
8 高阶函数:为什么说函数是Kotlin的“一等公民”?
9 实战:用Kotlin写一个英语词频统计程序
10 加餐一 | 初识Kotlin函数式编程
11 委托:你为何总是被低估?
12 泛型:逆变or协变,傻傻分不清?
13 注解与反射:进阶必备技能
14 实战:用Kotlin实现一个网络请求框架KtHttp
15 加餐二 | 什么是“表达式思维”?
16 加餐三 | 什么是“不变性思维”?
17 加餐四 | 什么是“空安全思维”?
18 春节刷题计划(一)| 当Kotlin遇上LeetCode
19 春节刷题计划(二)| 一题三解,搞定版本号判断
20 春节刷题计划(三)| 一题双解,搞定求解方程
21 春节刷题计划(四)| 一题三解,搞定分式加减法
22 什么是“协程思维模型”?
23 如何启动协程?
24 挂起函数:Kotlin协程的核心
25 Job:协程也有生命周期吗?
26 Context:万物皆为Context?
27 实战:让KtHttp支持挂起函数
28 期中考试 | 用Kotlin实现图片处理程序
29 题目解答 | 期中考试版本参考实现
30 Channel:为什么说Channel是“热”的?
31 Flow:为什么说Flow是“冷”的?
32 select:到底是在选择什么?
33 并发:协程不需要处理同步吗?
34 异常:try-catch居然会不起作用?坑!
35 实战:让KtHttp支持Flow
36 答疑(一)| Java和Kotlin到底谁好谁坏?
37 集合操作符:你也会“看完就忘”吗?
38 协程源码的地图:如何读源码才不会迷失?
39 图解挂起函数:原来你就是个状态机?
40 加餐五 | 深入理解协程基础元素
41 launch的背后到底发生了什么?
42 Dispatchers是如何工作的?
43 CoroutineScope是如何管理协程的?
44 图解Channel:如何理解它的CSP通信模型?
45 图解Flow:原来你是只纸老虎?
46 Java Android开发者还会有未来吗?
47 Kotlin与Jetpack简直是天生一对!
48 用Kotlin写一个GitHub Trending App
49 结课测试 | “Kotlin编程第一课”100分试卷等你来挑战!
50 结束语 | 不忘初心
Android Framework 教程—基础篇
01 Ubuntu 使用快速入门
02 Make 构建工具入门
03 理解 Unicode UTF-8 UTF-16 UTF-32
04 Linux Shell 脚本编程入门1——核心基础语法
05 SeAndroid 使用极速上手
06 理解 C++ 的 Memory Order
07 AOSP 极速上手
08 系统开发工具推荐
09 添加 Product
运动相关知识
爱上跑步
01 开篇词 | 跑步,不那么简单的事儿
02 跑两步就喘了,是不是我不适合跑步?
03 正确的跑步姿势是什么样的?
04 为什么跑步要先热身?
05 怎样制定你的第一个10公里跑步计划?
06 快跑和慢跑,哪个更燃脂?
07 普通跑步者应该如何选择跑鞋?
08 买跑步装备,不要踩这些坑儿
09 跑步前到底应不应该吃东西?
10 跑步到底伤不伤膝盖?
11 参加了20场马拉松,我是如何准备的?
12 除了马拉松,还能参加哪些跑步赛事?
13 热点问题答疑 :跑完第二天浑身疼,还要不要继续跑?
健身房计划
[DeepSeek]减脂塑形计划
【DeepSeek】训练周期安排
每日餐饮热量控制
减脂期间食物推荐避坑指南
HarmonyOS知识库
其他知识类目
心理学相关
如何学点心理学——关于非专业人士学心理学的一点建议
投射性认同
-
+
首页
05 SeAndroid 使用极速上手
### 1. 基本概念 SEAndroid 是一种安全系统,相关的概念和术语对于初学者来说都相对晦涩难懂。我们可以简单地理解: - 在 Android 系统里面有很多资源(资源主要包括了根文件系统下的文件,属性系统中的属性,binder 服务,进程,用户等) - 为了方便描述,我们需要给这些资源取名字 - 为方便管理,我们需要给资源进行分类管理 - 另外,我们需要定义一些规则,来规范资源的使用 #### 1.1 资源的名字——安全上下文(security context) 资源的名字在 SEAndroid 中称之为安全上下文(security context),我们看一个例子: ```shell # 这里的意思是 /dev/myse_dev 的名字(security context)是 u:object_r:myse_testdev_t:s0 /dev/myse_dev u:object_r:myse_testdev_t:s0 ``` `u:object_r:myse_testdev_t:s0` 就是一个安全上下文(security context) 由4部分组成: - u 是 selinux 的用户名(user),在 Android 中只定义了一个用户,固定为 u。 - object_r 是 selinux 中的角色(role),类似于 linux 中的用户组,一个用户可以拥有多个角色。 - myse_testdev_t 是资源的类型,对于文件来说叫 type,对于进程叫 domain,内容为用户自定义。 - S0 和 SELinux 为了满足军用和教育行业而设计的 Multi-Level Security(MLS)机制有关。在 Android 中固定为 s0。 上面说到,资源的类型可以自定义,接下来我们看看资源类型具体如何定义: ```bash # 定义一个类型 myse_testdev_t,后面的 dev_type 表示 myse_testdev_t 是一个设备类型 type myse_testdev_t, dev_type; ``` 这里的 dev_type 可以理解为安全上下文类型的类型,通过 attribute 定义: ```bash # 定义一个类型 dev_type,表示设备类型 来自 system/sepolicy/public/attributes attribute dev_type ``` 当定义好安全上下文后,我们需要将安全上下文(资源的名字)与具体的资源相关联: ```bash # 这里的意思是 /dev/myse_dev 的名字(security context)是 u:object_r:myse_testdev_t:s0 /dev/myse_dev u:object_r:myse_testdev_t:s0 ``` #### 1.2 定义资源的使用规则 以上的准备工作完成后,我们就可以添加相应的规则来限制资源的访问: ```bash # 示例来自于 system/sepolicy/private/adbd.te # 允许 adbd (安全上下文), 对安全上下文为 anr_data_file 的目录(dir)有读目录权限 allow adbd anr_data_file:dir r_dir_perms; ``` 上面这条规则的意思是:允许 adbd (安全上下文), 对安全上下文为 anr_data_file 的目录(dir)有读目录的权限。其中 adbd 称之为主体,anr_data_file 称为客体,dir 表示客体的类型是目录,类型的定义位于:`system/sepolicy/private/security_classes` : ```bash class security class process class system class capability # file-related classes class filesystem class file class dir class fd class lnk_file class chr_file class blk_file class sock_file class fifo_file # network-related classes class socket class tcp_socket class udp_socket class rawip_socket class node class netif class netlink_socket class packet_socket class key_socket class unix_stream_socket class unix_dgram_socket # 省略 #....... ``` r_dir_perms 表示目录的读权限,定义在 `system/sepolicy/public/global_macros`: ```bash # ...... define(`x_file_perms', `{ getattr execute execute_no_trans map }') define(`r_file_perms', `{ getattr open read ioctl lock map }') define(`w_file_perms', `{ open append write lock map }') define(`rx_file_perms', `{ r_file_perms x_file_perms }') define(`ra_file_perms', `{ r_file_perms append }') define(`rw_file_perms', `{ r_file_perms w_file_perms }') define(`rwx_file_perms', `{ rw_file_perms x_file_perms }') define(`create_file_perms', `{ create rename setattr unlink rw_file_perms }') #...... ``` #### 1.3 类型转换(Domain/Type Transition) 系统在运行的过程中,资源的名字(安全上下文)是会变化的,一个常见的例子:init 进程的名字为 `u:r:init:s0`,而 init fork 的子进程显然不会也不应该拥有和 init 进程一样的名字,否则这些子进程就有了和 init 一样的权限,这不是我们需要的结果。这样的问题称之为**类型转换(Domain/Type Transition) **。 接着我们来看一个类型转换的例子(例子来自这里[深入理解SELinux SEAndroid(第一部分)](https://link.juejin.cn/?target=https%3A%2F%2Fblog.csdn.net%2Finnost%2Farticle%2Fdetails%2F19299937)): ```bash type_transition init_t apache_exec_t : process apache_t; ``` type_transition 用于定义类型转换,上述语句的意思是:init_t 在执行(fork 并 execv)类型为 apache_exec_t 的可执行文件时,对应的进程(process)的类型要切换到 apache_t 上面的切换并不完整,需要完成切换,还需要下面的规则配合: ```shell # init_t 进程能够执行 type 为 apache_exec_t 的文件 allow init_t apache_exec_t : file execute; # 允许 init_t 切换进入 apache_t allow init_t apache_t : process transition; # 切换入口(对应为entrypoint权限)为 apache_exec_t allow apache_t apache_exec_t : file entrypoint; ``` 每次都这样写稍显麻烦,SeAndroid 定义了一个宏 `domain_auto_trans` 来帮我们完成上述所有功能: ```shell domain_auto_trans(init_t, apache_exec_t, apache_t) ``` 针对文件,也需要做类型转换,比如: ```css file_type_auto_trans(appdomain, download_file, download_file) ``` 上述规则的意思是,当资源名(安全上下文)为 `appdomain` 的进程在资源名(安全上下文)为 download_file 的文件夹中创建新的文件时,新文件的资源名(安全上下文)为 `download_file` ### 2. Hello SeAndroid 示例 SEAndroid 相关的内容繁多,要完全掌握其细节费神费力,一般通过示例来学习和积累。这里演示一个访问设备文件的 CPP 可执行程序。 在 `device/Jelly/Rice14` 目录下添加如下的文件和文件夹: ```bash hello_seandroid ├── Android.bp └── hello_seandroid.c sepolicy ├── device.te ├── file_contexts └── hello_se.te ``` `hello_seandroid` 目录下是一个读取文件的可执行程序: hello_seandroid.c: ```c++ #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define LOG_TAG "helloseandroid" #include <log/log.h> int main(int argc, char *argv[]) { int fd = -1; int ret = -1; char *content = "hello test for selinux"; char *dev_name = "/dev/hello_seandroid_dev"; fd = open(dev_name, O_RDWR); if(fd < 0) { ALOGE("open %s error: %s", dev_name, strerror(errno)); return -1; } ret = write(fd, content, strlen(content)); if(ret < 0) { ALOGE("write testfile error: %s", strerror(errno)); return -1; }else { ALOGD("write testfile ok: %d", ret); } while(1); close(fd); return 0; } ``` 这段程序的主要作用就是向 `/dev/hello_seandroid_dev` 文件写入一段字符串。 Android.bp: ```json cc_binary { name: "helloseandroid", srcs: ["hello_seandroid.c"], cflags: [ "-Werror", "-Wno-unused-parameter" ], //Android10 上貌似不支持配置 product 分区的 sepolicy(Android 11 及以后是支持的) //所以只能选择 vendor 分区了 vendor: true, shared_libs: [ "libcutils", "liblog" ] } ``` hello_se.te: ```bash # 进程对应的类型 type hello_se_dt, domain; # 可执行文件对应的类型 type hello_se_dt_exec, exec_type, vendor_file_type, file_type; #表示该程序如果从 init 进程启动 hello_se_dt_exec,其安全上下文的 domain 部分从 init 转化为 hello_seandroid_dt init_daemon_domain(hello_se_dt); #从 shell 启动 type 为 hello_se_dt_exec 的可执行程序,其对应进程的 domain 为 hello_se_dt domain_auto_trans(shell, hello_se_dt_exec, hello_se_dt); ``` device.te: ```bash # 定义设备 /dev/hello_seandroid_dev 的类型 type hello_se_dev_t, dev_type; ``` file_contexts: ```bash /vendor/bin/helloseandroid u:object_r:hello_se_dt_exec:s0 /dev/hello_seandroid_dev u:object_r:hello_se_dev_t:s0 ``` 编译运行: ```bash source build/envsetup.sh # 注意这里的版本和前面不一样了,选择了 userdebug lunch Rice14-userdebug make -j16 ``` 准备工作: ```bash #进入Android shell 环境 adb shell # 创建待访问的设备文件 su #使用 root touch /dev/hello_seandroid_dev ls -Z /dev/hello_seandroid_dev u:object_r:device:s0 /dev/hello_seandroid_dev # 加载 file_contexts restorecon /dev/hello_seandroid_dev # 查看文件的安全上下文 ls -Z /dev/hello_seandroid_dev u:object_r:hello_se_dev_t:s0 /dev/hello_seandroid_dev #放宽权限 chmod 777 /dev/hello_seandroid_dev # 查看可执行文件的安全上下文 ls -Z /vendor/bin/helloseandroid u:object_r:hello_se_dt_exec:s0 /vendor/bin/helloseandroid ``` 我们没有配置 selinux 权限,所以我们的程序是不能成功执行的,但是我们可以通过 `setenforce 0` 命令切换到 `selinux permissive` 模式,该模式下不会阻止进程的行为,只会打印权限缺失信息,这样我们就可以执行我们的程序,并得到缺失的权限信息,然后通过源码提供的 `audit2allow` 工具帮我们生成对于的 selinux 权限信息: ```bash setenforce 0 exit #退出 root # 执行程序 helloseandroid & ``` 接着查看 log: ```bash logcat | grep "helloseandroid" --line-buffered | grep "avc" 04-08 15:11:05.290 6595 6595 I helloseandroid: type=1400 audit(0.0:36): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:hello_se_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=1 04-08 15:11:05.290 6595 6595 I helloseandroid: type=1400 audit(0.0:37): avc: denied { read write } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:hello_se_dt:s0 tcontext=u:object_r:devpts:s0 tclass=chr_file permissive=1 04-08 15:11:05.290 6595 6595 I helloseandroid: type=1400 audit(0.0:38): avc: denied { read write } for path="socket:[10425]" dev="sockfs" ino=10425 scontext=u:r:hello_se_dt:s0 tcontext=u:r:adbd:s0 tclass=unix_stream_socket permissive=1 04-08 15:11:05.290 6595 6595 I helloseandroid: type=1400 audit(0.0:39): avc: denied { use } for path="/vendor/bin/helloseandroid" dev="dm-1" ino=71 scontext=u:r:hello_se_dt:s0 tcontext=u:r:shell:s0 tclass=fd permissive=1 04-08 15:11:05.300 6595 6595 I helloseandroid: type=1400 audit(0.0:40): avc: denied { read write } for name="hello_seandroid_dev" dev="tmpfs" ino=23235 scontext=u:r:hello_se_dt:s0 tcontext=u:object_r:hello_se_dev_t:s0 tclass=file permissive=1 ``` 我们把权限相关的 log 复制下来,在源码目录下,保存到 avc_log.txt 文件中,并执行一下命令: ```bash source build/envsetup.sh audit2allow -i avc_log.txt allow hello_se_dt adbd:unix_stream_socket { read write }; allow hello_se_dt devpts:chr_file { read write }; allow hello_se_dt hello_se_dev_t:file { read write }; allow hello_se_dt shell:fd use; ``` 这里就会输出相应的权限规则,我们将其添加到源码中 hello_se.te 后面即可: ```bash # 进程对应的类型 type hello_se_dt, domain; # 可执行文件对应的类型 type hello_se_dt_exec, exec_type, vendor_file_type, file_type; #表示该程序如果从 init 进程启动 hello_se_dt_exec,其安全上下文的 domain 部分从 init 转化为 hello_se_dt init_daemon_domain(hello_se_dt); #从 shell 启动 type 为 hello_se_dt_exec 的可执行程序,其对应进程的 domain 为 hello_seandroid_dt domain_auto_trans(shell, hello_se_dt_exec, hello_se_dt); allow hello_se_dt adbd:unix_stream_socket { read write }; allow hello_se_dt devpts:chr_file { read write }; allow hello_se_dt hello_se_dev_t:file { read write }; allow hello_se_dt shell:fd use; ``` 再次编译运行系统,即可正常使用 helloseandroid 程序 ### 参考资料 - [SEAndroid安全机制简要介绍和学习计划](https://link.juejin.cn/?target=https%3A%2F%2Fblog.csdn.net%2FLuoshengyang%2Farticle%2Fdetails%2F35392905%3Fops_request_misc%3D%25257B%252522request%25255Fid%252522%25253A%252522166579375216800180692092%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fblog.%252522%25257D%26request_id%3D166579375216800180692092%26biz_id%3D0%26utm_medium%3Ddistribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-3-35392905-null-null.nonecase%26utm_term%3Dselinux%26spm%3D1018.2226.3001.4450) - [Android系统10 RK3399 init进程启动(二十一) DAC和MAC介绍](https://link.juejin.cn/?target=https%3A%2F%2Fblog.csdn.net%2Fldswfun%2Farticle%2Fdetails%2F124637485%3Fspm%3D1001.2014.3001.5502) - [Android系统10 RK3399 init进程启动(三十一) SeAndroid实战之定义策略](https://link.juejin.cn/?target=https%3A%2F%2Fblog.csdn.net%2Fldswfun%2Farticle%2Fdetails%2F125899780) - [深入理解SELinux SEAndroid(第一部分)](https://link.juejin.cn/?target=https%3A%2F%2Fblog.csdn.net%2Finnost%2Farticle%2Fdetails%2F19299937) - [构建 SELinux 政策](https://link.juejin.cn/?target=https%3A%2F%2Fsource.android.com%2Fdocs%2Fsecurity%2Ffeatures%2Fselinux%2Fbuild%3Fhl%3Dzh-cn) - [自定义 SELinux](https://link.juejin.cn/?target=https%3A%2F%2Fsource.android.google.cn%2Fdocs%2Fsecurity%2Fselinux%2Fcustomize%3Fhl%3Dzh-cn)
嘿手大叔
2025年1月3日 10:38
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码