Linux内核安全模块深入剖析【1.7】
3.套接字socketUNIX 的哲学是“万物皆文件”。但是起源于 BSD 的套接字打破了这个哲学。等到 UNIX的捍卫者想要将套接字统一进文件时为时已晚㊀ 程序员已经熟悉了套接字改不回来了。从安全的角度考虑 UNIX 上原生的套接字确实有些问题。 UNIX 在套接字上几乎没有访问控制。作者只能查到两处一处是只允许代表 root 用户的进程绑定bind特权端口即 tcp和 udp 的端口号在 1024 以下的端口另一处是发生在 UNIX 本地域的套接字操作中服务器端bind 操作会创建一个套接字文件在客户端 connect 操作中会判断客户进程对此套接字文件是否有写许可。但是此处的套接字文件只是一种文件并不是套接字。SELinux 虽然弥补了这一缺失却矫枉过正了。它定义的套接字子类别过多各类别上的操作也过细。和文件一样套接字也有很多种它们有一些共有的操作许可其中有一些和文件的一样如 ioctl、 read、 write、 create、 getattr、 setattr、 lock、 relabelfrom、 relabelto、 append还有一些是套接字特有的如 bind、 connect、 listen、 accept、 getopt、 setopt、 shutdown、 recvfrom、sendto、 recv_msg、 send_msg、 name_bind。1套接字与文件共有的操作许可UNIX 的哲学“万物皆文件”并非凭空刻意为之。套接字中有很多和文件相同的操作类型。SELinux 让套接字和文件共享一些操作类型但是做得有些生硬一些对套接字没有意义的操作类型也混了进来。以下是套接字的基本操作● create 创建套接字● ioctl*● lock*以下操作与数据收发相关● read 自套接字读取消息。● write 向套接字发送消息。● append*。以下操作与套接字的地址相关● getattr 读取套接字当前绑定地址 getsockname 或者读取连接端套接字地址getpeername。● setattr*。以下操作与 SELinux 相关● relabelfrom 改变套接字的安全上下文使其不再是现在的值。只在 tun_socket 中用到。● relabelto 改变套接字的安全上下文使其变为新值。2套接字特有操作许可以下操作与连接相关● bind 对套接字执行系统调用 bind● name_bind 绑定套接字到特权端口Linux 系统为网络服务进程定义了一个端口范围绑定范围外端口需要此项操作许可关于端口范围可以查询/proc/sys/{ipv4,ipv6}/ip_local_port_range 文件。● listen 对套接字执行系统调用 listen● accept 对套接字执行系统调用 accept● connect 对套接字执行系统调用 connect以下操作与套接字属性相关● getopt 对套接字执行系统调用 getsockopt● setopt 对套接字执行系统调用 setsockopt以下操作与套接字的数据收发相关● recvfrom 自套接字读取数据包。只被 udp_socket、 tcp_socket、 rawip_socket 使用即仅适用于主机间通信。● sendto 向套接字发送数据包。只被 UNIX 套接字使用即仅适用于同一主机内进程间通信。● recv_msg*● send_msg*以下为其他操作● shutdown 对套接字执行系统调用 shutdown3不同的套接字类型上的特有操作1 tcp_socket在客体类别“tcp_socket”上的操作包含上面列出的所有套接字与文件共有的操作和所有套接字的特有操作此外 tcp_socket 还包含如下操作● connectto*● newconn*● acceptfrom*● node_bind 将套接字绑定到某个网络地址上主机可以有多个网络地址如 127.0.0.1 是回送地址 192.168.1.1 是内部局域网地址。这个操作限定绑定到某个地址之上。● name_connect socket 连接到某个网络端口上赋予端口安全上下文限制对端口的连接操作。2 udp_socket客体类别 udp_socket 的操作既包含所有套接字的共有操作还包含如下操作● node_bind 将 socket 绑定到某个网络地址上3 rawip_socket客体类别 rawip_socket 的特有操作如下 ● node_bind 将 socket 绑定到某个网络地址上4 netlink_socket此套接字类别没有特有操作。5 netlink_route_socket客体类别 netlink_route_socket 的特有操作如下● nlmsg_read 读取信息● nlmsg_write 写入信息6 netlink_firewall_socket客体类别 netlink_firewall_socket 的特有操作如下● nlmsg_read 读取信息● nlmsg_write 写入信息7 netlink_tcpdiag_socket客体类别 netlink_tcpdiag_socket 的特有操作如下● nlmsg_read 读取信息● nlmsg_write 写入信息8 netlink_nflog_socket此套接字类别没有特有操作。9 netlink_xfrm_socket客体类别 netlink_xfrm_socket 的特有操作如下● nlmsg_read 读取信息● nlmsg_write 写入信息10 netlink_selinux_socket此套接字类别没有特有操作。11 netlink_audit_socket客体类别 netlink_audit_socket 的特有操作如下● nlmsg_read 读取信息● nlmsg_write 写入信息● nlmsg_relay 将用户态 audit 消息转发给 audit 服务● nlmsg_readpriv 列出 audit 配置规则● nlmsg_tty_audit 控制 tty 审计信息12 netlink_ip6fw_socket客体类别 netlink_ip6fw_socket 的特有操作如下● nlmsg_read 读取信息● nlmsg_write 写入信息13 netlink_dnrt_socket此套接字类别没有特有操作。14 netlink_kobject_uevent_socket此套接字类别没有特有操作。15 packet_socket此套接字类别没有特有操作。16 key_socket此套接字类别没有特有操作。17 unix_stream_socket客体类别“unix_stream_socket”的特有操作如下● connectto 连接到服务 socket● newconn*● acceptfrom*18 unix_dgream_socket此套接字类别没有特有操作。19 appletalk_socket此套接字类别没有特有操作。20 dccp_socket客体类别 dccp_socket 的特有操作如下● node_bind 将 socket 绑定到某个网络地址上● name_connect 连接 socket 到某个地址21 tun_socket客体类别“tun_socket”的特有操作如下● attach_queue 附加一个新队列SELinux 不厌其烦地在套接字上细分出子类别其实有些过犹不及。4.进程间通信进程间通信 Inter-Process Communication IPC包括信号灯 Semaphore、消息队列Message Queue、共享内存Shared Memory。它们具有一些共同的操作许可1进程间通信的共有操作● create 创建● destroy 删除● getattr 读取属性● setattr 设置属性● associate 获取 IPC 对象 ID在系统调用 semget、 msgget、 shmget 中在获取 IPC 对象前 SELinux 会判断 associate 操作许可。● read 读取消息/内容● write 写入消息/内容● unix_read 读取● unix_write 写入unix_read 和 unix_write 是指传统的 UNIX 中规定的读写操作。它们和打开一个进程间通信对象时给出的操作模式相联系是只读、只写或读写方式。 read 和 write 的功能也是读和写其实与 unix_read 和 unix_write 是有重合的。下面看代码ipc/shm.c long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr, unsigned long shmlba) { … err -EACCES; if (ipcperms(ns, shp-shm_perm, acc_mode)) goto out_unlock; err security_shm_shmat(shp, shmaddr, shmflg); if (err) goto out_unlock; … }上面的代码首先调了进程间通信通用的 ipcperms然后调了共享内存专有的 security_ shm_shmat。先来看 ipcperms。ipc/util.c int ipcperms(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp, short flag) { … return security_ipc_permission(ipcp, flag); } security_ipc_permission 会调用 SELinux 的钩子函数 selinux_ipc_permission。 security/selinux/hooks.c static int selinux_ipc_permission(struct kern_ipc_perm *ipcp, short flag) { u32 av 0; av 0; if (flag S_IRUGO) av | IPC__UNIX_READ; if (flag S_IWUGO) av | IPC__UNIX_WRITE; if (av 0) return 0; return ipc_has_perm(ipcp, av); }总结一下 ipcperms 会间接调用 ipc_has_perm 来判断 unix_read 和 unix_write 操作许可。下面看 security_shm_shmat它会调用 SELinux 的钩子函数 selinux_ shm_shmat:security/selinux/hooks.c static int selinux_shm_shmat(struct shmid_kernel *shp, char __user *shmaddr, int shmflg) { u32 perms; if (shmflg SHM_RDONLY) perms SHM__READ; else perms SHM__READ | SHM__WRITE; return ipc_has_perm(shp-shm_perm, perms); }总结一下 security_shm_shmat 会间接调用 ipc_has_perm 来判断 read 和 write 操作许可。在 SELinux 代码中有些暗含读语义或写语义的系统调用中也会判断 read 和 write 操作例如在系统调用 semctl 中当参数 cmd 是 GETVAL 或 GETALL 时 SELinux 会判断 read操作许可。security/selinux/hooks.c static int selinux_sem_semctl(struct sem_array *sma, int cmd) { int err; u32 perms; switch (cmd) { … case GETVAL: case GETALL: perms SEM__READ; break; case SETVAL: case SETALL: perms SEM__WRITE; break; … default: return 0; } err ipc_has_perm(sma-sem_perm, perms); return err; }2各种进程间通信上的特有操作在信号灯上无特有操作在消息队列上有一个特有操作 enqueue含义是发送消息到消息队列在共享内存上的特有操作是 lock含义是加锁或解锁共享内存。3消息队列中的消息在消息队列的消息本身也带有安全上下文。对消息队列中的消息 SELinux 提供了额外的操作许可。● receive 从队列读取消息● send 向队列写入消息细分析 SELinux 代码你会发现它在有的地方实在是罗嗦。以消息队列为例为了获得消息队列对象应用程序要调用系统调用 msgget SELinux 会根据传入参数 msgflg 检查 unix_read和 unix_write还会判断 associate。然后应用程序调用 msgrcv 或 msgsnd 时 SELinux 会先判断unix_read 和 unix_write然后判断 read 和 write最后还要在消息上判断 receive 和 send。下面看一下代码。ipc/msg.c SYSCALL_DEFINE2(msgget, key_t, key, int, msgflg) { struct ipc_namespace *ns; struct ipc_ops msg_ops; struct ipc_params msg_params; ns current-nsproxy-ipc_ns; msg_ops.getnew newque; msg_ops.associate msg_security; msg_ops.more_checks NULL; msg_params.key key; msg_params.flg msgflg; return ipcget(ns, msg_ids(ns), msg_ops, msg_params); } ipc/util.c int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids, struct ipc_ops *ops, struct ipc_params *params) { if (params-key IPC_PRIVATE) return ipcget_new(ns, ids, ops, params); else return ipcget_public(ns, ids, ops, params); } ipc/util.c static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids, struct ipc_ops *ops, struct ipc_params *params) { … err ipc_check_perms(ns, ipcp, ops, params); … } ipc/util.c static int ipc_check_perms(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp, struct ipc_ops *ops, struct ipc_params *params) { int err; if (ipcperms(ns, ipcp, params-flg)) err -EACCES; else { err ops-associate(ipcp, params-flg); if (!err) err ipcp-id; } return err; }这里的 associate 函数指针指向函数 msg_security。ipc/msg.c static inline int msg_security(struct kern_ipc_perm *ipcp, int msgflg) { struct msg_queue *msq container_of(ipcp, struct msg_queue, q_perm); return security_msg_queue_associate(msq, msgflg); } security_msg_queue_associate 会调用 SELinux 的钩子函数。 security/selinux/hooks.c static int selinux_msg_queue_associate(struct msg_queue *msq, int msqflg) { struct ipc_security_struct *isec; struct common_audit_data ad; u32 sid current_sid(); isec msq-q_perm.security; ad.type LSM_AUDIT_DATA_IPC; ad.u.ipc_id msq-q_perm.key; return avc_has_perm(sid, isec-sid, SECCLASS_MSGQ, MSGQ__ASSOCIATE, ad); } 以发送为例 ipc/msg.c SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, int, msgflg) { long mtype; if (get_user(mtype, msgp-mtype)) return -EFAULT; return do_msgsnd(msqid, mtype, msgp-mtext, msgsz, msgflg); } ipc/msg.c long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg) { … for (;;) { struct msg_sender s; err -EACCES; if (ipcperms(ns, msq-q_perm, S_IWUGO)) goto out_unlock0; … err security_msg_queue_msgsnd(msq, msg, msgflg); if (err) goto out_unlock0; … } … } security_msg_queue_msgsnd 会调用 SELinux 的钩子函数 selinux_msg_queue_msgsnd: security/selinux/hooks.c static int selinux_msg_queue_msgsnd(struct msg_queue *msq, struct msg_msg *msg, int msqflg) { … rc avc_has_perm(sid, isec-sid, SECCLASS_MSGQ, MSGQ__WRITE, ad); if (!rc) /* Can this process send the message */ rc avc_has_perm(sid, msec-sid, SECCLASS_MSG, MSG__SEND, ad); if (!rc) /* Can the message be put in the queue? */ rc avc_has_perm(msec-sid, isec-sid, SECCLASS_MSGQ, MSGQ__ENQUEUE, ad); return rc; }5.网络SELinux 对网络对象的标记比较复杂目前大概有 4 套体系共存通过 SELinux 网络策略标记、通过标签化的 IP 协议头CIPSO标记㊀ 、通过 IPSEC 标记、通过 iptable 的 secmark 标记。其中标签化网络形式又支持在 IP 协议头没有标记的情况下根据环境信息IP 地址、端口号标记网络这又和通过 SELinux 网络策略标记重合。为什么 SELinux 中会有这么多套体系同时作用于网络类的客体呢作者认为原因是SELinux 在如何为网络类客体设计安全上下文上还不成熟。为了便于理解下面只介绍第一个方案。先思考一下网络对象是由什么组成的首先是节点node 节点由 IP 地址限定。 其次 在节点上有一个进程在收发网络包 这被定义为“peer”。然后当网络包到达本地端时它要经过网卡这被定义为网络接口“netif”。最后当我们将控制的粒度细化时对每一个网络包都可以实施不同的访问控制网络包就被定义为“packet”。1网络节点node客体类别“node”上有以下操作。这些操作不是同时加入的。在“recvfrom”和“sendto”两个操作加入内核后其他的操作就不再使用了。● dccp_recv*● dccp_send*● tcp_recv*● tcp_send*● udp_recv*● udp_send*● rawip_recv*● rawip_send*● enforce_dest*● recvfrom 接收来自网络节点的数据包● sendto 发送数据包到网络节点2网络接口netif客体类别“netif”上有以下操作。这些操作不是同时加入的。在“ingress”和“egress”两个操作加入内核后其他的操作就不再使用了。● tcp_recv*● tcp_send*● udp_recv*● udp_send*● rawip_recv*● rawip_send*● dccp_recv*● dccp_send*● ingress 通过网络接口接收数据包● egress 通过网络接口发送数据包3 peer客体类别“peer”的引入简化了 SELinux 的逻辑㊀ 。其上有一个操作● recv 接收消息4 packet客体类别“packet”指网络上传输的数据包。其上有五个操作可分为两类。1基本● send 发送● recv 接收● forward_in 向内转发● forward_out 向外转发2 SELinux 相关● relabelto标记 SELinux 的安全上下文。奇怪的是没有 relabelfrom。