【SEAndroid】适配 netlink_xxx_socket 的 SELinux 权限
版权声明:本文为
gfson
原创文章,转载请注明出处。
注:作者水平有限,文中如有不恰当之处,请予以指正,万分感谢。一. 概述
1.1 Linux 的 netlink 机制
Linux 的 netlink 机制是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。
详细内容可以参考如下博客: Linux 的 netlink 机制 1.2 实现背景
用户空间有一个应用 scpd 通过自定义协议 NETLINK_SCPD 创建 netlink socket,相关代码如下: #define NETLINK_SCPD 28 /* scpd communition*/ nlfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SCPD);内核中使用对应的协议号 28 创建 netlink socket,相关代码如下: #define NETLINK_SCPD 28 nl_sk = netlink_kernel_create(&init_net, NETLINK_SCPD, &cfg);使用了相同协议号的用户程序 scpd 和内核就可以开始通信了,这个时候只需要配置如下 SELinux 权限即可: type scpd, domain; type scpd_exec, exec_type, file_type; init_daemon_domain(scpd) allow scpd self:capability { dac_override }; allow scpd serial_device:chr_file open; allow scpd serial_device:chr_file read; allow scpd serial_device:chr_file write; allow scpd serial_device:chr_file ioctl; allow scpd device:dir write; allow scpd device:dir add_name; allow scpd device:sock_file create; allow scpd device:sock_file setattr;
可以看到,上述的 SELinux 权限并没有对特定的协议号有限制,换而言之,任何一个程序只要能够访问 socket,有上述的 SELinux 权限,即可通过协议号 28 来访问内核中特定的内容。 了解上述的背景后,我们的目的是通过 SELinux 限制程序对协议号 28 的访问,既: 只允许配置了特定 SELinux 权限的程序可以通过协议号 28 访问内核,其他程序就算可以访问 socket,如果该程序没有针对协议号 28 配置 SELinux 权限,那么这个程序就不能使用这个协议号 28 访问内核。
二. SELinux 对 netlink 的扩展
为了解决上述问题,我们先来研究一下 kernel 中 netlink 的实现,看看其中有没有 socket 对 netlink socket 的 SElinux 扩展。内核中通过
netlink_kernel_create [ kernel\include\linux\Netlink.h ]netlink_kernel_create
来创建 netlink socket,我们从这个函数开始分析。static inline struct sock * netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg) return __netlink_kernel_create(net, unit, THIS_MODULE, cfg); __netlink_kernel_create [ kernel\net\netlink\Af_netlink.c ]
struct sock * __netlink_kernel_create(struct net *net, int unit, struct module *module, struct netlink_kernel_cfg *cfg) struct socket *sock; if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock)) return NULL; sock_create_lite [ kernel\net\Socket.c ]
int sock_create_lite(int family, int type, int protocol, struct socket **res) int err; struct socket *sock = NULL; err = security_socket_create(family, type, protocol, 1); security_socket_create [ kernel\security\Security.c ]
int security_socket_create(int family, int type, int protocol, int kern) return security_ops->socket_create(family, type, protocol, kern); security_ops 的初始化 selinux_init [ kernel\security\selinux\Hooks.c ] if (register_security(&selinux_ops))
panic("SELinux: Unable to register with kernel.\n");
- **register_security** [ kernel\security\Security.c ]
int __init register_security(struct security_operations *ops)
security_ops = ops;
- **selinux_ops** [ kernel\security\selinux\Hooks.c ]
static struct security_operations selinux_ops = {
.socket_create = selinux_socket_create,
- 所以,`security_ops = selinux_ops`,`security_ops->socket_create = selinux_ops->socket_create = selinux_socket_create`。 - **selinux_socket_create** [ kernel\security\selinux\Hooks.c ]
static int selinux_socket_create(int family, int type,
int protocol, int kern)
const struct task_security_struct *tsec = current_security();
u32 newsid;
u16 secclass;
int rc;if (kern) return 0; secclass = socket_type_to_security_class(family, type, protocol); rc = socket_sockcreate_sid(tsec, secclass, &newsid); if (rc) return rc; return avc_has_perm(tsec->sid, newsid, secclass, SOCKET__CREATE, NULL); - **socket_type_to_security_class** [ kernel\security\selinux\Hooks.c ]
static inline u16 socket_type_to_security_class(int family, int type, int protocol)
switch (family) {
case PF_NETLINK:
switch (protocol) {
case NETLINK_ROUTE:
return SECCLASS_NETLINK_ROUTE_SOCKET;
case NETLINK_FIREWALL:
return SECCLASS_NETLINK_FIREWALL_SOCKET;
case NETLINK_SOCK_DIAG:
return SECCLASS_NETLINK_TCPDIAG_SOCKET;
case NETLINK_NFLOG:
return SECCLASS_NETLINK_NFLOG_SOCKET;
case NETLINK_XFRM:
return SECCLASS_NETLINK_XFRM_SOCKET;
case NETLINK_SELINUX:
return SECCLASS_NETLINK_SELINUX_SOCKET;
case NETLINK_AUDIT:
return SECCLASS_NETLINK_AUDIT_SOCKET;
case NETLINK_IP6_FW:
return SECCLASS_NETLINK_IP6FW_SOCKET;
case NETLINK_DNRTMSG:
return SECCLASS_NETLINK_DNRT_SOCKET;
case NETLINK_KOBJECT_UEVENT:
return SECCLASS_NETLINK_KOBJECT_UEVENT_SOCKET;
default:
return SECCLASS_NETLINK_SOCKET;
return SECCLASS_SOCKET;
- 从以上代码中,我们可以看出,**在源码中对 netlink socket 根据协议号是有相对应的 security class 的**。 - Linux 中已经实现了对 netlink socket 的源码扩展和 security class 类的扩展。每一个协议号 NETLINK_XXX 对应于一个 SECCLASS_NETLINK_XXX_SOCKET,如果没有为某一个协议号定义对应的 security class,则默认使用 SECCLASS_NETLINK_SOCKET。 - 所以,**正是由于其默认使用的是 SECCLASS_NETLINK_SOCKET,如果一个协议号没有定义对应的 security class,则其他可以访问 SECCLASS_NETLINK_SOCKET 的应用程序便都可以访问这个协议号**。为了安全考虑,保证特定的程序才可以访问这个协议号,需要对源码进行修改。 - 接下来,我们需要做的事情分为两步: - 自定义协议号 28 的 security class。 - 为 scpd 程序配置访问协议号 28 的 SELinux 权限。 # 三. 适配协议号 28 的 SELinux 权限 我们定义协议号 28 的名称为 NETLINK_SCPD。 ## 3.1 在 SELinux 的配置文件中定义协议号的 security class。 - 在文件 **security_classes** [ external/sepolicy ] 中定义类名:
class netlink_scpd_socket
- 在文件 **access_vectors** [ external/sepolicy ] 中定义操作类别:
class netlink_scpd_socket
inherits socket
nlmsg_read
nlmsg_write
## 3.2 在 kernel 中实现对 NETLINK_SCPD 的扩展 - 在文件 **netlink.h** [ kernel/include/uapi/linux ] 中定义协议号:define NETLINK_SCPD 28 /* scpd communition*/
- 在文件 **Hooks.c** [ kernel\security\selinux\Hooks.c ] 中对协议号自定义 security class。
static inline u16 socket_type_to_security_class(int family, int type, int protocol)
switch (family) {
case PF_NETLINK:
switch (protocol) {
case NETLINK_KOBJECT_UEVENT:
return SECCLASS_NETLINK_KOBJECT_UEVENT_SOCKET;
case NETLINK_SCPD:
return SECCLASS_NETLINK_SCPD_SOCKET;
default:
return SECCLASS_NETLINK_SOCKET;
return SECCLASS_SOCKET;
- 在文件 **classmap.h** [ kernel\security\selinux\include ] 中根据 `netlink_scpd_socket` 生成 `SECCLASS_NETLINK_SCPD_SOCKET`。
struct security_class_mapping secclass_map[] = {
{ "netlink_ip6fw_socket",
{ COMMON_SOCK_PERMS,
"nlmsg_read", "nlmsg_write", NULL } },
{ "netlink_scpd_socket",
{ COMMON_SOCK_PERMS,
"nlmsg_read", "nlmsg_write", NULL } },
## 3.3 配置访问 netlink_scpd_socket 的 SELinux 权限 - 在文件 **scpd.te** [ device/qcom/sepolicy/common ] 中配置 scpd 对 netlink_scpd_socket 的权限。type scpd, domain;
# 四. 注意事项 ## 4.1 SECCLASS_NETLINK_SCPD_SOCKET 的生成原理 文件 **classmap.h** 中定义了 `netlink_scpd_socket` 以后,文件 **genheaders.c** [ kernel/scripts/selinux/genheaders ] 中会根据 **classmap.h** 中的内容动态生成文件 **flask.h** [ out\target\product\msm8909\obj\KERNEL_OBJ\security\selinux ],其中内容如下:
type scpd_exec, exec_type, file_type;
init_daemon_domain(scpd)
allow scpd self:capability { dac_override };
allow scpd serial_device:chr_file open;
allow scpd serial_device:chr_file read;
allow scpd serial_device:chr_file write;
allow scpd serial_device:chr_file ioctl;
allow scpd device:dir write;
allow scpd device:dir add_name;
allow scpd device:sock_file create;
allow scpd device:sock_file setattr;
allow scpd device:dir remove_name;
allow scpd device:sock_file unlink;
allow scpd self:netlink_scpd_socket { create write bind read };define SECCLASS_NETLINK_SCPD_SOCKET 38
并且,Hooks.c 中 `#include "avc.h"`,而 avc.h 中 `#include "flask.h"`,所以 Hooks.c 中可以正常引用 `SECCLASS_NETLINK_SCPD_SOCKET`。 ## 4.2 定义 security class 类名要相同 在 security_classes、access_vectors、Hooks.c、classmap.h 中定义的类名要相同。 - security_classes、access_vectors和 classmap.h 中的类名一定要相同。 - classmap.h 中的类名 xxx 部分和 Hooks.c 中的 SECCLASS_XXX 部分相同。 ## 4.3 eng 或 userdebug 版本测试时出现的问题 在 **eng** 或 **userdebug** 版本测试的时候,发现: - **正常现象**:如果没有为 scpd 配置 netlink_scpd_socket 相关权限,通过 `start scpd` 启动这个程序时,可以出现 avc denied 的限制,netlink_scpd_socket 无法正常访问。只有当配置了相应权限以后,`start scpd` 才可以正常访问 netlink_scpd_socket。 - **奇怪现象**:不通过 `start scpd` 启动程序,而且直接在 shell 中运行 scpd,发现即使没有给 scpd 配置相应的 SELinux 权限,也可以正常的访问 netlink_scpd_socket。 - 通过 `start scpd` 启动时,其实是通过 init 进程启动,和开机 init 进程启动 scpd 是一样的,由于配置了 `init_daemon_domain(scpd)`,所以进程域会由 init 转换成 scpd,所以进程 scpd 的 scontext 为 `u:r:scpd:s0`。这个时候,配置的 SELinux 规则对这个进程起作用,会执行 mac 检查。 - 通过在 shell 中直接运行,我们可以发现,进程 scpd 的 scontext 为 `u:r:su:s0`,为了解释这个问题,我们看一下文件 **su.te** [ extern/sepolicy ] 的内容:
type su_exec, exec_type, file_type;
userdebug_or_eng(`
type su, domain;
domain_auto_trans(shell, su_exec, su)
permissive su;
上述的配置文件包含几个意思: - **上述 `domain_auto_trans(shell, su_exec, su)` 的意思是在 userdebug 版本或者 eng 版本,在 shell 中执行 su 时,会自动转化成 su 域**。我们可以回想一下,在 eng 版本中,默认就有 root 权限,这个是因为 adb shell 中已经自动执行了 su。而 userdebug 版本需要执行 `adb root` 或者在 shell 中执行 su,就会有 root 权限。而执行完 su 后,就从 shell 域切换到了 su 域了。 - **上述 `permissive su` 的意思,是不对 su 域的行为进行任何的 mac 检查,即不受 SELinux 的规则约束**。换而言之,su 域既有 root 权限,而且不受 SELinux 的规则约束,那么 su 可以做任何事情。所以,上述内容只在 userdebug 和 eng 编译进去,user 版本的 su 权限还是受到了很大限制的。 - 所以,这就是为什么 shell 中直接运行 scpd 没有权限,而必须切换到 su 后,才可以运行 scpd,同理,由于在 su 下运行的程序都是 su 域,故这种情况下,scpd 可以不受 SELinux 的规则约束。 - 这种情况对 user 版本没有影响,因为 user 版本没有 su,就算通过非法手段放进去了 su,但是执行 su 的时候,会受到 SELinux 很大的约束,最大程度的限制了 user 版本下 su 的权限。