【kernel exploit】CVE-2017-6074 DCCP擁塞控制協(xié)議Double-Free提權(quán)分析

文章首發(fā)于安全客:CVE-2017-6074 DCCP擁塞控制協(xié)議Double-Free提權(quán)分析

影響版本:Linux v2.6.14 - v4.9.13。 v4.9.13已修補,v4.9.12未修補少态。 評分7.8分滑潘。 隱藏時間超過10年,從2005年10月的v2.6.14開始斤葱。

測試版本:Linux-v4.9.12 exploit及測試環(huán)境下載地址https://github.com/bsauce/kernel-exploit-factory

編譯選項CONFIG_IP_DCCP=y CONFIG_INET_DCCP_DIAG=y 以及與DCCP相關(guān)的選項。

在編譯時將.config中的CONFIG_E1000CONFIG_E1000E,變更為=y弹惦。參考

$ wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v4.x/linux-4.9.12.tar.xz
$ tar -xvf linux-4.9.12.tar.xz
# KASAN: 設(shè)置 make menuconfig 設(shè)置"Kernel hacking" ->"Memory Debugging" -> "KASan: runtime memory debugger"。
$ make -j32
$ make all
$ make modules
# 編譯出的bzImage目錄:/arch/x86/boot/bzImage悄但。

漏洞描述:Linux內(nèi)核IP V6協(xié)議簇的DCCP(數(shù)據(jù)報擁塞控制協(xié)議)棠隐,net/dccp/input.c中的 dccp_rcv_state_process() 函數(shù),在LISTEN狀態(tài)下錯誤處理 DCCP_PKT_REQUEST 包數(shù)據(jù)結(jié)構(gòu)檐嚣,用戶采用IPV6_RECVPKTINFO選項調(diào)用setsockopt()時會觸發(fā)sk_buff結(jié)構(gòu)的Double-Free助泽。

補丁patch 調(diào)用consume_skb()繼續(xù)占用skb,以避免跳到discardkfree_skb()釋放skb嚎京。consume_skb() 表示 skb是正常釋放嗡贺,kfree_skb() 表示因為某種錯誤報文被丟棄。

diff --git a/net/dccp/input.c b/net/dccp/input.c
index ba347184bda9b..8fedc2d497709 100644
--- a/net/dccp/input.c
+++ b/net/dccp/input.c
@@ -606,7 +606,8 @@ int dccp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
            if (inet_csk(sk)->icsk_af_ops->conn_request(sk,
                                    skb) < 0)
                return 1;
-           goto discard;
+           consume_skb(skb);
+           return 0;
        }
        if (dh->dccph_type == DCCP_PKT_RESET)
            goto discard;

保護機制:開啟SMEP/SMAP鞍帝,未開啟KASLR暑刃。

利用總結(jié):利用方式類似CVE-2016-8655。第一次觸發(fā)漏洞膜眠,堆噴偽造po->rx_ring->prb_bdqc->retire_blk_timer結(jié)構(gòu)岩臣,執(zhí)行native_write_cr4(0x406e0)來關(guān)閉SMEP/SMAP溜嗜;第二次觸發(fā)漏洞,堆噴偽造skb-> ... ->destructor_arg結(jié)構(gòu)架谎,執(zhí)行commit_creds(prepare_kernel_cred(0))來提權(quán)炸宵。


1. 漏洞分析

漏洞流程

  • (1)dccp_rcv_state_process() 處理請求包,如果DCCP協(xié)議棧socket狀態(tài)為DCCP_LISTEN谷扣,且請求類型為DCCP_PKT_REQUEST土全,則調(diào)用 dccp_v6_conn_request()[1]處;
  • (2)dccp_v6_conn_request()[3]處会涎,只要滿足條件裹匙,就將skb引用計數(shù)加1,且將skb指針保存到 ireq->pktopts——[4][5]末秃;用戶可通過調(diào)用 setsockopt()IPV6_RECVPKTINFO 選項來設(shè)置 np->rxopt.bits.rxinfo概页,使之滿足條件[3]
  • (3)dccp_v6_conn_request() 返回成功练慕,卻跳至[2]處惰匙,該skb被__kfree_skb()強制釋放。之后skb再次釋放時即觸發(fā)Double-Free铃将。

調(diào)用鏈dccp_rcv_state_process() -> dccp_v6_conn_request()

