SEAndroid实战

系列推荐

ubuntu编译android12源码

使用AndroidStudio阅读源码

Aosp开发神器Cuttlefish

Aosp源码学习之目录介绍

Aosp之mk文件简介

AOSP实战之bp文件介绍

Aosp15之添加自己的产品Product

Aosp之编译命令介绍

Aosp之添加C++可执行程序

Aosp之添加C++动态库

SEAndroid理论基础

Aosp实战之SEAndroid实战

你是否经常呗avc: denied这个报错所折磨过的?

假如遇到这个烦人的限制正确的姿势是什么?今天我们就来解答这个问题。

在上一篇博文中我们介绍了硬啃了SEAndroid理论知识后,新手肯定是一头雾水, 没关系,想当初我学的时候也是一样,怎么看都看不懂。

菜就多练,在项目中使用多了我们自然而然就懂了。。。

我们今天就来一个简单的实战例子:我们在对我们自定义的文件/dev/MySeAndroidDemo进行读写。

当然,这里参照我们前面 Aosp之添加C++可执行程序 的文章,这里我们依然使用C++的形式来对这个节点进行读写操作。

废话少说,放码过来

依瓢画葫芦,首先我们照往常一下,建好我们的目录,建好之后我们的目录分布是这样子的:

css 复制代码
├── AndroidProducts.mk
├── BoardConfig.mk
├── IFly18.mk
└── MySeAndroidDemo
    ├── Android.bp
    └── main.cpp

首先我们的main文件也很简单,就是往dev目录下写一个文件

ini 复制代码
int main(int argc, char *argv[])
{
    int ret = -1;
    int fd;

    char *devpath = "/dev/MySeAndroidDemo";
    char *data = "This is a test content for MySeAndroidDemo";

    // 1. 打开设备/文件
    fd = open(devpath, O_RDWR);
    if (fd < 0) {
        return -1; // 打开失败直接退出
    }

    // 2. 写入数据
    ret = write(fd, data, 23); // 写入固定长度
    if (ret < 0) {
        close(fd);
        return -1; // 写入失败退出
    }

    // 3. 关闭文件描述符
    close(fd);

    return 0;
}

在看看我们的bp文件:

arduino 复制代码
cc_binary {
    name: "MySeAndroidDemo",
    srcs: ["main.cpp"],
    vendor: true, // 关键:将可执行文件安装到 /vendor/bin
    shared_libs: ["libcutils","liblog"],
}

这两个文件写好之后呢,我们还需要在我们产品的mk文件配置一下,把我们新编译的可执行程序MySeAndroidDemo 添加到镜像中去,也就是需要拓展变量PRODUCT_PACKAGES:

makefile 复制代码
# 将 MySeAndroidDemo模块打包进系统镜像
PRODUCT_PACKAGES += \
MySeAndroidDemo

到了这里,如果我们对我们的镜像进行整体编,然后使用adb命令进入/vendor/bin目录下执行MySeAndroidDemo, 我们会看到成功的打印,当然在执行MySeAndroidDemo之前我们需要手动创建一下我们的节点文件:

bash 复制代码
# 进入adb shell命令后创建我们的节点文件
touch /dev/MySeAndroidDemo

那么为什么?是不是说明我们的SEAndroid失效了呢?明明没有配置权限,居然成功了,这SEAndroid不就是一个摆设吗?

其实这也是SEAndroid起了作用的,我们一步一步来看看怎么起了作用的呢?

先看看我们的主体,从 adb shell 执行的进程,从 adbd fork 出来,天生继承 shell 域:

bash 复制代码
adb shell id -Z
# u:r:shell:s0 ...      ← 这就是你的进程的身份
# 如果已经root了那么将是
u:r:su:s0

然后再看看我们的客体,也就是我们需要读写的节点文件。

因为我们再/dev下没有专属标签规则的节点,所以它继承父目录的类型。 AOSP 的 file_contexts 里 /dev 目录是 u:object_r:device:s0,所以:

bash 复制代码
adb shell ls -Z /dev/MySeAndroidDemo
# u:object_r:device:s0   ← 类型是通用的 device,不是你的专属类型

翻 AOSP 策略源码(system/sepolicy/private/shell.te),能看到成组的调试放行规则:

