
/* 分析堆喷射路径，需考虑的问题：如何阻塞发送端进程，使得喷射的堆块长驻于内存。
___sys_sendmsg() 函数分析 —— https://elixir.bootlin.com/linux/v4.11.9/source/net/socket.c#L1921
（1）首先建立一个ctl[36]的数组,大小为36，然后把该数组地址给一个指针ctl_buf
（2）flag != MSG_CMSG_COMPAT ==> 把参数msg，传递给内核空间的msg_sys (均为 struct msghdr)
（3）判断 msg_controllen 不大于 INT_AMX ，并将 该值赋给 ctl_len
（4）flag != MSG_CMSG_COMAPT ，因此调用 sock_malloc
（5）进入sock_malloc 首先判断malloc 的size是否大于sysctl_optmem_max（：int sysctl_optmem_max __read_mostly = sizeof(unsigned long)*(2**UIO_MAXIOV+512）(: uio_maxiov = 1024)(: sk_omem_alloc 初始化为0) ,因为我们要malloc的对象大小为1024，因此满足，所以通过kmalloc申请一个 1024 的堆空间，并返回该指针
（6）回到___sys_sendmsg ： 把申请的堆空间指针赋值给 ctl_buf,并将 msg_control 拷贝进去，并将msg_sys->msg_control 修改为 ctl_buf
（7）used_address 为null，因此执行 sock_sendmsg,这里会回调sock->unix_dgram_ops->unix_dgram_sendmsg
（8）进入unix_dgram_sendmsg
（9）直接调用scm_send()->__scm_send()
（10）在介绍下面之前，有必要理解一下 "control infomation",控制消息通过msghdr的msg_control传递，msg_control指向控制第一条控制信息所在位置，一次可以传递多个控制信息，控制信息的总长度为msg_controllen,每一个控制信息都有一个cmshdr的头部，因为包含多个控制信息，所以，下一个控制信息的地址，就是通过当前控制信息地址 + cmsg_len确定的,通过判断当前控制信息地址 + cmsg_len > msg_controllen可以确定是否还有控制消息
struct cmsghdr {
  __kernel_size_t cmsg_len;     // data byte count, including hdr 
      int     cmsg_level;       // originating protocol 
      int     cmsg_type;        // protocol-specific type 
};
（11）___scm_send : cmsg_level != SQL_COCKET , cmsg_type，=1 或 2 都可以，只要能return 0 ;就可以
（12）进入sock_alloc_send_pskb函数：判断 sk_wmem_alloc< sk_sndbuf,sk_wmem_alloc 表示发送缓冲区长度，sk_sndbuf表示发送缓冲区的最大长度，条件如果为真，则不会阻塞。
（13）然后 申请skb空间， 通过 skb_set_owner_w 函数， 增加 sk_wmem_alloc长度。，再次申请便会阻塞

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1.分析sendmsg阻塞路径——阻塞发送端进程
接下来，为了去阻塞该进程，需要两点：
    1.选择一个合适的socket协议，既不会抢占 1024 字节，也不会触碰 UAF 的内存块，即选择AF_UNIX
    2.寻找函数来设置timeo的值，使进程产生阻塞，并且阻塞时间尽可能大，即仍然为setsockopt()函数
不用原来的netlink套接字流程，主要因为其中netlink_getsockbypid()函数，它会调用netlink_lookup()遍历nl_table里成员，可能会对 UAF 内存块产生致命影响。

对比阻塞：对比sendmsg()->... -> sock_alloc_send_pskb() —堆喷阻塞和netlink_attachskb()—漏洞阻塞：前者检验发送端的sk_sndbuf，后者检验接收端的sk_rcvbuf，二者都是由skb_set_owner_w()函数来增加数据块大小，来达到阻塞的前提条件。从这点来看，虽然是不同函数，但内核逻辑思路还是统一的。
2.设置阻塞时间：通过setsockopt()函数设置timeo的值 —— sk->sndtimeo，即阻塞时间
*/


// ----------------------------------------------------------------------1.分析sendmsg 堆喷及阻塞路径----------------------------------------------------------------------------------------------------------------