int dccp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
               struct dccp_hdr *dh, unsigned int len)
{
    struct dccp_sock *dp = dccp_sk(sk);
    struct dccp_skb_cb *dcb = DCCP_SKB_CB(skb);
    const int old_state = sk->sk_state;
    int queued = 0;
    ... ...
    if (sk->sk_state == DCCP_LISTEN) {
        if (dh->dccph_type == DCCP_PKT_REQUEST) {
            if (inet_csk(sk)->icsk_af_ops->conn_request(sk,     // [1]  實際調(diào)用 dccp_v6_conn_request() 函數(shù)
                                    skb) < 0)
                return 1;
            goto discard;
        }
        if (dh->dccph_type == DCCP_PKT_RESET)
            goto discard;

        /* Caller (dccp_v4_do_rcv) will send Reset */
        dcb->dccpd_reset_code = DCCP_RESET_CODE_NO_CONNECTION;
        return 1;
    } else if (sk->sk_state == DCCP_CLOSED) {
        dcb->dccpd_reset_code = DCCP_RESET_CODE_NO_CONNECTION;
        return 1;
    }
    ... ...
    if (!queued) {
discard:
        __kfree_skb(skb);                                       // [2] 錯誤釋放 skb
    }
    return 0;
}
// [1] dccp_v6_conn_request()
static int dccp_v6_conn_request(struct sock *sk, struct sk_buff *skb)
{
    struct request_sock *req;
    struct dccp_request_sock *dreq;
    struct inet_request_sock *ireq;
    struct ipv6_pinfo *np = inet6_sk(sk);
    const __be32 service = dccp_hdr_request(skb)->dccph_req_service;
    struct dccp_skb_cb *dcb = DCCP_SKB_CB(skb);
    ... ...
    ireq = inet_rsk(req);
    ireq->ir_v6_rmt_addr = ipv6_hdr(skb)->saddr;
    ireq->ir_v6_loc_addr = ipv6_hdr(skb)->daddr;
    ireq->ireq_family = AF_INET6;

    if (ipv6_opt_accepted(sk, skb, IP6CB(skb)) ||
        np->rxopt.bits.rxinfo || np->rxopt.bits.rxoinfo ||      // [3] 可通過 setsockopt() 和 IPV6_RECVPKTINFO 選項來設(shè)置 np->rxopt.bits.rxinfo
        np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim) {
        atomic_inc(&skb->users);                                // [4] 只要滿足其中一個條件项鬼,就會將skb的引用計數(shù)加1
        ireq->pktopts = skb;                                    // [5] 且將skb指針保存到 ireq->pktopts 中。
    }
    ireq->ir_iif = sk->sk_bound_dev_if;
    ... ...
}

2. 漏洞利用

2-1. 觸發(fā)漏洞

觸發(fā)步驟

  • (1)創(chuàng)建s1 = socket(PF_INET6, SOCK_DCCP, …)劲阎,并且監(jiān)聽該socket绘盟;

  • (2)設(shè)置該socket的屬性值IPV6_RECVPKTINFO,使函數(shù)dccp_v6_conn_request()通過if條件[3]悯仙,觸發(fā)釋放skb龄毡;

  • (3)skb釋放后,進行堆噴雁比,偽造skb-> ... ->destructor_arg->callback函數(shù),觸發(fā)二次釋放skb撤嫩,執(zhí)行偽造的回調(diào)函數(shù)偎捎。

結(jié)構(gòu)鏈(偽造ubuf_info結(jié)構(gòu)):sk_buff -> skb_shared_info -> ubuf_info -> callback

struct sk_buff {
    union {
        struct {
            /* These two members must be first. */
            struct sk_buff      *next;
            struct sk_buff      *prev;

            union {
                ktime_t     tstamp;
                struct skb_mstamp skb_mstamp;
            };
        };
        struct rb_node  rbnode; /* used in netem & tcp stack */
    };
    struct sock     *sk;
    struct net_device   *dev;
  ... ... 
  /* These elements must be at the end, see alloc_skb() for details.  */
    sk_buff_data_t      tail;
    sk_buff_data_t      end;
    unsigned char       *head,
                *data;                                                      // <------------ (head+end) 指向 skb_shared_info 結(jié)構(gòu)
    unsigned int        truesize;
    atomic_t        users;
};

struct skb_shared_info {
    unsigned char   nr_frags;
    __u8        tx_flags;
    unsigned short  gso_size;
    /* Warning: this field is not always filled in (UFO)! */
    unsigned short  gso_segs;
    unsigned short  gso_type;
    struct sk_buff  *frag_list;
    struct skb_shared_hwtstamps hwtstamps;
    u32     tskey;
    __be32          ip6_frag_id;

    /*
     * Warning : all fields before dataref are cleared in __alloc_skb()
     */
    atomic_t    dataref;

    /* Intermediate layers must ensure that destructor_arg
     * remains valid until skb destructor */
    void *      destructor_arg;                                         // <------------ 指向 ubuf_info 結(jié)構(gòu)

    /* must be last field, see pskb_expand_head() */
    skb_frag_t  frags[MAX_SKB_FRAGS];
};

struct ubuf_info {
    void (*callback)(struct ubuf_info *, bool zerocopy_success);        // <------------ 待偽造的回調(diào)函數(shù)
    void *ctx;
    unsigned long desc;
};

sk_buff二次釋放調(diào)用鏈dccp_close() -> inet_csk_destroy_sock() -> dccp_v6_destroy_sock() -> inet6_destroy_sock() -> kfree_skb() -> __kfree_skb() -> skb_release_all() -> skb_release_data()

