分析了过几个Linux内核的漏洞,都与netlink 机制有关,这篇文章用于介绍linux kernel中的netlink 机制,主要是介绍 用户->内核的通信。

什么是netlink

它是一种用于内核空间与用户空间进行通信的机制,同时也可以用于进程间通信,属于异步全双工通信。支持单播和多播模式。

netlink 消息格式

netlink消息由两部分组成:消息头(16字节)+消息体

netlink 消息头

消息头结构 nlmsghdr

struct nlmsghdr
{
    __u32        nlmsg_len;    /* 消息总长度:消息头+消息体 */
    __u16        nlmsg_type;    /* 消息类型 */
    __u16        nlmsg_flags;    /* 标志 */
    __u32        nlmsg_seq;    /* 消息序号,类似于tcp的确认机制 */
    __u32        nlmsg_pid;    /*  */
};
  • 消息类型:
NLMSG_NOOP-   空消息,什么也不做;
NLMSG_ERROR-  指明该消息中包含一个错误;
NLMSG_DONE-   如果内核通过Netlink队列返回了多个消息,那么队列的最后一条消息的类型为NLMSG_DONE,
              其余所有消息的nlmsg_flags属性都被设置NLM_F_MULTI位有效。
NLMSG_OVERRUN-暂时没用到。
  • 消息标志:
NLM\_F\_REQUEST  : 如果消息中有该标记位,说明这是一个请求消息。所有从用户空间到内核空间的消息都要设置该位,否则内核将向用户返回一个EINVAL无效参数的错误 
NLM\_F\_MULTI : 消息从用户->内核是同步的立刻完成,而从内核->用户则需要排队。如果内核之前收到过来自用户的消息中有NLM_F_DUMP位为1的消息,那么内核就会向用户空间发送一个由多个Netlink消息组成的链表。除了最后个消息外,其余每条消息中都设置了该位有效。 
NLM\_F\_ACK : 该消息是内核对来自用户空间的NLM_F_REQUEST消息的响应 
NLM\_F\_ECHO : 如果从用户空间发给内核的消息中该标记为1,则说明用户的应用进程要求内核将用户发给它的每条消息通过单播的形式再发送给用户进程。和我们通常说的“回显”功能类似。 
  • 消息序号:配合消息标志 NLM_F_ACK类型的消息使用,类似于tcp的确认机制
  • 发送方进程id:建立的“通道编号”。当从用户->内核时,这个值一般用用户空间的进程id表示;当从内核->用户时,一般为0.

消息体

地址结构体sockaddr_nl,该结构体用于在执行bind()时,将netlink socket与源地址进行绑定,

struct sockaddr_nl
{
    sa_family_t    nl_family;    /*该字段总是为AF_NETLINK    */
    unsigned short    nl_pad;    /* 填充为0*/
    __u32        nl_pid;        /* process pid    */
    __u32        nl_groups;    /* multicast groups mask */
};
  • nl_pid : 发送或接收方进程id,如果是希望发送给内核或者这是一个多播消息,则写为0。
  • nl_groups : 用户空间多播组掩码。如果设为0,则不加入任何多播组,为单播消息。

如何使用

netlink 就是一种特殊的socket。

netlink socket 创建

fd = socket(AF_NETLINK,SOCK_RAW,0)

将netlink socket与地址进行绑定

再绑定之前,需要先创建一个地址结构sockaddr_nl

struct sockaddr_nl nladdr;
nladdr.nl_family = AF_NETLINK;
nladdr.nl_groups = 0;
nladdr.nl_pad = 0;
nladdr.nl_pid = 0;
bind(fd,(struct sockaddr*)&nladdr,sizeof(struct sockaddr_nl));

将该消息发送出去

struct msghdr

msghdr结构体为sendmsg()系统调用发送的消息整体,因此他需要包含几个部分:

  • 目的地址
  • netlink 消息头
  • netlink 消息体

如图所示:

msg_name:接收者的地址
msg_namelen:长度

首先发送该消息之前需要创建一个地址结构体sockaddr_nl表示接收者的地址。

    struct msghdr msg;
    memset(&msg,0,sizeof(msg));
    msg.msg_name = &nladdr; /*address of receiver*/
    msg.msg_namelen = sizeof(nladdr);

构建消息头 nlmsghdr

    char buffer[] = "An example message";
    struct nlmsghdr nlhdr;
    nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
    strcpy(NLMSG_DATA(nlhdr),buffer);
    nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer));
    nlhdr->nlmsg_pid = getpid();  /* self pid */
    nlhdr->nlmsg_flags = 0;

消息头里面的nlmsg_pid是发送方的pid

preView