static int ___sys_sendmsg(struct socket *sock, struct user_msghdr __user *msg,
             struct msghdr *msg_sys, unsigned int flags,
             struct used_address *used_address,
             unsigned int allowed_msghdr_flags)
{
    struct compat_msghdr __user *msg_compat =
        (struct compat_msghdr __user *)msg;
    struct sockaddr_storage address;
    struct iovec iovstack[UIO_FASTIOV], *iov = iovstack;
    unsigned char ctl[sizeof(struct cmsghdr) + 20]                  // （1）首先建立一个ctl[36]的数组,大小为36 (16+20)，然后把该数组地址给一个指针ctl_buf
                __aligned(sizeof(__kernel_size_t));
    /* 20 is size of ipv6_pktinfo */
    unsigned char *ctl_buf = ctl;
    int ctl_len;
    ssize_t err;

    msg_sys->msg_name = &address;

    if (MSG_CMSG_COMPAT & flags)
        err = get_compat_msghdr(msg_sys, msg_compat, NULL, &iov);
    else
        err = copy_msghdr_from_user(msg_sys, msg, NULL, &iov);      // （2）flag != MSG_CMSG_COMPAT ==> 把参数msg，传递给内核空间的 msg_sys (均为 struct msghdr)
    if (err < 0)
        return err;

    err = -ENOBUFS;

    if (msg_sys->msg_controllen > INT_MAX)                          // （3）判断 msg_controllen 不大于 INT_AMX ，并将 该值赋给 ctl_len
        goto out_freeiov;
    flags |= (msg_sys->msg_flags & allowed_msghdr_flags);   //allowed_msghdr_flags为0，flags即为flags
    ctl_len = msg_sys->msg_controllen;                      //ctl_len值就等于用户提供的msg_controllen
    if ((MSG_CMSG_COMPAT & flags) && ctl_len) {             //MSG_CMSG_COMPAT值经查询为0x80000000，flags一般设置为0即可进入else if判断
        err =
            cmsghdr_from_user_compat_to_kern(msg_sys, sock->sk, ctl,
                             sizeof(ctl));
        if (err)
            goto out_freeiov;
        ctl_buf = msg_sys->msg_control;
        ctl_len = msg_sys->msg_controllen;
    } else if (ctl_len) {
        BUILD_BUG_ON(sizeof(struct cmsghdr) !=                      // （4）flag != MSG_CMSG_COMAPT ，因此调用 sock_malloc
                 CMSG_ALIGN(sizeof(struct cmsghdr)));
        if (ctl_len > sizeof(ctl)) {                        //辅助块struct cmsghdr一般只有16字节，只有有更多data时会扩充
            ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL);  // （5）检查分配的大小是否满足条件，1024大小是满足条件的，接着调用kmalloc来分配空间
            if (ctl_buf == NULL)
                goto out_freeiov;
        }
        err = -EFAULT;
        /*
         * Careful! Before this, msg_sys->msg_control contains a user pointer.
         * Afterwards, it will be a kernel pointer. Thus the compiler-assisted
         * checking falls down on this.
         */
        if (copy_from_user(ctl_buf,                                 // （6）把申请的堆空间指针赋值给 ctl_buf,并将用户态的 msg_control 拷贝进去，并将 msg_sys->msg_control 修改为 ctl_buf 。 msg_control即是辅助块struct cmsghdr
                   (void __user __force *)msg_sys->msg_control,
                   ctl_len))
            goto out_freectl;
        msg_sys->msg_control = ctl_buf;
    }
    msg_sys->msg_flags = flags;

    if (sock->file->f_flags & O_NONBLOCK)
        msg_sys->msg_flags |= MSG_DONTWAIT;
    /*
     * If this is sendmmsg() and current destination address is same as
     * previously succeeded address, omit asking LSM's decision.
     * used_address->name_len is initialized to UINT_MAX so that the first
     * destination address never matches.
     */
    if (used_address && msg_sys->msg_name &&                        // （7）used_address 为null，因此执行 sock_sendmsg,这里会回调sock->unix_dgram_ops->unix_dgram_sendmsg
        used_address->name_len == msg_sys->msg_namelen &&
        !memcmp(&used_address->name, msg_sys->msg_name,
            used_address->name_len)) {
        err = sock_sendmsg_nosec(sock, msg_sys);
        goto out_freectl;
    }
    err = sock_sendmsg(sock, msg_sys);                              // （8）这里最终将会调用 unix_dgram_sendmsg()
    /*
     * If this is sendmmsg() and sending to current destination address was
     * successful, remember it.
     */
    if (used_address && err >= 0) {
        used_address->name_len = msg_sys->msg_namelen;
        if (msg_sys->msg_name)
            memcpy(&used_address->name, msg_sys->msg_name,
                   used_address->name_len);
    }