static struct proto dccp_v6_prot = {
    .name          = "DCCPv6",
    .owner         = THIS_MODULE,
    .close         = dccp_close,                // close(socket) -> dccp_close() ->  ...  ->      sk->sk_prot->destroy(sk)
    ... ...
    .destroy       = dccp_v6_destroy_sock,
    ... ...
};

static void skb_release_data(struct sk_buff *skb)
{
    struct skb_shared_info *shinfo = skb_shinfo(skb);           // skb_shared_info 在 sk_buff中線性數(shù)據(jù)區(qū)的偏移: skb->head+skb->end
    int i;

    if (skb->cloned &&
        atomic_sub_return(skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1,
                  &shinfo->dataref))
        return;

    for (i = 0; i < shinfo->nr_frags; i++)
        __skb_frag_unref(&shinfo->frags[i]);

    /*
     * If skb buf is from userspace, we need to notify the caller
     * the lower device DMA has done;
     */
    if (shinfo->tx_flags & SKBTX_DEV_ZEROCOPY) {
        struct ubuf_info *uarg;

        uarg = shinfo->destructor_arg;
        if (uarg->callback)                                                                 // 執(zhí)行回調(diào)函數(shù)
            uarg->callback(uarg, true);
    }

    if (shinfo->frag_list)
        kfree_skb_list(shinfo->frag_list);

    skb_free_head(skb);
}

#define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))

static inline unsigned char *skb_end_pointer(const struct sk_buff *skb)
{
    return skb->head + skb->end;
}

2-2. 關(guān)閉SMEP/SMAP

思路:參考CVE-2016-8655的利用方法,調(diào)用native_write_cr4(0x406e0)來關(guān)閉SMEP/SMAP序攘。如果采用以上觸發(fā)方法來劫持skb-> ... ->destructor_arg->callback函數(shù)茴她,則無法傳遞參數(shù)0x406e0。所以借鑒CVE-2016-8655的利用方法程奠,劫持回調(diào)函數(shù) packet_sock --> struct packet_ring_buffer rx_ring --> struct tpacket_kbdq_core prb_bdqc --> struct timer_list retire_blk_timer --> function

結(jié)構(gòu)鏈(偽造timer_list結(jié)構(gòu)):

struct packet_sock {
    /* struct sock has to be the first member of packet_sock */
    struct sock     sk;
    struct packet_fanout    *fanout;
    union  tpacket_stats_u  stats;
    struct packet_ring_buffer   rx_ring;                    // <--------------- rx_ring
    struct packet_ring_buffer   tx_ring;
    ... ...
};
struct packet_ring_buffer {
    struct pgv      *pg_vec;
    ... ...
    unsigned int        pg_vec_order;
    unsigned int        pg_vec_pages;
    unsigned int        pg_vec_len;

    unsigned int __percpu   *pending_refcnt;

    struct tpacket_kbdq_core    prb_bdqc;                   // <---------------- prb_bdqc
};
/* kbdq - kernel block descriptor queue */
struct tpacket_kbdq_core {
    struct pgv  *pkbdq;
    ... ...
    struct sk_buff  *skb;                                   // <---------------- skb

    atomic_t    blk_fill_in_prog;

    /* Default is set to 8ms */
#define DEFAULT_PRB_RETIRE_TOV  (8)

    unsigned short  retire_blk_tov;
    unsigned short  version;
    unsigned long   tov_in_jiffies;

    /* timer to retire an outstanding block */
    struct timer_list retire_blk_timer;                     // <---------------- retire_blk_timer
};
struct timer_list {
    struct hlist_node   entry;
    unsigned long       expires;
    void            (*function)(unsigned long);             // 待偽造的回調(diào)函數(shù)
    unsigned long       data;                               // 參數(shù)
    u32         flags;
#ifdef CONFIG_TIMER_STATS
    int         start_pid;
    void            *start_site;
    char            start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map  lockdep_map;
#endif
};

創(chuàng)建timer調(diào)用鏈setsockopt(fd, SOL_PACKET, PACKET_RX_RING, (void*)&tp, sizeof(tp)); —— packet_set_ring()->init_prb_bdqc()->prb_setup_retire_blk_timer()->prb_init_blk_timer()

注銷timer調(diào)用鏈close(fd); —— packet_release() -> packet_set_ring()->prb_shutdown_retire_blk_timer() -> prb_del_retire_blk_timer() -> del_timer_sync()

2-3. 完整利用

