php socket selinux,【SEAndroid】适配 netlink_xxx_socket 的 SELinux 权限

版权声明:本文为 gfson 原创文章,转载请注明出处。

注:作者水平有限,文中如有不恰当之处,请予以指正,万分感谢。

一. 概述

1.1 Linux 的 netlink 机制

Linux 的 netlink 机制是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 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 来创建 netlink socket,我们从这个函数开始分析。

netlink_kernel_create [ kernel\include\linux\Netlink.h ]

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 ]

static __init int selinux_init(void)

{

...

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;

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 };

# 四. 注意事项

## 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 ],其中内容如下:

...

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 的权限。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值