您所在的位置: 首页>>读书频道>>操作系统>>Linux>>

3.5.6 netlink机制获取内核SELinux实时消息

http://book.51cto.com  2007-12-28 12:44  倪继利 著  电子工业出版社博文视点  我要评论(0)
  • 摘要:《Linux安全体系分析与编程》第三章分析了SELinux的安全机制,介绍了安全策略配置语言、内核策略库的结构,简述了SELinux内核模块的实现,还分析了用户空间的客体管理器。本文主要介绍的是netlink机制获取内核SELinux实时消息。
  • 标签:Linux  AVC  netlink  SELinux  Linux安全体系分析与编程

3.5.6  netlink机制获取内核SELinux实时消息

内核SELinux将策略库装载、策略类型变化等事件从内核通过netlink机制实时地发送给客体管理器,让客体管理器即时更新AVC。

netlink机制提供了内核态与用户空间的无阻塞交互通信方法,netlink机制是一种socket实时通信方式,协议簇为netlink机制协议簇NETLINK_SELINUX,使用netlink机制的方法与socket使用方法一样。在用户空间,应用程序调用C库的socket库函数创建和绑定socket,然后发送和接收消息。在内核空间,内核线程使用netlink机制的内核接口函数netlink_unicast和skb_dequeue分别发送和接收消息。

1.内核SELinux的netlink机制实现

内核SELinux使用netlink机制将实时消息SELNL_MSG_POLICYLOAD和SELNL_MSG_ SETENFORCE发送到用户空间。

消息SELNL_MSG_POLICYLOAD表示内核装载策略,由消息的参数区别引起装载策略的原因,如:用户装载策略库,或者用户改变boolean设置引起内核策略库变化等。发送消息SELNL_MSG_ POLICYLOAD的函数是selnl_notify_policyload(u32 seqno)。消息对应的结构列出如下:

struct selnl_msg_policyload {
   u_int32_tseqno;
};

消息SELNL_MSG_SETENFORCE表示策略状态改变消息,如:用户通过写selinuxfs文件系统的enforce文件来使用强制策略失效。发送消息SELNL_MSG_SETENFORCE的函数是selnl_notify_ setenforce(int val),参数val表示策略状态消息值。消息对应的结构列出如下:

struct selnl_msg_setenforce {
    int32_tval;
};

下面分析内核SELinux的netlink机制实现函数。

1)创建netlink机制的socket

函数selnl_init创建并初始化netlink机制的socket,设置消息的起始编号。其列出如下(在linux26/securety/selinux/netlink.c中):

static int __init selnl_init(void)
{
    //创建并初始化netlink类型的套接字selnl,NETLINK_SELINU为netlink的协议簇宏
   selnl = netlink_kernel_create(NETLINK_SELINUX, SELNLGRP_MAX, NULL,
                              THIS_MODULE);
  if (selnl == NULL)
panic("SELinux:  Cannot create netlink socket.");
        //设置消息标识的起始编号,即nl_table[NETLINK_SELINUX].nl_nonroot = NL_NONROOT_RECV
  netlink_set_nonroot(NETLINK_SELINUX, NL_NONROOT_RECV);
  return 0;
}

__initcall(selnl_init);

2)消息发送函数

发送消息函数selnl_notify_setenforce和selnl_notify_policyload分别列出如下:

void selnl_notify_setenforce(int val)
{
    selnl_notify(SELNL_MSG_SETENFORCE, &val);
}

void selnl_notify_policyload(u32 seqno)
{
    selnl_notify(SELNL_MSG_POLICYLOAD, &seqno);
}

内核SELinux模块的netlink机制消息都通过函数selnl_notify发送,函数selnl_notify将消息放到socket的消息缓冲区中,然后,调用netlink机制的消息广播函数netlink_broadcast将消息发送出去。

函数netlink_broadcast列出如下:

static void selnl_notify(int msgtype, void *data)
{
  int len;
  unsigned char *tmp;
  struct sk_buff *skb;
  struct nlmsghdr *nlh;

  len = selnl_msglen(msgtype);//得到消息结构的长度

  skb = alloc_skb(NLMSG_SPACE(len), GFP_USER); //在socket中分配消息的缓存区
  if (!skb)
goto oom;

  tmp = skb->tail;
  nlh = NLMSG_PUT(skb, 0, 0, msgtype, len); //放上消息
  selnl_add_payload(nlh, len, msgtype, data); //加上消息数据
  nlh->nlmsg_len = skb->tail - tmp;
  NETLINK_CB(skb).dst_group = SELNLGRP_AVC;
  netlink_broadcast(selnl, skb, 0, SELNLGRP_AVC, GFP_USER); //广播消息
out:
  return;

nlmsg_failure:
  kfree_skb(skb); //释放消息释放区
oom:
  printk(KERN_ERR "SELinux:  OOM in %s\n", __FUNCTION__);
  goto out;
}

函数selnl_add_payload根据消息类型的不同,将消息数据加载到消息结构上。其列出如下:

static void selnl_add_payload(struct nlmsghdr *nlh, int len, int msgtype, void *data)
{
switch (msgtype) {
case SELNL_MSG_SETENFORCE: {
// NLMSG_DATA(nlh)从socket的消息缓冲区得到消息存放的起始地址
struct selnl_msg_setenforce *msg = NLMSG_DATA(nlh);

memset(msg, 0, len);
msg->val = *((int *)data); //放上消息数据
break;
}

case SELNL_MSG_POLICYLOAD: {
struct selnl_msg_policyload *msg = NLMSG_DATA(nlh);

memset(msg, 0, len);
msg->seqno = *((u32 *)data);
break;
}

default:
BUG();
}
}