利用步驟

  • (1)第一次觸發(fā)漏洞丈牢,偽造函數(shù)指針 po->rx_ring->prb_bdqc->retire_blk_timer->function,指向native_write_cr4()函數(shù)瞄沙,偽造參數(shù) po->rx_ring->prb_bdqc->retire_blk_timer->data 為 0x406e0己沛,關(guān)閉SMEP/SMAP保護慌核;
  • (2)第二次觸發(fā)漏洞,偽造函數(shù)指針 skb-> ... ->destructor_arg->callback申尼,指向 commit_creds(prepare_kernel_cred(0)) 函數(shù)垮卓,提權(quán);
  • (3)如果能讀取特權(quán)文件师幕,表示提權(quán)成功粟按,fork子進程彈shell,避免直接彈shell時釋放sk_buff導(dǎo)致崩潰霹粥。

堆噴射:注意灭将,關(guān)閉SMEP/SMAP時噴射覆蓋的是packet_sock對象,大小為0x580后控;提權(quán)時噴射覆蓋的是sk_buff指向的數(shù)據(jù)區(qū)和skb_shared_info結(jié)構(gòu)所在的堆塊庙曙,大小為0x800。這兩個對象都位于0x800大小的堆塊中忆蚀,所以exp中發(fā)送的占位數(shù)據(jù)大小是1536矾利,也就是0x600,對齊后大小為0x800馋袜。關(guān)于sk_buff對象的知識可以參考第3節(jié)男旗,了解sk_buff結(jié)構(gòu)和skb_shared_info結(jié)構(gòu)的空間排布關(guān)系。

修正偏移

# 1. timer offset               ---> 偏移為 0x2e8+0x30+104
gef?  p/x &(*(struct packet_sock*)0)->rx_ring
$3 = 0x2e8                  =744
gef?  p/x &(*(struct packet_ring_buffer*)0)->prb_bdqc
$4 = 0x30
gef?  p/x &(*(struct tpacket_kbdq_core*)0)->retire_blk_timer
$5 = 0x68                   =104

# 2. skb_shared_info offset     ---> 偏移為 0x6c0
/exp $ cat /tmp/kallsyms | grep skb_release_data
ffffffff81783260 t skb_release_data
gef?  x /30i 0xffffffff81783260
   0xffffffff81783260 <skb_release_data>:   nop    DWORD PTR [rax+rax*1+0x0]
   0xffffffff81783265 <skb_release_data+5>: push   rbp
   0xffffffff81783266 <skb_release_data+6>: mov    rbp,rsp
   0xffffffff81783269 <skb_release_data+9>: push   r14
   0xffffffff8178326b <skb_release_data+11>:    push   r13
   0xffffffff8178326d <skb_release_data+13>:    push   r12
   0xffffffff8178326f <skb_release_data+15>:    push   rbx
=> 0xffffffff81783270 <skb_release_data+16>:    movzx  eax,BYTE PTR [rdi+0x8e]
   0xffffffff81783277 <skb_release_data+23>:    mov    r14d,DWORD PTR [rdi+0xcc]
   0xffffffff8178327e <skb_release_data+30>:    add    r14,QWORD PTR [rdi+0xd0]
   0xffffffff81783285 <skb_release_data+37>:    test   al,0x1
   0xffffffff81783287 <skb_release_data+39>:    je     0xffffffff817832af <skb_release_data+79>
gef?  p skb
$1 = (struct sk_buff *) 0xffff88007fa5f200
gef?  p *(struct sk_buff *) 0xffff88007fa5f200
  tail = 0x4ac,
  end = 0x6c0,
  head = 0xffff88007a890800 "",
  data = 0xffff88007a890c78

提權(quán)成功

succeed.png

3. sk_buff 擴展學(xué)習(xí)

目的:了解sk_buff結(jié)構(gòu)和skb_shared_info結(jié)構(gòu)的空間排布關(guān)系欣鳖。

3-1. sk_buff 結(jié)構(gòu)

sk_buff結(jié)構(gòu)體:sk_buff結(jié)構(gòu)體關(guān)聯(lián)多個其他結(jié)構(gòu)體察皇,第一是線性數(shù)據(jù)區(qū),由sk_buff->headsk_buff->end指向的數(shù)據(jù)塊泽台,用來存儲sk_buff結(jié)構(gòu)的數(shù)據(jù)也即是存儲數(shù)據(jù)包的內(nèi)容和各層協(xié)議頭什荣。第二是分片結(jié)構(gòu),也即skb_shared_info結(jié)構(gòu)怀酷,跟在線性數(shù)據(jù)區(qū)后面稻爬,即是end指針的下一個字節(jié)開始就是分片結(jié)構(gòu),用來表示IP分片的一個結(jié)構(gòu)體蜕依。因此桅锄,skb_shared_info分片結(jié)構(gòu)和sk_buff的線性數(shù)據(jù)區(qū)內(nèi)存分配及銷毀時都是一起的。第三個是分片結(jié)構(gòu)指向的非線性數(shù)據(jù)區(qū)样眠,即是IP分片內(nèi)容友瘤。

struct sk_buff {
    union {
        struct {
            /* These two members must be first. */
            struct sk_buff      *next;      // sk_buff結(jié)構(gòu)體是雙鏈表, 指向下一個sk_buff結(jié)構(gòu)體
            struct sk_buff      *prev;      // 指向前一個sk_buff結(jié)構(gòu)體