out_freectl:
    if (ctl_buf != ctl)                             //只要辅助块写入过用户态传入的值，说明申请过内存块，需要对其释放，所以辅助块生命周期很短
        sock_kfree_s(sock->sk, ctl_buf, ctl_len);
out_freeiov:
    kfree(iov);
    return err;
}

//表面上cmsghdr只有一点点成员，实际你可以在其后创建一块data
struct cmsghdr {
        __kernel_size_t            cmsg_len;             /*     0     8 */
        int                        cmsg_level;           /*     8     4 */
        int                        cmsg_type;            /*    12     4 */
        /* size: 16, cachelines: 1, members: 3 */
        /* last cacheline: 16 bytes */
};

struct msghdr {
    void        *msg_name;  /* ptr to socket address structure */
    int     msg_namelen;    /* size of socket address structure */
    struct iov_iter msg_iter;   /* data */
    void        *msg_control;   /* ancillary data */
    __kernel_size_t msg_controllen; /* ancillary data buffer length */
    unsigned int    msg_flags;  /* flags on received message */
    struct kiocb    *msg_iocb;  /* ptr to iocb for async requests */
};
        struct iov_iter {
            int type;
            size_t iov_offset;
            size_t count;
            union {
                const struct iovec *iov;
                const struct kvec *kvec;
                const struct bio_vec *bvec;
            };
            unsigned long nr_segs;
        };
struct user_msghdr {
    void        __user *msg_name;   /* ptr to socket address structure */
    int     msg_namelen;        /* size of socket address structure */
    struct iovec    __user *msg_iov;    /* scatter/gather array */
    __kernel_size_t msg_iovlen;     /* # elements in msg_iov */
    void        __user *msg_control;    /* ancillary data */
    __kernel_size_t msg_controllen;     /* ancillary data buffer length */
    unsigned int    msg_flags;      /* flags on received message */
};







// sock_kmalloc() 申请内存  —— https://elixir.bootlin.com/linux/v4.11.9/source/net/core/sock.c#L1788
/*  （5） 进入sock_malloc 首先判断malloc 的size是否大于sysctl_optmem_max（：int sysctl_optmem_max __read_mostly = sizeof(unsigned long)*(2**UIO_MAXIOV+512）(: uio_maxiov = 1024)(: sk_omem_alloc 初始化为0) ,因为我们要malloc的对象大小为1024，因此满足，所以通过kmalloc申请一个 1024 的堆空间，并返回该指针
 * Allocate a memory block from the socket's option memory buffer.
 */
// 在申请内存而调用的函数是sock_kmalloc()函数，申请时有个关于系统自身属性optmem_max的限制，可以通过以下命令查看系统的optmem_max:  $ cat /proc/sys/net/core/optmem_max     只有其大于 512 时才可以继续提权
void *sock_kmalloc(struct sock *sk, int size, gfp_t priority)
{
    if ((unsigned int)size <= sysctl_optmem_max &&  // 这块申请内存大小和避免race而总共申请都要小于optmem_max，所以最后喷射的堆也不能很多
        atomic_read(&sk->sk_omem_alloc) + size < sysctl_optmem_max) {
        void *mem;
        /* First do the add, to avoid the race if kmalloc
         * might sleep.
         */
        atomic_add(size, &sk->sk_omem_alloc);
        mem = kmalloc(size, priority);
        if (mem)
            return mem;
        atomic_sub(size, &sk->sk_omem_alloc);
    }
    return NULL;
}
EXPORT_SYMBOL(sock_kmalloc);





