美文网首页
【SEAndroid】适配 netlink_xxx_socket

【SEAndroid】适配 netlink_xxx_socket

作者: gfson | 来源:发表于2017-06-20 14:30 被阅读0次

    版权声明:本文为 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,我们从这个函数开始分析。

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

    相关文章

      网友评论

          本文标题:【SEAndroid】适配 netlink_xxx_socket

          本文链接:https://www.haomeiwen.com/subject/kthoqxtx.html