            union {
                ktime_t     tstamp;         // 時間戳,表示這個skb的接收到的時間檐束,一般是在包從驅(qū)動中往二層發(fā)送的接口函數(shù)中設(shè)置
                struct skb_mstamp skb_mstamp;
            };
        };
        struct rb_node  rbnode; /* used in netem & tcp stack */
    };
    struct sock     *sk;                    // 指向擁有此緩沖的套接字sock結(jié)構(gòu)體辫秧,即:宿主傳輸控制模塊
    struct net_device   *dev;               // 表示一個網(wǎng)絡(luò)設(shè)備,當(dāng)skb為輸出/輸入時被丧,dev表示要輸出/輸入到的設(shè)備

    char            cb[48] __aligned(8);

    unsigned long       _skb_refdst;
    void            (*destructor)(struct sk_buff *skb); // 這是析構(gòu)函數(shù)盟戏,后期在skb內(nèi)存銷毀時會用到
    unsigned int        len,                // 表示數(shù)據(jù)區(qū)的總長度: (tail - data)與分片結(jié)構(gòu)體數(shù)據(jù)區(qū)的長度之和绪妹。注意是數(shù)據(jù)的有效長度
                data_len;                   // 只表示分片結(jié)構(gòu)體數(shù)據(jù)區(qū)的長度(skb_shared_info->page指向的數(shù)據(jù)長度),所以len = (tail - data) + data_len    
    __u16           mac_len,
                hdr_len;
    ... ...
    __u16           inner_transport_header; 
    __u16           inner_network_header;
    __u16           inner_mac_header;

    __be16          protocol;               // 這是包的協(xié)議類型抓半,標(biāo)識是IP包還是ARP包或者其他數(shù)據(jù)包喂急。
    __u16           transport_header;       // 指向四層幀頭結(jié)構(gòu)體指針
    __u16           network_header;         // 指向三層IP頭結(jié)構(gòu)體指針
    __u16           mac_header;             // 指向二層mac頭的頭

    /* private: */
    __u32           headers_end[0];
    /* public: */

    /* These elements must be at the end, see alloc_skb() for details.  */
    sk_buff_data_t      tail;               // 指向線性數(shù)據(jù)區(qū)中實際數(shù)據(jù)結(jié)束的位置
    sk_buff_data_t      end;                // 指向線性數(shù)據(jù)區(qū)中結(jié)束的位置(非實際數(shù)據(jù)區(qū)域結(jié)束位置)
    unsigned char       *head,              // 指向線性數(shù)據(jù)區(qū)中開始的位置(非實際數(shù)據(jù)區(qū)域開始位置)
                *data;                      // 指向數(shù)據(jù)區(qū)中實際數(shù)據(jù)開始的位置
    unsigned int        truesize;           // 表示緩沖區(qū)總長度,包括sk_buff自身長度+線性數(shù)據(jù)區(qū)+分片結(jié)構(gòu)體的數(shù)據(jù)區(qū)長度, truesize = len + sizeof(sk_buff) = (data - tail) + data_len + sizeof(sk_buff)
    atomic_t        users;                  // 引用計數(shù)笛求,表明了有多少實體引用了這個skb廊移。其作用就是在銷毀skb結(jié)構(gòu)體時,先查看下users是否為零探入,若不為零狡孔,則調(diào)用函數(shù)遞減下引用計數(shù)users即可;當(dāng)某一次銷毀時蜂嗽,users為零才真正釋放內(nèi)存空間苗膝。有兩個操作函數(shù):atomic_inc()引用計數(shù)增加1;atomic_dec()引用計數(shù)減去1植旧;
};

3-2. sk_buff 線性數(shù)據(jù)區(qū)

sk_buff線性數(shù)據(jù)區(qū):數(shù)據(jù)區(qū)的大小是:(skb->end - skb->head)辱揭;對于每個數(shù)據(jù)包來說這個大小都是固定的,而且在傳輸過程中 skb->endskb->head 所指向的地址都是不變的病附。這塊數(shù)據(jù)區(qū)是用來存放應(yīng)用層發(fā)下來的數(shù)據(jù)和各層的協(xié)議信息问窃。但在計算數(shù)據(jù)長度或者操作協(xié)議信息時,一般都要和實際的數(shù)據(jù)存放指針為準(zhǔn)完沪。實際數(shù)據(jù)指針為data和tail域庇,data指向?qū)嶋H數(shù)據(jù)開始的地方,tail指向?qū)嶋H數(shù)據(jù)結(jié)束的地方覆积。

sk_buff結(jié)構(gòu)體中的指針和數(shù)據(jù)區(qū)關(guān)系:

1.png