// unix_dgram_sendmsg() —— https://elixir.bootlin.com/linux/v4.11.9/source/net/unix/af_unix.c#L1638
static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
                  size_t len)
{
    struct sock *sk = sock->sk;
    struct net *net = sock_net(sk);
    struct unix_sock *u = unix_sk(sk);
    DECLARE_SOCKADDR(struct sockaddr_un *, sunaddr, msg->msg_name);
    struct sock *other = NULL;
    int namelen = 0; /* fake GCC */
    int err;
    unsigned int hash;
    struct sk_buff *skb;
    long timeo;
    struct scm_cookie scm;
    int max_level;
    int data_len = 0;
    int sk_locked;

    wait_for_unix_gc();
    err = scm_send(sock, msg, &scm, false);                         // （9）直接调用scm_send()->__scm_send()     需要绕过此函数的判断条件
/*
    （10）在介绍下面之前，有必要理解一下 "control infomation",控制消息通过msghdr的msg_control传递，msg_control指向控制第一条控制信息所在位置，一次可以传递多个控制信息，
    控制信息的总长度为msg_controllen,每一个控制信息都有一个cmshdr的头部，因为包含多个控制信息，所以，下一个控制信息的地址，就是通过当前控制信息地址 + cmsg_len确定的,
    通过判断当前控制信息地址 + cmsg_len > msg_controllen可以确定是否还有控制消息
struct cmsghdr {
  __kernel_size_t cmsg_len;     // data byte count, including hdr 
      int     cmsg_level;       // originating protocol 
      int     cmsg_type;        // protocol-specific type 
};
*/
    if (err < 0)
        return err;

    err = -EOPNOTSUPP;
    if (msg->msg_flags&MSG_OOB)
        goto out;

    if (msg->msg_namelen) {
        err = unix_mkname(sunaddr, msg->msg_namelen, &hash);
        if (err < 0)
            goto out;
        namelen = err;
    } else {
        sunaddr = NULL;
        err = -ENOTCONN;
        other = unix_peer_get(sk);
        if (!other)
            goto out;
    }

    if (test_bit(SOCK_PASSCRED, &sock->flags) && !u->addr
        && (err = unix_autobind(sock)) != 0)
        goto out;

    err = -EMSGSIZE;
    if (len > sk->sk_sndbuf - 32)
        goto out;

    if (len > SKB_MAX_ALLOC) {
        data_len = min_t(size_t,
                 len - SKB_MAX_ALLOC,
                 MAX_SKB_FRAGS * PAGE_SIZE);
        data_len = PAGE_ALIGN(data_len);

        BUILD_BUG_ON(SKB_MAX_ALLOC < PAGE_SIZE);
    }

    skb = sock_alloc_send_pskb(sk, len - data_len, data_len,        // （12）进入sock_alloc_send_pskb函数 —— 阻塞函数：判断 sk_wmem_alloc< sk_sndbuf,sk_wmem_alloc 表示发送缓冲区长度，sk_sndbuf表示发送缓冲区的最大长度，条件如果为真，则不会阻塞。
                   msg->msg_flags & MSG_DONTWAIT, &err,
                   PAGE_ALLOC_COSTLY_ORDER);







// __scm_send() —— https://elixir.bootlin.com/linux/v4.11.9/source/net/core/scm.c#L134
    // cmsg_len要大于等于16，又要小于等于整体辅助块的大小
int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)   // （11）cmsg_level != SQL_COCKET , cmsg_type, =1 或 2 都可以，只要能return 0 ;就可以
{
    struct cmsghdr *cmsg;
    int err;

    for_each_cmsghdr(cmsg, msg) {       // 只进入一次，第二次因为没有就直接跳出
        err = -EINVAL;

        /* Verify that cmsg_len is at least sizeof(struct cmsghdr) */
        /* The first check was omitted in <= 2.2.5. The reasoning was
           that parser checks cmsg_len in any case, so that
           additional check would be work duplication.
           But if cmsg_level is not SOL_SOCKET, we do not check
           for too short ancillary data object at all! Oops.
           OK, let's add it...
         */
        if (!CMSG_OK(msg, cmsg))        // 长度检查，cmsg_len要大于等于16，又要小于等于整体辅助块的大小
            goto error;

        if (cmsg->cmsg_level != SOL_SOCKET) // cmsg_level需要不等于SOL_SOCKET，即不等于0xffff的某一取值
            continue;

        switch (cmsg->cmsg_type)
        {
        case SCM_RIGHTS:
            if (!sock->ops || sock->ops->family != PF_UNIX)
                goto error;
            err=scm_fp_copy(cmsg, &p->fp);
            if (err<0)
                goto error;
            break;
        case SCM_CREDENTIALS:
        {
            struct ucred creds;
            kuid_t uid;
            kgid_t gid;
            if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct ucred)))
                goto error;
            memcpy(&creds, CMSG_DATA(cmsg), sizeof(struct ucred));
            err = scm_check_creds(&creds);
            if (err)
                goto error;

            p->creds.pid = creds.pid;
            if (!p->pid || pid_vnr(p->pid) != creds.pid) {
                struct pid *pid;
                err = -ESRCH;
                pid = find_get_pid(creds.pid);
                if (!pid)
                    goto error;
                put_pid(p->pid);
                p->pid = pid;
            }

            err = -EINVAL;
            uid = make_kuid(current_user_ns(), creds.uid);
            gid = make_kgid(current_user_ns(), creds.gid);
            if (!uid_valid(uid) || !gid_valid(gid))
                goto error;

            p->creds.uid = uid;
            p->creds.gid = gid;
            break;
        }
        default:
            goto error;
        }
    }

    if (p->fp && !p->fp->count)
    {
        kfree(p->fp);
        p->fp = NULL;
    }
    return 0;