scss 复制代码
# userdebug_or_eng() 包住的块:只在 userdebug / eng 编译产物中生效
userdebug_or_eng(`
    allow shell device:chr_file rw_file_perms;
    ...
')

所以我们直接使用adb命令去运行我们的可执行程序是能写入成功,SEAndroid也生效了。

那么我们需要怎么通过adb验证我们自定义的SEAndroid也生效了呢?

那么我们就要进行下一步啦,定义我们自己的策略文件。

定制策略文件

AOSP 8.0 之后厂商策略有严格分区:system/sepolicy 里的规则不能引用 vendor 类型,反之亦然。 厂商自定义的东西应放在 device///sepolicy/ 下。

在这里我们统一放在我们在MySeAndroidDemo再建一个sepolicy,里面有三个文件:

css 复制代码
├── Android.bp
├── main.cpp
└── sepolicy
    ├── file_contexts
    ├── mydevnode.te
    └── myseandroiddemo.te

① 定义新类型

这主要就是mydevnode.te文件干的事情,当然这个文件名不一定是这个:

arduino 复制代码
# myseandroiddemo_dev_t 它是一个节点
type myseandroiddemo_dev_t, dev_type;

② 给文件路径打标签

这里就是file_contexts文件干的事情,这里必须注意的是这个文件名必须是file_contexts

bash 复制代码
# 你的程序绝对路径
/vendor/bin/MySeAndroidDemo      u:object_r:myseandroiddemo_dt_exec:s0
# 资源文件   这里用到的myseandroiddemo_dev_t要和上面的新类型对应上
/dev/MySeAndroidDemo   u:object_r:myseandroiddemo_dev_t:s0

③ 补 allow 规则

这里我起的文件名是myseandroiddemo.te,这个文件名字也没有强制要求:

ini 复制代码
# 进程对应的类型
type myseandroiddemo_dt, domain;
# myseandroiddemo_dt_exec就是file_contexts用的那个
type myseandroiddemo_dt_exec, exec_type, vendor_file_type, file_type;
#表示该程序如果从 init 进程启动 myseandroiddemo_dt_exec,其安全上下文的 domain 部分从 init 转化为 myseandroiddemo_dt
init_daemon_domain(myseandroiddemo_dt);
#从 shell 启动 type 为 myseandroiddemo_dt_exec 的可执行程序,其对应进程的 domain 为 myseandroiddemo_dt
domain_auto_trans(shell, myseandroiddemo_dt_exec, myseandroiddemo_dt);

补充完这三个策略文件后,我们就可以把我们的sepolicy文件加加入到我们的产品配置mk文件中去参与编译了, 也就是加入到BoardConfig.mk中去

bash 复制代码
# 将SEAndroid文件纳入编译
BOARD_VENDOR_SEPOLICY_DIRS += device/kunpeng/IFly18/MySeAndroidDemo/sepolicy

然后就是老套路,编译我们的产品,启动模拟器验证;

bash 复制代码
source build/envsetup.sh
# 注意这里一定要用userdebug版本,不要用eng
lunch IFly18-trunk_staging-userdebug
make -j16
emulator

读懂avc: denied日志

上面说到adb执行我们的测试可执行程序时,总是有权限,那么我们应该怎么验证呢?

首先我们通过ls -Z命令来查看我们的资源标签对不对先。

这个我们上面已经看过了,肯定不对啊,为什么?

既然已经过了开机时间了,我们需要用restorecon命令刷新一下我们的权限标签。

bash 复制代码
restorecon /dev/MySeAndroidDemo

restorecon /vendor/bin/MySeAndroidDemo

此时我们再用ls -Z命令查看就能看到我自定义的标签啦:

bash 复制代码
ls -Z /vendor/bin/MySeAndroidDemo                                   
u:object_r:myseandroiddemo_dt_exec:s0 /vendor/bin/MySeAndroidDemo

ls -Z /dev/MySeAndroidDemo                                          
u:object_r:myseandroiddemo_dev_t:s0 /dev/MySeAndroidDemo

在这里我们不要直接使用adb命令去运行我们的可执行程序去验证SEAndroid是否生效,我们需要使用runcon命令 让我们的进程运行在特定的上下文中去进行验证,例如这里我们使用:

bash 复制代码
adb shell runcon u:r:myseandroiddemo_dt:s0 /vendor/bin/MySeAndroidDemo

在运行验证命令之前,我们先把我们的logcat开起来,方便我们抓取日志:

perl 复制代码
logcat | grep "avc" | grep "demo" 

我们看看我们的logcat抓到了什么:

arduino 复制代码
10-26 01:33:26.840  3142  3142 W MySeAndroidDemo: type=1400 audit(0.0:529): avc:  denied  { read write } for  name="MySeAndroidDemo" dev="tmpfs" ino=1170 scontext=u:r:myseandroiddemo_dt:s0 tcontext=u:object_r:myseandroiddemo_dev_t:s0 tclass=file permissive=0

翻译成人话就是运行在 myseandroiddemo_dt 域里的进程,想读写贴着 myseandroiddemo_dev_t 标签的对象,策略里没有一条 allow 允许这个组合,所以拒绝。

字段 值 含义
时间/进程 10-26 01:33:26.840 3142 3142 进程 PID 3142,进程名 MySeAndroidDemo
拒绝的操作 { read write } 对目标对象的读和写被拒
对象名 name="MySeAndroidDemo" 被访问的文件叫 MySeAndroidDemo
设备 dev="tmpfs" 在 tmpfs 上------/dev 就是 tmpfs/devtmpfs
inode ino=1170 文件 inode 号
源上下文(谁发起) scontext=u:r:myseandroiddemo_dt:s0 进程运行在自定义域 myseandroiddemo_dt 里
目标上下文(被访问的) tcontext=u:object_r:myseandroiddemo_dev_t:s0 目标是你定义的 /dev/MySeAndroidDemo 类型
资源类别 tclass=file 目标是一个普通文件,不是字符设备节点
模式 permissive=0 Enforcing 模式,真实拒绝,不是宽松模式

借助audit2allow工具

找到我们的audit2allow

swift 复制代码
屏蔽以下4个语句 :
vim /usr/bin/audit2allow  //打开文件,屏蔽以下几行
             self.__parse_options()                                                                                                                                       |~                             
             #if self.__options.policy:                                                                                                                                   |~                             
             #    audit2why.init(self.__options.policy)                                                                                                                   |~                             
             #else:                                                                                                                                                       |~                             
             #    audit2why.init() 

我们把上面logcat抓到的avc报错复制下来,在源码目录下,保存到 avc_log.txt 文件中,并执行一下命令:

bash 复制代码
python3 external/selinux/python/audit2allow/audit2allow -i avc_log.txt

看到输出:

arduino 复制代码
python3 external/selinux/python/audit2allow/audit2allow -i avc_log.txt


#============= myseandroiddemo_dt ==============
allow myseandroiddemo_dt devpts:chr_file { open read write };
allow myseandroiddemo_dt myseandroiddemo_dev_t:file { open read write };

我们把相关的输出拷贝到我们的myseandroiddemo.te文件中去。

有时候我们只是改了te文件,然后重新编译,系统并不会重新对我们的的te文件进行重新编译, 此时我们可以手动刷新一下进行编译:

bash 复制代码
touch device/kunpeng/IFly18/MySeAndroidDemo/sepolicy/myseandroiddemo.te
make -j16

再次编译,通过runcon命令验证即可。

重新验证后如果我们的adb没有抓到avc相关的日志,一般来说就是我们的程序写入成功啦,此时我们可以用cat命令看看目标文件内容, 是不是就是我们程序中写的内容。。。

一般来说audit2allow这个工具只适合学习参考,直接采用它的输出是反模式:它会把"你的服务被攻破后能干什么"授权得和日志一样宽。 也就是说使用这个工具,会使得权限可能超出可控范围,因此我们应该会用这个工具,但不能全部采纳它的答案。

正确流程是:用 audit2allow 得到草案 → 人工收窄(能限制到具体类型就不要用宽泛类型,能少给一个权限就少给)→ 补注释说明业务原因。

调试常见坑与解法(血泪浓缩)

注意,这些坑并不是说就在这个例子遇到,毕竟这个例子只是冰山一角, 刚好笔者就把一些常见的坑列举出来,仅此而已。

坑 1:日志里没有 avc,但功能还是不工作

可能原因与排查顺序:

  • 日志被 dmesg 吞了:avc 消息一部分走内核 ring buffer,用 adb shell dmesg | grep -i avc 再查一遍,别只看 logcat。

  • 问题根本不在 SELinux:先 getenforce 确认模式;再临时把全局切 permissive(setenforce 0,root 可用的 debug 版设备)看功能是否恢复。 恢复了 → 确实是 SELinux 问题,回头按 avc 修策略;没恢复 → 别在 sepolicy 上浪费时间,查别处。

坑 2:改了 file_contexts,但 ls -Z 标签没变

一般来说会有以下两个原因:

1、文件已有旧标签(扩展属性持久化):restorecon 命令进行强制重打。

2、改的是 system 分区策略,但文件在 vendor 分区管辖:确认你的 file_contexts 放对了文件(vendor 路径用 vendor_file_contexts)。

相关推荐
.YM.Z1 小时前
优选算法——【双指针算法2】C++实现
开发语言·c++·算法
思想觉悟1 小时前
Aosp开发磨刀石之ADB速查
android·c++
小此方1 小时前
C++ 工程组件与基础设施(三)详解RPC服务的概念与安装,图片介绍Brpc服务器和客户端的搭建过程
服务器·c++·rpc
前进吧-程序员1 小时前
C++20 协程实战:手写 Task、单线程调度器与 awaiter 的三个钩子
linux
qq_406981032 小时前
什么是顶层 const什么是底层const
开发语言·c++
Android系统攻城狮2 小时前
Linux FFmpeg深度解析之avcodec_alloc_context3调用流程与实战(十四)
linux·运维·ffmpeg·车载音视频·音视频进阶·ffmpeg音视频进阶
自由彩虹2 小时前
靶场通关15--bossplayersctf
linux·服务器·网络·安全·web安全·网络安全
叼烟扛炮2 小时前
Linux 学习笔记(四十五):Web 安全基石——Cookie、Session 与 HTTPS 原理解析
linux·前端·网络·笔记·学习·tcp/ip·安全
zwc32104320552 小时前
第四周第五天——重构成类
c++·人工智能·opencv·算法·计算机视觉