包構(gòu)造與數(shù)據(jù)區(qū)變化

  • (1)sk_buff結(jié)構(gòu)數(shù)據(jù)區(qū)剛被申請好听皿,此時head指針、data指針宽档、tail指針都是指向同一個地方尉姨。記住前面講過的:head指針和end指針指向的位置一直都不變,而對于數(shù)據(jù)的變化和協(xié)議信息的添加都是通過data指針和tail指針的改變來表現(xiàn)的吗冤。
  • (2)開始準(zhǔn)備存儲應(yīng)用層下發(fā)過來的數(shù)據(jù)又厉,通過調(diào)用函數(shù) skb_reserve() 來使data指針和tail指針同時向下移動,空出一部分空間來為后期添加協(xié)議信息欣孤。
  • (3)開始存儲數(shù)據(jù)了馋没,通過調(diào)用函數(shù) skb_put() 來使tail指針向下移動空出空間來添加數(shù)據(jù)昔逗,此時 skb->dataskb->tail 之間存放的都是數(shù)據(jù)信息降传,無協(xié)議信息。
  • (4)這時就開始調(diào)用函數(shù) skb_push() 來使data指針向上移動勾怒,空出空間來添加各層協(xié)議信息婆排。直到最后到達二層声旺,添加完幀頭然后就開始發(fā)包了。
2.png

3-3. sk_buff 非線性數(shù)據(jù)區(qū)

skb_shared_info 分片結(jié)構(gòu)體這個分片結(jié)構(gòu)體和sk_buff結(jié)構(gòu)的線性數(shù)據(jù)區(qū)是一體的段只,sk_buff->end指針的下個字節(jié)就是分片結(jié)構(gòu)的開始位置,所以在各種操作時都把他們兩個結(jié)構(gòu)看做是一個來操作腮猖。比如:為sk_buff結(jié)構(gòu)的數(shù)據(jù)區(qū)申請和釋放空間時郭脂,分片結(jié)構(gòu)也會跟著該數(shù)據(jù)區(qū)一起分配和釋放感昼。而克隆時,sk_buff 的數(shù)據(jù)區(qū)和分片結(jié)構(gòu)都由分片結(jié)構(gòu)中的 dataref 成員字段來標(biāo)識是否被引用塌鸯。關(guān)系如下圖所示:

struct skb_shared_info {
    unsigned char   nr_frags;           // 表示有多少個分片結(jié)構(gòu)
    __u8        tx_flags;
    unsigned short  gso_size;
    unsigned short  gso_segs;
    unsigned short  gso_type;           // 分片的類型
    struct sk_buff  *frag_list;         // 這也是一種類型的分配數(shù)據(jù)
    struct skb_shared_hwtstamps hwtstamps;
    u32     tskey;
    __be32          ip6_frag_id;
    atomic_t    dataref;                // 用于數(shù)據(jù)區(qū)的引用計數(shù),克隆一個skb結(jié)構(gòu)體時炕婶,會增加一個引用計數(shù)
    void *      destructor_arg;
    /* must be last field, see pskb_expand_head() */
    skb_frag_t  frags[MAX_SKB_FRAGS];   // 這是個比較重要的數(shù)組姐赡,到講分片結(jié)構(gòu)數(shù)據(jù)區(qū)時會細講
};
3.png

分片結(jié)構(gòu)的非線性數(shù)據(jù)區(qū)skb_shared_info中有個成員字段,skb_frag_t frags[MAX_SKB_FRAGS]柠掂,和分片結(jié)構(gòu)的數(shù)據(jù)區(qū)有關(guān)项滑。

typedef struct skb_frag_struct skb_frag_t;
struct skb_frag_struct {
    struct page *page;  // 指向分片數(shù)據(jù)區(qū)的指針,類似于sk_buff中的data指針
    __u32 page_offset;  // 偏移量涯贞,表示從page指針指向的地方枪狂,偏移page_offset
    __u32 size;         // 數(shù)據(jù)區(qū)的長度,即:sk_buff結(jié)構(gòu)中的data_len
}

有兩種數(shù)據(jù)結(jié)構(gòu)來存儲分片數(shù)據(jù)宋渔,一種是采用frags數(shù)組來存儲分片數(shù)據(jù)區(qū)的指針州疾,一種是用frag_list雙鏈表來存儲。frags一般用在數(shù)據(jù)很多傻谁,且線性數(shù)據(jù)區(qū)放不下的情況孝治,skb_frag_t中是一頁一頁的數(shù)據(jù);對于frag_list审磁,我們在分片的時候裝入每個片的信息谈飒,每個片最終也被封裝成一個小的skb。分別如下圖所示:

4.png
5.png

3-4. sk_buff 指針操作函數(shù)

sk_buff指針操作函數(shù)

  • (1)skb_put():向后擴大數(shù)據(jù)區(qū)空間态蒂,headroom空間不變杭措,tailroom空間減少,skb->data指針不變钾恢,skb->tail指針下移手素;
  • (2)skb_push():向前擴大數(shù)據(jù)區(qū)空間,headroom空間減少瘩蚪,tailroom空間不變泉懦,skb->tail指針不變,skb->data指針上移疹瘦;
  • (3)skb_pull():縮小數(shù)據(jù)區(qū)空間崩哩,headroom空間增大,tailroom空間不變,skb->data指針下移邓嘹,skb->tail指針不變酣栈;
  • (4)skb_reserve():數(shù)據(jù)區(qū)不變,headroom空間增大汹押,tailroom空間減少矿筝,skb->data和skb->tail同時下移;