error:
    scm_destroy(p);
    return err;
}
EXPORT_SYMBOL(__scm_send);

//cmsg_len要大于等于16，又要小于等于整体辅助块的大小
#define CMSG_OK(mhdr, cmsg) ((cmsg)->cmsg_len >= sizeof(struct cmsghdr) && 
                 (cmsg)->cmsg_len <= (unsigned long) 
                 ((mhdr)->msg_controllen - 
                  ((char *)(cmsg) - (char *)(mhdr)->msg_control)))
#define for_each_cmsghdr(cmsg, msg) 






// sock_alloc_send_pskb()  阻塞函数（通过setsockopt()函数设置timeo的值 —— sk->sndtimeo，即阻塞时间） —— https://elixir.bootlin.com/linux/v4.11.9/source/net/core/sock.c#L1866
// 调用路径： `___sys_sendmsg() -> ... -> sock_sendmsg()-unix_dgram_sendmsg() -> sock_alloc_send_pskb()`
// （12）进入 sock_alloc_send_pskb() 函数：判断 sk_wmem_alloc < sk_sndbuf,sk_wmem_alloc 表示发送缓冲区长度，sk_sndbuf表示发送缓冲区的最大长度，条件如果为真，则不会阻塞。
/*  对比sendmsg()->... -> sock_alloc_send_pskb() —堆喷阻塞 和 netlink_attachskb()—漏洞阻塞：前者检验发送端的sk_sndbuf，后者检验接收端的sk_rcvbuf，二者都是由skb_set_owner_w()函数来增加数据块大小，来达到阻塞的前提条件。从这点来看，虽然是不同函数，但内核逻辑思路还是统一的。
 *  Generic send/receive buffer handlers
 */
struct sk_buff *sock_alloc_send_pskb(struct sock *sk, unsigned long header_len,
                     unsigned long data_len, int noblock,
                     int *errcode, int max_page_order)
{
    struct sk_buff *skb;
    long timeo;
    int err;

    timeo = sock_sndtimeo(sk, noblock);     // timeo从这里赋值，如果sk->sndtimeo不为零即会得到阻塞时间值。 sock_sndtimeo() 如下所示。
    for (;;) {
        err = sock_error(sk);
        if (err != 0)
            goto failure;

        err = -EPIPE;
        if (sk->sk_shutdown & SEND_SHUTDOWN)
            goto failure;

        if (sk_wmem_alloc_get(sk) < sk->sk_sndbuf)                          // （12）条件为假，则会阻塞。即如果得到数据块大小小于sk_sndbuf，仍然无法阻塞
            break;

        sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
        set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
        err = -EAGAIN;
        if (!timeo)
            goto failure;
        if (signal_pending(current))
            goto interrupted;
        timeo = sock_wait_for_wmem(sk, timeo);                              // 阻塞开始
    }
    skb = alloc_skb_with_frags(header_len, data_len, max_page_order,        // （13）申请skb空间， 通过 skb_set_owner_w 函数， 增加 sk_wmem_alloc 长度。再次申请便会阻塞
                   errcode, sk->sk_allocation);
    if (skb)
        skb_set_owner_w(skb, sk);
    return skb;

interrupted:
    err = sock_intr_errno(timeo);
failure:
    *errcode = err;
    return NULL;
}
EXPORT_SYMBOL(sock_alloc_send_pskb);