2.用户空间的netlinux机制实现

用户空间的客体管理器通过一个线程完成对内核SELinux发送的消息进行接收和处理。客体管理器初始化函数avc_init调用函数avc_netlink_open创建socket;调用用户定义的线程创建函数创建一个线程,由线程工作任务函数avc_netlink_loop使用一个死循环接收并处理内核SELinux发送的消息。

下面对这两个函数分别进行说明。

1)函数avc_netlink_open

函数avc_netlink_open创建并绑定netlink机制的socket,它与一般socket的使用方法一样。函数avc_netlink_open列出如下:

static int fd;  //static表示fd这个全局变量仅在本文件中使用,以避免与其他全局变量重名
int avc_netlink_open(int blocking)
{
int len, rc = 0;
struct sockaddr_nl addr;

fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_SELINUX);
if (fd < 0) {
rc = fd;
goto out;
}

if (!blocking && fcntl(fd, F_SETFL, O_NONBLOCK)) {
close(fd);
rc = -1;
goto out;
}

len = sizeof(addr);

memset(&addr, 0, len);
addr.nl_family = AF_NETLINK;
addr.nl_groups = SELNL_GRP_AVC;

if (bind(fd, (struct sockaddr *)&addr, len) < 0) {
close(fd);
rc = -1;
goto out;
}
      out:
return rc;
}

2)函数avc_netlink_loop

函数avc_netlink_loop是一个线程的工作任务函数,它通过一个死循环不断地从netlink socket接收消息,完成对内核SELinux的消息侦听工作。程序运行的异常信息由函数avc_log打印,函数avc_log可以使用用户定义的函数或者libselinux库定义的函数打印信息。

函数avc_netlink_loop列出如下(在libselinux/src/avc_internal.c中):

void avc_netlink_loop(void)
{
int ret;
struct sockaddr_nl nladdr;
socklen_t nladdrlen = sizeof nladdr;
char buf[1024];
struct nlmsghdr *nlh;

while (1) {
ret = recvfrom(fd, buf, sizeof(buf), 0,
     (struct sockaddr *)&nladdr, &nladdrlen);
if (ret < 0) {
if (errno == EINTR)
continue;
avc_log("%s:  netlink thread: recvfrom: error %d\n",
avc_prefix, errno);
goto out;
}

if (nladdrlen != sizeof nladdr) {
avc_log
    ("%s:  warning: netlink address truncated, len %d?\n",
     avc_prefix, nladdrlen);
ret = -1;
goto out;
}

if (nladdr.nl_pid) {
avc_log
    ("%s:  warning: received spoofed netlink packet from: %d\n",
     avc_prefix, nladdr.nl_pid);
continue;
}

if (ret == 0) {
avc_log("%s:  netlink thread: received EOF on socket\n",
avc_prefix);
goto out;
}

nlh = (struct nlmsghdr *)buf;

if (nlh->nlmsg_flags & MSG_TRUNC              //socket消息异常截断
    || nlh->nlmsg_len > (unsigned)ret) {
avc_log
    ("%s:  netlink thread: incomplete netlink message\n",
     avc_prefix);
goto out;
}

switch (nlh->nlmsg_type) {
case NLMSG_ERROR:{  //netlink出错信息
struct nlmsgerr *err = NLMSG_DATA(nlh);

/* 这是netlink应答信息,而不是错误*/
if (err->error == 0)
break;

avc_log("%s:  netlink thread: msg: error %d\n",
avc_prefix, -err->error);
goto out;
}

                //这是selinux自定义的消息类型,值必须在netlink定义的消息类型之后
case SELNL_MSG_SETENFORCE:{   //内核SELinux设置了策略模式
struct selnl_msg_setenforce *msg =
    NLMSG_DATA(nlh);
avc_log
    ("%s:  received setenforce notice (enforcing=%d)\n",
     avc_prefix, msg->val);
avc_enforcing = msg->val;
break;
}

case SELNL_MSG_POLICYLOAD:{  //内核SELinux装载了新策略
struct selnl_msg_policyload *msg =
    NLMSG_DATA(nlh);
avc_log
    ("%s:  received policyload notice (seqno=%d)\n",
     avc_prefix, msg->seqno);
                                //AVC复位,并设置AVC中的策略序列号,表示是新的策略
ret = avc_ss_reset(msg->seqno);
if (ret < 0) {
     avc_log(
                            "%s: netlink thread: cache reset returned %d (errno %d)\n",
     avc_prefix, ret, errno);
goto out;
}
break;
}

default:
avc_log(
    "%s:  netlink thread: warning: unknown msg type %d\n",
     avc_prefix, nlh->nlmsg_type);
}
}
      out:
close(fd);
avc_netlink_trouble = 1;
avc_log("%s:  netlink thread: errors encountered, terminating\n",
avc_prefix);
}

打印异常信息的函数avc_log来自用户定义的avc_func_log函数,如果用户未定义,使用库的打印函数。avc_log列出如下:

/* this is a macro in order to use the variadic capability. */
#define avc_log(format...) \
  if (avc_func_log) \
    avc_func_log(format); \
  else \
    fprintf(stderr, format)

【责任编辑:董书 TEL:(010)68476606】

回书目   上一节   下一节
Linux——从菜鸟到高手
Linux/Solaris服务器的安全配置
Linux 集群技术专题
Linux中文环境
Linux防火墙
 
 验证码: (点击刷新验证码)   匿名发表
  • 野蛮生长

  • 作者:冯仑著
  • “地产界的思想家”冯仑纵横生意江湖20年来,第一次系统梳理出书。  三十年来中国民营企业从前公司时代发展到公司时代,21..
Copyright©2005-2008 51CTO.COM 版权所有