head-->     |----------|
            | headroom |
data-->     |----------|
            |   data   |
tail-->     |----------|
            | tailroom |
end -->     |----------|

3-5. sk_buff 分配與釋放

skb分配__alloc_skb() 棚贾,通常被三個函數(shù)所調(diào)用 alloc_skb()(常用)窖维、alloc_skb_fclone()(分配克隆的sk_buff結(jié)構(gòu))、dev_alloc_skb()(驅(qū)動中調(diào)用妙痹,申請時不可中斷) —— 參考分配SKB陈辱。

分配SKB時,需要分配兩塊內(nèi)存细诸,一塊是SKB描述符沛贪,一塊是線性數(shù)據(jù)緩存區(qū)(包括線性數(shù)據(jù)區(qū)和skb_shared_info結(jié)構(gòu))。

內(nèi)核對于sk_buff結(jié)構(gòu)的內(nèi)存分配不是和一般的結(jié)構(gòu)動態(tài)內(nèi)存申請一樣:只分配指定大小的內(nèi)存空間震贵。而是在開始的時候利赋,在初始化函數(shù)skb_init()中就分配了兩段內(nèi)存(skbuff_head_cacheskbuff_fclone_cache)來供sk_buff后期申請時用,所以后期要為sk_buff結(jié)構(gòu)動態(tài)申請內(nèi)存時猩系,都會從這兩段內(nèi)存中來申請(其實這不叫申請了媚送,因為這兩段內(nèi)存開始就申請好了的,只是根據(jù)你要的內(nèi)存大小從某個你選定的內(nèi)存段中還回個指針給你罷了)寇甸。如果在這個內(nèi)存段中申請失敗塘偎,則再用內(nèi)核中用最低層,最基本的kmalloc()來申請內(nèi)存了(這才是真正的申請)拿霉。釋放時也一樣吟秩,并不會真正的釋放,只是把數(shù)據(jù)清零绽淘,然后放回內(nèi)存段中涵防,以供下次sk_buff結(jié)構(gòu)的申請。這是內(nèi)核動態(tài)申請的一種策略沪铭,專門為那些經(jīng)常要申請和釋放的結(jié)構(gòu)設(shè)計的壮池,這種策略不僅可以提高申請和釋放時的效率,而且還可以減少內(nèi)存碎片的杀怠。

struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
                int flags, int node)
{
    struct kmem_cache *cache;
    struct skb_shared_info *shinfo;
    struct sk_buff *skb;
    u8 *data;
    bool pfmemalloc;

    cache = (flags & SKB_ALLOC_FCLONE)
        ? skbuff_fclone_cache : skbuff_head_cache;

    if (sk_memalloc_socks() && (flags & SKB_ALLOC_RX))
        gfp_mask |= __GFP_MEMALLOC;

    /* Get the HEAD */
    skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);    // [1] 分配SKB描述符堆塊椰憋,存放sk_buff結(jié)構(gòu)。從高速緩存中分配赔退,DMA有特定用途橙依,所以排除在DMA中分配
    if (!skb)
        goto out;
    prefetchw(skb);

    size = SKB_DATA_ALIGN(size);        // 數(shù)據(jù)對齊
    size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
    data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);          // [2] 分配線性數(shù)據(jù)緩存區(qū):線性數(shù)據(jù)區(qū)+skb_shared_info結(jié)構(gòu)。這里可以從DMA內(nèi)存分配
    if (!data)
        goto nodata;
    size = SKB_WITH_OVERHEAD(ksize(data));
    prefetchw(data + size);

    memset(skb, 0, offsetof(struct sk_buff, tail));
    /* Account for allocated memory : skb + skb->head */
    skb->truesize = SKB_TRUESIZE(size);                                 // [3] skb 初始化
    skb->pfmemalloc = pfmemalloc;
    atomic_set(&skb->users, 1);
    skb->head = data;
    skb->data = data;
    skb_reset_tail_pointer(skb);
    skb->end = skb->tail + size;
    skb->mac_header = (typeof(skb->mac_header))~0U;
    skb->transport_header = (typeof(skb->transport_header))~0U;

    /* make sure we initialize shinfo sequentially */
    shinfo = skb_shinfo(skb);                                           // [4] skb_shared_info 分片結(jié)構(gòu)初始化
    memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
    atomic_set(&shinfo->dataref, 1);
    kmemcheck_annotate_variable(shinfo->destructor_arg);

out:
    return skb;
nodata:
    kmem_cache_free(cache, skb);
    skb = NULL;
    goto out;
}
EXPORT_SYMBOL(__alloc_skb);