static inline long sock_sndtimeo(const struct sock *sk, bool noblock)
{
    return noblock ? 0 : sk->sk_sndtimeo;
}
























--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/*
阻塞发送端进程
接下来，为了去阻塞该进程，需要两点：
    1.选择一个合适的socket协议，既不会抢占 1024 字节，也不会触碰 UAF 的内存块，即选择AF_UNIX
    2.寻找函数来设置timeo的值，使进程产生阻塞，并且阻塞时间尽可能大，即仍然为setsockopt()函数
不用原来的netlink套接字流程，主要因为其中netlink_getsockbypid()函数，它会调用netlink_lookup()遍历nl_table里成员，可能会对 UAF 内存块产生致命影响。
*/
static struct sock *netlink_getsockbyportid(struct sock *ssk, u32 portid)
{
    struct sock *sock;
    struct netlink_sock *nlk;

    //此处会查找连接的recv_fd
    sock = netlink_lookup(sock_net(ssk), ssk->sk_protocol, portid);
    if (!sock)
        return ERR_PTR(-ECONNREFUSED);

    /* Don't bother queuing skb if kernel socket has no input function */
    nlk = nlk_sk(sock);
    if (sock->sk_state == NETLINK_CONNECTED &&
        nlk->dst_portid != nlk_sk(ssk)->portid) {
        sock_put(sock);
        return ERR_PTR(-ECONNREFUSED);
    }
    return sock;
}

static struct sock *netlink_lookup(struct net *net, int protocol, u32 portid)
{
    struct netlink_table *table = &nl_table[protocol];
    struct sock *sk;

    rcu_read_lock();

    //到对应协议的hash表单中寻找
    sk = __netlink_lookup(table, portid, net);
    if (sk)
        sock_hold(sk);
    rcu_read_unlock();

    return sk;
}

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/*
那么，在AF_UNIX协议中一般使用struct sockaddr_un，它仅包含成员sun_family和sun_path，很独特的是它的端口是类似于文件路径，使它更像是共享内存模式，将sun_path头字节置为 0 ，就不会有寻找不到路径的麻烦。
*/








// ----------------------------------------------------------------------2.分析setsockopt()设置阻塞时间的路径----------------------------------------------------------------------------------------------------------------
//level选择是 SOL_SOCKET 值
int sock_setsockopt(struct socket *sock, int level, int optname,
            char __user *optval, unsigned int optlen)
{
    ...
    switch (optname) {
    [...]
    //当 optname 为 SO_SNDTIMEO 时，可以修改timeo，继续跟踪
    case SO_SNDTIMEO:
        ret = sock_set_timeout(&sk->sk_sndtimeo, optval, optlen);                       // !!!!!
        break;


//其中的检查有点绕，但有简单办法就是结构体全置零，即可绕过
static int sock_set_timeout(long *timeo_p, char __user *optval, int optlen)
{
    struct timeval tv;

    //optlen不能小于struct timeval结构体大小
    if (optlen < sizeof(tv))
        return -EINVAL;

    //用户态的struct timeval需要真实存在
    if (copy_from_user(&tv, optval, sizeof(tv)))
        return -EFAULT;

    //tv.tv_usec 需要大于等于0 ，又要小于USEC_PER_SEC
    if (tv.tv_usec < 0 || tv.tv_usec >= USEC_PER_SEC)
        return -EDOM;

    //tv.tv_sec需要大于等于零
    if (tv.tv_sec < 0) {
        static int warned __read_mostly;

        *timeo_p = 0;
        if (warned < 10 && net_ratelimit()) {
            warned++;
            pr_info("%s: `%s' (pid %d) tries to set negative timeoutn",
                __func__, current->comm, task_pid_nr(current));
        }
        return 0;
    }

    //timeo_p成功赋予最大时延
    *timeo_p = MAX_SCHEDULE_TIMEOUT;

    //俩者皆为0时，直接退出
    if (tv.tv_sec == 0 && tv.tv_usec == 0)
        return 0;
    if (tv.tv_sec < (MAX_SCHEDULE_TIMEOUT/HZ - 1))
        *timeo_p = tv.tv_sec * HZ + DIV_ROUND_UP(tv.tv_usec, USEC_PER_SEC / HZ);
    return 0;
}




// 从 setsockopt() 到 sock_setsockopt() 需要绕过的检查
查阅网上资料，都介绍到这一步就算完成此模块，但我在真正调试时，发现还有保护需要绕过，网上版本比我低，所以或许没有此保护。
不过这也不是问题，找到对应函数，再一一绕过检验。

SYSCALL_DEFINE5(setsockopt, int, fd, int, level, int, optname,
        char __user *, optval, int, optlen)
{
    int err, fput_needed;
    struct socket *sock;

    if (optlen < 0)
        return -EINVAL;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock != NULL) { 

        //此处是个struct socket的安全检查函数
        err = security_socket_setsockopt(sock, level, optname);
        if (err)
            goto out_put;

        if (level == SOL_SOCKET)
            err =
                sock_setsockopt(sock, level, optname, optval,
                        optlen);
        else
            err =
                sock->ops->setsockopt(sock, level, optname, optval,
                          optlen);
out_put:
        fput_light(sock->file, fput_needed);
    }
    return err;
}

//这里是个内核hook函数，可以拿gdb跟踪下一步
int security_socket_setsockopt(struct socket *sock, int level, int optname)
{
    return call_int_hook(socket_setsockopt, 0, sock, level, optname);
}

//gdb跟踪到此处，其中主要有sock_has_perm()函数检查校验
static int selinux_socket_setsockopt(struct socket *sock, int level, int optname)
{
    int err;

    //err为 0 即可绕过
    err = sock_has_perm(sock->sk, SOCKET__SETOPT);
    if (err)
        return err;

    return selinux_netlbl_socket_setsockopt(sock, level, optname);
}

//主要检查了struct sock里的sk->sk_security值来判断安全
static int sock_has_perm(struct sock *sk, u32 perms)
{
    struct sk_security_struct *sksec = sk->sk_security;
    struct common_audit_data ad;
    struct lsm_network_audit net = {0,};

    //SECINITSID_KERNEL值为 1 ，所以sksec->sid值必须为 1
    if (sksec->sid == SECINITSID_KERNEL)
        return 0;

    ad.type = LSM_AUDIT_DATA_NET;
    ad.u.net = &net;
    ad.u.net->sk = sk;

    return avc_has_perm(current_sid(), sksec->sid, sksec->sclass, perms,
                &ad);
}

//由于无法找到其头文件，需要在利用代码中，构造对应结构体并且传值，其中用不到的结构体指针可以拿 void * 代替
struct sk_security_struct {
#ifdef CONFIG_NETLABEL
    enum {                /* NetLabel state */
        NLBL_UNSET = 0,
        NLBL_REQUIRE,
        NLBL_LABELED,
        NLBL_REQSKB,
        NLBL_CONNLABELED,
    } nlbl_state;
    struct netlbl_lsm_secattr *nlbl_secattr; /* NetLabel sec attributes */
#endif
    u32 sid;            /* SID of this object */
    u32 peer_sid;            /* SID of peer */
    u16 sclass;            /* sock security class */
};

//只要sksec->nlbl_state为 0，即可通过此函数判断。
int selinux_netlbl_socket_setsockopt(struct socket *sock,
                     int level,
                     int optname)
{
    int rc = 0;
    struct sock *sk = sock->sk;
    struct sk_security_struct *sksec = sk->sk_security;
    struct netlbl_lsm_secattr secattr;

    if (selinux_netlbl_option(level, optname) &&
        (sksec->nlbl_state == NLBL_LABELED ||
         sksec->nlbl_state == NLBL_CONNLABELED)) {
        netlbl_secattr_init(&secattr);
        lock_sock(sk);
        /* call the netlabel function directly as we want to see the
         * on-the-wire label that is assigned via the socket's options
         * and not the cached netlabel/lsm attributes */
        rc = netlbl_sock_getattr(sk, &secattr);
        release_sock(sk);
        if (rc == 0)
            rc = -EACCES;
        else if (rc == -ENOMSG)
            rc = 0;
        netlbl_secattr_destroy(&secattr);
    }

    return rc;
}