skb釋放kfree_skb() -> __kfree_skb() -> skb_release_all() -> skb_release_data()

如果skb->users==1,表明是最后一個引用該結(jié)構(gòu)的票编,可以調(diào)用__kfree_skb()函數(shù)直接釋放。當(dāng)skb釋放掉后卵渴,dst_release同樣會被調(diào)用以減小相關(guān)dst_entry數(shù)據(jù)結(jié)構(gòu)的引用計數(shù)慧域。如果 skb->destructor(skb的析構(gòu)函數(shù))被初始化過,相應(yīng)的函數(shù)會在此時被調(diào)用浪读。還有分片結(jié)構(gòu)體 skb_shared_info 也會相應(yīng)的被釋放掉昔榴,然后把所有內(nèi)存空間全部返還到 skbuff_head_cache 緩存池中,這些操作都是由 kfree_skbmem() 函數(shù)來完成的碘橘。這里分片的釋放涉及到了克隆問題:如果skb沒有被克隆互订,數(shù)據(jù)區(qū)也沒有其他skb引用,則直接釋放即可痘拆;如果是克隆了skb結(jié)構(gòu)仰禽,則當(dāng)克隆數(shù)計數(shù)為1時,才能釋放skb結(jié)構(gòu)體纺蛆;如果分片結(jié)構(gòu)被克隆了吐葵,那么也要等到分片克隆計數(shù)為1時,才能釋放掉分片數(shù)據(jù)結(jié)構(gòu)桥氏。如果skb是從 skbuff_fclone_cache 緩存池中申請的內(nèi)存時温峭,則要仔細銷毀過程了,因為從這個緩存池中申請的內(nèi)存字支,會返還2個skb結(jié)構(gòu)體和一個引用計數(shù)器凤藏。所以銷毀時不僅要考慮克隆問題還要考慮2個skb的釋放順序。

void kfree_skb(struct sk_buff *skb)
{
    if (unlikely(!skb))
        return;
    if (likely(atomic_read(&skb->users) == 1))          // [1] 如果 skb->users 不為1堕伪,則 skb->users 只是減1揖庄,表明減少一次引用。
        smp_rmb();
    else if (likely(!atomic_dec_and_test(&skb->users)))
        return;
    trace_kfree_skb(skb, __builtin_return_address(0));
    __kfree_skb(skb);
}
EXPORT_SYMBOL(kfree_skb);

參考

https://nvd.nist.gov/vuln/detail/CVE-2017-6074

利用漏洞CVE-2017-6074獲取root權(quán)限

【漏洞預(yù)警】雪藏11年:Linux kernel DCCP double-free 權(quán)限提升漏洞(CVE-2017-6074)

https://www.openwall.com/lists/oss-security/2017/02/26/2

https://github.com/xairy/kernel-exploits/tree/master/CVE-2017-6074

ftrace: trace your kernel functions!

【技術(shù)分享】CVE-2016-8655內(nèi)核競爭條件漏洞調(diào)試分析

What I Learnt From the CVE-2016-8655 Exploit

sk_buff 整理筆記(一欠雌、數(shù)據(jù)結(jié)構(gòu))

sk_buff整理筆記(二抠艾、操作函數(shù))

sk_buff整理筆記(三、內(nèi)存申請和釋放)

sk_buff整理筆記(四桨昙、克隆與復(fù)制)

sk_buff整理筆記(五检号、隊列管理函數(shù))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蛙酪,隨后出現(xiàn)的幾起案子齐苛,更是在濱河造成了極大的恐慌,老刑警劉巖桂塞,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凹蜂,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機玛痊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門汰瘫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人擂煞,你說我怎么就攤上這事混弥。” “怎么了对省?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵蝗拿,是天一觀的道長。 經(jīng)常有香客問我蒿涎,道長哀托,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任劳秋,我火速辦了婚禮仓手,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘玻淑。我一直安慰自己俗或,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布岁忘。 她就那樣靜靜地躺著辛慰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪干像。 梳的紋絲不亂的頭發(fā)上帅腌,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機與錄音麻汰,去河邊找鬼速客。 笑死,一個胖子當(dāng)著我的面吹牛五鲫,可吹牛的內(nèi)容都是我干的溺职。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼位喂,長吁一口氣:“原來是場噩夢啊……” “哼浪耘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起塑崖,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤七冲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后规婆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澜躺,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蝉稳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掘鄙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耘戚。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖操漠,靈堂內(nèi)的尸體忽然破棺而出收津,到底是詐尸還是另有隱情,我是刑警寧澤颅夺,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站蛹稍,受9級特大地震影響吧黄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜唆姐,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一拗慨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奉芦,春花似錦赵抢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至先巴,卻和暖如春其爵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伸蚯。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工摩渺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剂邮。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓摇幻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親挥萌。 傳聞我的和親對象是個殘疾皇子绰姻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349