Dig into Netfilter (一) —— nf_tables核心概念

xtables? iptables? nf_tables !

nf_tables核心概念

源码版本为v5.8或v5.11

概述

根据作用及协议族, 在netfilter中分成不同的table., 每个table只能看到自己所属协议族的包.
每个table在其有的每一个hook点上有一条chain. 用户也可以添加自定义的chain, 但由于没有对应的hook点, 只能从内置的chain的rule中跳转到(其实从自定义的chain跳转到也行, 前提是前者能被触发). 实际上之前内置chain的说法并不准确, 不同于iptables, nft并不存在预定义的chain, 需要创建nft_base_chain并将其注册到hook点上.
多条rule可能在同一个hook点触发, 即属于同一条chain.
处理一条rule的过程实际是对rule中包含的expr求值(eval).

举个例子, 拦截收到的所有目标端口是5000的ipv4数据包.
由于作用是拦截(过滤)ipv4, 所以对应的table应该是ip族的某个tale..
拦截的hook点应该是在NF_IP_PRE_ROUTING, 那么chain就对应filter表中的PREROUTING.
“拦截所有目标端口是5000的ipv4数据包”其实就是rule,
为了实现这一rule, 需要的expr有获取数据包中的端口, 将其与5000比较, 若相同则DROP.
执行过程以状态机的形式实现, expr是一条”指令”, rule是一个实现特定的目的”函数”, chain是一系列函数的调用点, 以nf_hook_ops注册到hook点上. 执行一个chain的过程使用一个寄存器集合. 数据包是该状态机的输入输出设备.

规则执行

每个table的base_chain具有类型nft_chain_type(面向对象), 指示这个table的协议, 注册到各个hook点的nf_hook_ops等.
当base_chain被创建时, base_chain->ops->hook被设置为type->hooks[ops->hooknum].
并通过nf_tables_register_hook(net, table, chain);在对应hook点进行注册.

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
static const struct nft_chain_type nft_chain_nat_inet = {
.name = "nat",
.type = NFT_CHAIN_T_NAT,
.family = NFPROTO_INET,
.owner = THIS_MODULE,
.hook_mask = (1 << NF_INET_PRE_ROUTING) |
(1 << NF_INET_LOCAL_IN) |
(1 << NF_INET_LOCAL_OUT) |
(1 << NF_INET_POST_ROUTING),
.hooks = {
[NF_INET_PRE_ROUTING] = nft_nat_do_chain,
[NF_INET_LOCAL_IN] = nft_nat_do_chain,
[NF_INET_LOCAL_OUT] = nft_nat_do_chain,
[NF_INET_POST_ROUTING] = nft_nat_do_chain,
},
.ops_register = nft_nat_inet_reg,
.ops_unregister = nft_nat_inet_unreg,
};

static void nft_basechain_hook_init(struct nf_hook_ops *ops, u8 family,
const struct nft_chain_hook *hook,
struct nft_chain *chain)
{
ops->pf = family;
ops->hooknum = hook->num;
ops->priority = hook->priority;
ops->priv = chain;
ops->hook = hook->type->hooks[ops->hooknum];
ops->hook_ops_type = NF_HOOK_OP_NF_TABLES;
}

static int nf_tables_register_hook(struct net *net,
const struct nft_table *table,
struct nft_chain *chain)
{
struct nft_base_chain *basechain;
const struct nf_hook_ops *ops;

if (table->flags & NFT_TABLE_F_DORMANT ||
!nft_is_base_chain(chain))
return 0;

basechain = nft_base_chain(chain);
ops = &basechain->ops;

if (basechain->type->ops_register)
return basechain->type->ops_register(net, ops);

if (nft_base_chain_netdev(table->family, basechain->ops.hooknum))
return nft_netdev_register_hooks(net, &basechain->hook_list);

return nf_register_net_hook(net, &basechain->ops);

nf_register_net_hook在netns_nf在nf_hook_entries对应的hook点处添加一个hook.

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
int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
{
int err;

if (reg->pf == NFPROTO_INET) {
if (reg->hooknum == NF_INET_INGRESS) {
err = __nf_register_net_hook(net, NFPROTO_INET, reg);
if (err < 0)
return err;
} else {
err = __nf_register_net_hook(net, NFPROTO_IPV4, reg);
if (err < 0)
return err;

err = __nf_register_net_hook(net, NFPROTO_IPV6, reg);
if (err < 0) {
__nf_unregister_net_hook(net, NFPROTO_IPV4, reg);
return err;
}
}
} else {
err = __nf_register_net_hook(net, reg->pf, reg);
if (err < 0)
return err;
}

return 0;
}
EXPORT_SYMBOL(nf_register_net_hook);

当到达某个hook点, 会调用NF_HOOK或NF_HOOK_LIST调用该hook点所有的hook函数.
根据nf_hook的返回值是否为1, 决定是否允许这个包继续通行.若可以继续通行, 调用okfn.如果不能继续通行, 则直接返回, 数据包将不会进行下一阶段的处理. 数据包处理阶段的衔接是通过okfn完成的.
比如在ip_recv中, okfn是ip_rcv_finish, 然后将数据包放行到ip_local_deliver, 进入下一阶段的处理.

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
static inline int
NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb,
struct net_device *in, struct net_device *out,
int (*okfn)(struct net *, struct sock *, struct sk_buff *))
{
int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn);
if (ret == 1)
ret = okfn(net, sk, skb);
return ret;
}

/*
* IP receive entry point
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
struct net_device *orig_dev)
{
struct net *net = dev_net(dev);

skb = ip_rcv_core(skb, net);
if (skb == NULL)
return NET_RX_DROP;

return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
net, NULL, skb, dev, NULL,
ip_rcv_finish);
}

static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
int ret;

/* if ingress device is enslaved to an L3 master device pass the
* skb to its handler for processing
*/
skb = l3mdev_ip_rcv(skb);
if (!skb)
return NET_RX_SUCCESS;

ret = ip_rcv_finish_core(net, sk, skb, dev, NULL);
if (ret != NET_RX_DROP)
ret = dst_input(skb);
return ret;
}


/* Input packet from network to transport. */
static inline int dst_input(struct sk_buff *skb)
{
return INDIRECT_CALL_INET(skb_dst(skb)->input,
ip6_input, ip_local_deliver, skb);
}

nf_hook函数根据协议类型和hook点, 从netns_nf取出对应的nf_hook_entries, 转入nf_hook_slow函数

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

/**
* nf_hook - call a netfilter hook
*
* Returns 1 if the hook has allowed the packet to pass. The function
* okfn must be invoked by the caller in this case. Any other return
* value indicates the packet has been consumed by the hook.
*/
static inline int nf_hook(u_int8_t pf, unsigned int hook, struct net *net,
struct sock *sk, struct sk_buff *skb,
struct net_device *indev, struct net_device *outdev,
int (*okfn)(struct net *, struct sock *, struct sk_buff *))
{
struct nf_hook_entries *hook_head = NULL;
int ret = 1;

#ifdef CONFIG_JUMP_LABEL
if (__builtin_constant_p(pf) &&
__builtin_constant_p(hook) &&
!static_key_false(&nf_hooks_needed[pf][hook]))
return 1;
#endif

rcu_read_lock();
switch (pf) {
case NFPROTO_IPV4:
hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]);
break;
case NFPROTO_IPV6:
hook_head = rcu_dereference(net->nf.hooks_ipv6[hook]);
break;
case NFPROTO_ARP:
#ifdef CONFIG_NETFILTER_FAMILY_ARP
if (WARN_ON_ONCE(hook >= ARRAY_SIZE(net->nf.hooks_arp)))
break;
hook_head = rcu_dereference(net->nf.hooks_arp[hook]);
#endif
break;
case NFPROTO_BRIDGE:
#ifdef CONFIG_NETFILTER_FAMILY_BRIDGE
hook_head = rcu_dereference(net->nf.hooks_bridge[hook]);
#endif
break;
default:
WARN_ON_ONCE(1);
break;
}

if (hook_head) {
struct nf_hook_state state;

nf_hook_state_init(&state, hook, pf, indev, outdev,
sk, net, okfn);

ret = nf_hook_slow(skb, &state, hook_head, 0);
}
rcu_read_unlock();

return ret;
}

struct netns_nf {
#if defined CONFIG_PROC_FS
struct proc_dir_entry *proc_netfilter;
#endif
const struct nf_logger __rcu *nf_loggers[NFPROTO_NUMPROTO];
#ifdef CONFIG_SYSCTL
struct ctl_table_header *nf_log_dir_header;
#endif
struct nf_hook_entries __rcu *hooks_ipv4[NF_INET_NUMHOOKS];
struct nf_hook_entries __rcu *hooks_ipv6[NF_INET_NUMHOOKS];
#ifdef CONFIG_NETFILTER_FAMILY_ARP
struct nf_hook_entries __rcu *hooks_arp[NF_ARP_NUMHOOKS];
#endif
#ifdef CONFIG_NETFILTER_FAMILY_BRIDGE
struct nf_hook_entries __rcu *hooks_bridge[NF_INET_NUMHOOKS];
#endif
#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV4)
unsigned int defrag_ipv4_users;
#endif
#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)
unsigned int defrag_ipv6_users;
#endif
};
#endif

nf_hook_slow遍历nf_hook_entries中的每一个hook, 调用hookfn. 并根据返回的verdict决定数据包的进一步处理.

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
/* Returns 1 if okfn() needs to be executed by the caller,
* -EPERM for NF_DROP, 0 otherwise. Caller must hold rcu_read_lock. */
int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,
const struct nf_hook_entries *e, unsigned int s)
{
unsigned int verdict;
int ret;

for (; s < e->num_hook_entries; s++) {
verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);
switch (verdict & NF_VERDICT_MASK) {
case NF_ACCEPT:
break;
case NF_DROP:
kfree_skb_reason(skb,
SKB_DROP_REASON_NETFILTER_DROP);
ret = NF_DROP_GETERR(verdict);
if (ret == 0)
ret = -EPERM;
return ret;
case NF_QUEUE:
ret = nf_queue(skb, state, s, verdict);
if (ret == 1)
continue;
return ret;
default:
/* Implicit handling for NF_STOLEN, as well as any other
* non conventional verdicts.
*/
return 0;
}
}

return 1;
}
EXPORT_SYMBOL(nf_hook_slow);

通过base_chain创建时注册的hook, 在这里进入到对一个chain中一系列rule的执行, 即状态机从这里开始运行.比如对于nft_chain_filter_ipv4类型的chain来说, 这里注册的hook函数是nft_do_chain_ipv4.做一些特化的工作后, 转到nft_do_chain函数.

1
2
3
4
5
6
7
8
9
10
11
12
static unsigned int nft_do_chain_ipv4(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct nft_pktinfo pkt;

nft_set_pktinfo(&pkt, skb, state);
nft_set_pktinfo_ipv4(&pkt);

return nft_do_chain(&pkt, priv);
}

nft_do_chain函数遍历chain中的rule中的每一条expr, 进行求值. 根据每次求值后的verdict决定是否继续求值或如何继续求值(状态机执行流的分支).

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
unsigned int
nft_do_chain(struct nft_pktinfo *pkt, void *priv)
{
const struct nft_chain *chain = priv, *basechain = chain;
const struct nft_rule_dp *rule, *last_rule;
const struct net *net = nft_net(pkt);
const struct nft_expr *expr, *last;
struct nft_regs regs = {};
unsigned int stackptr = 0;
struct nft_jumpstack jumpstack[NFT_JUMP_STACK_SIZE];
bool genbit = READ_ONCE(net->nft.gencursor);
struct nft_rule_blob *blob;
struct nft_traceinfo info;

info.trace = false;
if (static_branch_unlikely(&nft_trace_enabled))
nft_trace_init(&info, pkt, &regs.verdict, basechain);
do_chain:
if (genbit)
blob = rcu_dereference(chain->blob_gen_1);
else
blob = rcu_dereference(chain->blob_gen_0);

rule = (struct nft_rule_dp *)blob->data;
last_rule = (void *)blob->data + blob->size;
next_rule:
regs.verdict.code = NFT_CONTINUE;
for (; rule < last_rule; rule = nft_rule_next(rule)) {
nft_rule_dp_for_each_expr(expr, last, rule) {
if (expr->ops == &nft_cmp_fast_ops)
nft_cmp_fast_eval(expr, &regs);
else if (expr->ops == &nft_cmp16_fast_ops)
nft_cmp16_fast_eval(expr, &regs);
else if (expr->ops == &nft_bitwise_fast_ops)
nft_bitwise_fast_eval(expr, &regs);
else if (expr->ops != &nft_payload_fast_ops ||
!nft_payload_fast_eval(expr, &regs, pkt))
expr_call_ops_eval(expr, &regs, pkt);

if (regs.verdict.code != NFT_CONTINUE)
break;
}

switch (regs.verdict.code) {
case NFT_BREAK:
regs.verdict.code = NFT_CONTINUE;
nft_trace_copy_nftrace(pkt, &info);
continue;
case NFT_CONTINUE:
nft_trace_packet(pkt, &info, chain, rule,
NFT_TRACETYPE_RULE);
continue;
}
break;
}

nft_trace_verdict(&info, chain, rule, &regs);

switch (regs.verdict.code & NF_VERDICT_MASK) {
case NF_ACCEPT:
case NF_DROP:
case NF_QUEUE:
case NF_STOLEN:
return regs.verdict.code;
}

switch (regs.verdict.code) {
case NFT_JUMP:
if (WARN_ON_ONCE(stackptr >= NFT_JUMP_STACK_SIZE))
return NF_DROP;
jumpstack[stackptr].chain = chain;
jumpstack[stackptr].rule = nft_rule_next(rule);
jumpstack[stackptr].last_rule = last_rule;
stackptr++;
fallthrough;
case NFT_GOTO:
chain = regs.verdict.chain;
goto do_chain;
case NFT_CONTINUE:
case NFT_RETURN:
break;
default:
WARN_ON_ONCE(1);
}

if (stackptr > 0) {
stackptr--;
chain = jumpstack[stackptr].chain;
rule = jumpstack[stackptr].rule;
last_rule = jumpstack[stackptr].last_rule;
goto next_rule;
}

nft_trace_packet(pkt, &info, basechain, NULL, NFT_TRACETYPE_POLICY);

if (static_branch_unlikely(&nft_counters_enabled))
nft_update_chain_stats(basechain, pkt);

return nft_base_chain(basechain)->policy;
}
EXPORT_SYMBOL_GPL(nft_do_chain);

新增规则

通过netlink发起新增规则的请求会调用到nf_tables_newrule函数. 先通过nlattr中的NFTA_RULE_TABLE, NFTA_RULE_CHAIN两个字段查找对应table中的对应chain.

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
static int nf_tables_newrule(struct net *net, struct sock *nlsk,
struct sk_buff *skb, const struct nlmsghdr *nlh,
const struct nlattr * const nla[],
struct netlink_ext_ack *extack)
{
const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
u8 genmask = nft_genmask_next(net);
struct nft_expr_info *info = NULL;
int family = nfmsg->nfgen_family;
struct nft_flow_rule *flow;
struct nft_table *table;
struct nft_chain *chain;
struct nft_rule *rule, *old_rule = NULL;
struct nft_userdata *udata;
struct nft_trans *trans = NULL;
struct nft_expr *expr;
struct nft_ctx ctx;
struct nlattr *tmp;
unsigned int size, i, n, ulen = 0, usize = 0;
int err, rem;
u64 handle, pos_handle;

lockdep_assert_held(&net->nft.commit_mutex);

table = nft_table_lookup(net, nla[NFTA_RULE_TABLE], family, genmask);
if (IS_ERR(table)) {
NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_TABLE]);
return PTR_ERR(table);
}

chain = nft_chain_lookup(net, table, nla[NFTA_RULE_CHAIN], genmask);
if (IS_ERR(chain)) {
NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_CHAIN]);
return PTR_ERR(chain);
}

接下来是个分支.

  • 如果在nla中指定了NFTA_RULE_HANDLE, 意味着是要在已有的rule上进行替换.通过查找NFTA_RULE_HANDLE值找到对应rule, 并检查nlh->nlmsg_flags是否允许在已有规则上操作.
  • 否则是添加一个新的规则. 同样检查nlh->nlmsg_flags后, 分配一个handle值(实际就是++table->hgenerator). 然后获取上一条规则(即确定插入位置), 有两种获取方式, 通过handle值NFTA_RULE_POSITION或上一条规则被添加时的NFTA_RULE_POSITION_ID.
    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
    if (nla[NFTA_RULE_HANDLE]) {
    handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_HANDLE]));
    rule = __nft_rule_lookup(chain, handle);
    if (IS_ERR(rule)) {
    NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_HANDLE]);
    return PTR_ERR(rule);
    }

    if (nlh->nlmsg_flags & NLM_F_EXCL) {
    NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_HANDLE]);
    return -EEXIST;
    }
    if (nlh->nlmsg_flags & NLM_F_REPLACE)
    old_rule = rule;
    else
    return -EOPNOTSUPP;
    } else {
    if (!(nlh->nlmsg_flags & NLM_F_CREATE) ||
    nlh->nlmsg_flags & NLM_F_REPLACE)
    return -EINVAL;
    handle = nf_tables_alloc_handle(table);

    if (chain->use == UINT_MAX)
    return -EOVERFLOW;

    if (nla[NFTA_RULE_POSITION]) {
    pos_handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_POSITION]));
    old_rule = __nft_rule_lookup(chain, pos_handle);
    if (IS_ERR(old_rule)) {
    NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_POSITION]);
    return PTR_ERR(old_rule);
    }
    } else if (nla[NFTA_RULE_POSITION_ID]) {
    old_rule = nft_rule_lookup_byid(net, nla[NFTA_RULE_POSITION_ID]);
    if (IS_ERR(old_rule)) {
    NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_POSITION_ID]);
    return PTR_ERR(old_rule);
    }
    }
    }

接下来进入到实际rule的解析部分.
先是表达式解析, 代码比较难理解.

一个表达式属性具有多种属性, 所以表达式属性本身的类型是一个嵌套属性.而nla[NFTA_RULE_EXPRESSIONS]中存储了一条rule中的所有表达式属性, 其类型是嵌套属性数组(虽然nft_policy中表示其类型是嵌套属性, 但笔者认为应该是后者).

NL_ATTR_TYPE_NESTED
nested, i.e. the content of this attribute consists of sub-attributes. The nested policy and maxtype inside may be specified.

NL_ATTR_TYPE_NESTED_ARRAY
nested array, i.e. the content of this attribute contains sub-attributes whose type is irrelevant (just used to separate the array entries) and each such array entry has attributes again, the policy for those inner ones and the corresponding maxtype may be specified.

先分配NFT_RULE_MAXEXPRS个nft_expr_info的空间, 遍历nla[NFTA_RULE_EXPRESSIONS]嵌套属性数组中的嵌套属性, 调用nf_tables_expr_parse函数进行parse, parse的结果即存储在nft_expr_info中.

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
nft_ctx_init(&ctx, net, skb, nlh, family, table, chain, nla);

n = 0;
size = 0;
if (nla[NFTA_RULE_EXPRESSIONS]) {
info = kvmalloc_array(NFT_RULE_MAXEXPRS,
sizeof(struct nft_expr_info),
GFP_KERNEL);
if (!info)
return -ENOMEM;

nla_for_each_nested(tmp, nla[NFTA_RULE_EXPRESSIONS], rem) {
err = -EINVAL;
if (nla_type(tmp) != NFTA_LIST_ELEM)
goto err1;
if (n == NFT_RULE_MAXEXPRS)
goto err1;
err = nf_tables_expr_parse(&ctx, tmp, &info[n]);
if (err < 0)
goto err1;
size += info[n].ops->size;
n++;
}
}
/* Check for overflow of dlen field */
err = -EFBIG;
if (size >= 1 << 12)
goto err1;

审计到这里时, 发现ulen来自属性NFTA_RULE_USERDATA且在此处缺乏验证,若可控可能会在kzalloc处造成整型溢出. 但实际上对于每一种属性, 都在对应的nla_policy中有相应的类型定义和限制. 如nft_rule_policy中就限制了ulen的最大值为NFT_USERDATA_MAXLEN.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (nla[NFTA_RULE_USERDATA]) {
ulen = nla_len(nla[NFTA_RULE_USERDATA]);
if (ulen > 0)
usize = sizeof(struct nft_userdata) + ulen;
}

err = -ENOMEM;
rule = kzalloc(sizeof(*rule) + size + usize, GFP_KERNEL);
if (rule == NULL)
goto err1;

nft_activate_next(net, rule);

rule->handle = handle;
rule->dlen = size;
rule->udata = ulen ? 1 : 0;

if (ulen) {
udata = nft_userdata(rule);
udata->len = ulen - 1;
nla_memcpy(udata->data, nla[NFTA_RULE_USERDATA], ulen);
}

根据nft_expr_info数组生成对应的expr. nf_tables_newexpr函数设置expr->ops=info->ops, 并调用ops->init函数构造表达式.

1
2
3
4
5
6
7
8
9
10
11
12
expr = nft_expr_first(rule);
for (i = 0; i < n; i++) {
err = nf_tables_newexpr(&ctx, &info[i], expr);
if (err < 0)
goto err2;

if (info[i].ops->validate)
nft_validate_state_update(net, NFT_VALIDATE_NEED);

info[i].ops = NULL;
expr = nft_expr_next(expr);
}

将rule加入或替换到chain中, 做一些收尾工作.

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
	if (nlh->nlmsg_flags & NLM_F_REPLACE) {
trans = nft_trans_rule_add(&ctx, NFT_MSG_NEWRULE, rule);
if (trans == NULL) {
err = -ENOMEM;
goto err2;
}
err = nft_delrule(&ctx, old_rule);
if (err < 0) {
nft_trans_destroy(trans);
goto err2;
}

list_add_tail_rcu(&rule->list, &old_rule->list);
} else {
trans = nft_trans_rule_add(&ctx, NFT_MSG_NEWRULE, rule);
if (!trans) {
err = -ENOMEM;
goto err2;
}

if (nlh->nlmsg_flags & NLM_F_APPEND) {
if (old_rule)
list_add_rcu(&rule->list, &old_rule->list);
else
list_add_tail_rcu(&rule->list, &chain->rules);
} else {
if (old_rule)
list_add_tail_rcu(&rule->list, &old_rule->list);
else
list_add_rcu(&rule->list, &chain->rules);
}
}
kvfree(info);
chain->use++;

if (net->nft.validate_state == NFT_VALIDATE_DO)
return nft_table_validate(net, table);

if (chain->flags & NFT_CHAIN_HW_OFFLOAD) {
flow = nft_flow_rule_create(net, rule);
if (IS_ERR(flow))
return PTR_ERR(flow);

nft_trans_flow_rule(trans) = flow;
}

return 0;
err2:
nf_tables_rule_release(&ctx, rule);
err1:
for (i = 0; i < n; i++) {
if (info[i].ops) {
module_put(info[i].ops->type->owner);
if (info[i].ops->type->release_ops)
info[i].ops->type->release_ops(info[i].ops);
}
}
kvfree(info);
return err;
}

下面来看看一个表达式嵌套属性的parse过程.
nf_tables_expr_parse函数.

  • 调用nla_parse_nested_deprecated将嵌套属性nla中的各属性起始位置解析到tb中, tb是nlattr类型的指针数组.
  • 根据ctx->net, ctx->family, tb[NFTA_EXPR_NAME]获取nft_expr_type.
  • 如果有tb[NFTA_EXPR_DATA]嵌套属性, 调用nla_parse_nested_deprecated将其parse到nft_expr_info->tb, 否则将info->tb清空.
  • 设置info->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
    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
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    static int nf_tables_expr_parse(const struct nft_ctx *ctx,
    const struct nlattr *nla,
    struct nft_expr_info *info)
    {
    const struct nft_expr_type *type;
    const struct nft_expr_ops *ops;
    struct nlattr *tb[NFTA_EXPR_MAX + 1];
    int err;

    err = nla_parse_nested_deprecated(tb, NFTA_EXPR_MAX, nla,
    nft_expr_policy, NULL);
    if (err < 0)
    return err;

    type = nft_expr_type_get(ctx->net, ctx->family, tb[NFTA_EXPR_NAME]);
    if (IS_ERR(type))
    return PTR_ERR(type);

    if (tb[NFTA_EXPR_DATA]) {
    err = nla_parse_nested_deprecated(info->tb, type->maxattr,
    tb[NFTA_EXPR_DATA],
    type->policy, NULL);
    if (err < 0)
    goto err1;
    } else
    memset(info->tb, 0, sizeof(info->tb[0]) * (type->maxattr + 1));

    if (type->select_ops != NULL) {
    ops = type->select_ops(ctx,
    (const struct nlattr * const *)info->tb);
    if (IS_ERR(ops)) {
    err = PTR_ERR(ops);
    #ifdef CONFIG_MODULES
    if (err == -EAGAIN)
    if (nft_expr_type_request_module(ctx->net,
    ctx->family,
    tb[NFTA_EXPR_NAME]) != -EAGAIN)
    err = -ENOENT;
    #endif
    goto err1;
    }
    } else
    ops = type->ops;

    info->ops = ops;
    return 0;

    err1:
    module_put(type->owner);
    return err;
    }

    /**
    * nla_parse_nested_deprecated - parse nested attributes
    * @tb: destination array with maxtype+1 elements
    * @maxtype: maximum attribute type to be expected
    * @nla: attribute containing the nested attributes
    * @policy: validation policy
    * @extack: extended ACK report struct
    *
    * See nla_parse_deprecated()
    */
    static inline int nla_parse_nested_deprecated(struct nlattr *tb[], int maxtype,
    const struct nlattr *nla,
    const struct nla_policy *policy,
    struct netlink_ext_ack *extack)
    {
    return __nla_parse(tb, maxtype, nla_data(nla), nla_len(nla), policy,
    NL_VALIDATE_LIBERAL, extack);
    }

    /**
    * __nla_parse - Parse a stream of attributes into a tb buffer
    * @tb: destination array with maxtype+1 elements
    * @maxtype: maximum attribute type to be expected
    * @head: head of attribute stream
    * @len: length of attribute stream
    * @policy: validation policy
    * @validate: validation strictness
    * @extack: extended ACK pointer
    *
    * Parses a stream of attributes and stores a pointer to each attribute in
    * the tb array accessible via the attribute type.
    * Validation is controlled by the @validate parameter.
    *
    * Returns 0 on success or a negative error code.
    */
    int __nla_parse(struct nlattr **tb, int maxtype,
    const struct nlattr *head, int len,
    const struct nla_policy *policy, unsigned int validate,
    struct netlink_ext_ack *extack)
    {
    return __nla_validate_parse(head, len, maxtype, policy, validate,
    extack, tb, 0);
    }
    EXPORT_SYMBOL(__nla_parse);

    static int __nla_validate_parse(const struct nlattr *head, int len, int maxtype,
    const struct nla_policy *policy,
    unsigned int validate,
    struct netlink_ext_ack *extack,
    struct nlattr **tb, unsigned int depth)
    {
    const struct nlattr *nla;
    int rem;

    if (depth >= MAX_POLICY_RECURSION_DEPTH) {
    NL_SET_ERR_MSG(extack,
    "allowed policy recursion depth exceeded");
    return -EINVAL;
    }

    if (tb)
    memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1));

    nla_for_each_attr(nla, head, len, rem) {
    u16 type = nla_type(nla);

    if (type == 0 || type > maxtype) {
    if (validate & NL_VALIDATE_MAXTYPE) {
    NL_SET_ERR_MSG_ATTR(extack, nla,
    "Unknown attribute type");
    return -EINVAL;
    }
    continue;
    }
    if (policy) {
    int err = validate_nla(nla, maxtype, policy,
    validate, extack, depth);

    if (err < 0)
    return err;
    }

    if (tb)
    tb[type] = (struct nlattr *)nla;
    }

    if (unlikely(rem > 0)) {
    pr_warn_ratelimited("netlink: %d bytes leftover after parsing attributes in process `%s'.\n",
    rem, current->comm);
    NL_SET_ERR_MSG(extack, "bytes leftover after parsing attributes");
    if (validate & NL_VALIDATE_TRAILING)
    return -EINVAL;
    }

    return 0;
    }

最终在validate_nla中完成对单个属性的校验, 校验依据是对应的nla_policy.

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
static int validate_nla(const struct nlattr *nla, int maxtype,
const struct nla_policy *policy, unsigned int validate,
struct netlink_ext_ack *extack, unsigned int depth)
{
u16 strict_start_type = policy[0].strict_start_type;
const struct nla_policy *pt;
int minlen = 0, attrlen = nla_len(nla), type = nla_type(nla);
int err = -ERANGE;

if (strict_start_type && type >= strict_start_type)
validate |= NL_VALIDATE_STRICT;

if (type <= 0 || type > maxtype)
return 0;

pt = &policy[type];

BUG_ON(pt->type > NLA_TYPE_MAX);

if ((nla_attr_len[pt->type] && attrlen != nla_attr_len[pt->type]) ||
(pt->type == NLA_EXACT_LEN &&
pt->validation_type == NLA_VALIDATE_WARN_TOO_LONG &&
attrlen != pt->len)) {
pr_warn_ratelimited("netlink: '%s': attribute type %d has an invalid length.\n",
current->comm, type);
if (validate & NL_VALIDATE_STRICT_ATTRS) {
NL_SET_ERR_MSG_ATTR(extack, nla,
"invalid attribute length");
return -EINVAL;
}
}

if (validate & NL_VALIDATE_NESTED) {
if ((pt->type == NLA_NESTED || pt->type == NLA_NESTED_ARRAY) &&
!(nla->nla_type & NLA_F_NESTED)) {
NL_SET_ERR_MSG_ATTR(extack, nla,
"NLA_F_NESTED is missing");
return -EINVAL;
}
if (pt->type != NLA_NESTED && pt->type != NLA_NESTED_ARRAY &&
pt->type != NLA_UNSPEC && (nla->nla_type & NLA_F_NESTED)) {
NL_SET_ERR_MSG_ATTR(extack, nla,
"NLA_F_NESTED not expected");
return -EINVAL;
}
}

switch (pt->type) {
case NLA_REJECT:
if (extack && pt->reject_message) {
NL_SET_BAD_ATTR(extack, nla);
extack->_msg = pt->reject_message;
return -EINVAL;
}
err = -EINVAL;
goto out_err;

case NLA_FLAG:
if (attrlen > 0)
goto out_err;
break;

case NLA_BITFIELD32:
if (attrlen != sizeof(struct nla_bitfield32))
goto out_err;

err = validate_nla_bitfield32(nla, pt->bitfield32_valid);
if (err)
goto out_err;
break;

case NLA_NUL_STRING:
if (pt->len)
minlen = min_t(int, attrlen, pt->len + 1);
else
minlen = attrlen;

if (!minlen || memchr(nla_data(nla), '\0', minlen) == NULL) {
err = -EINVAL;
goto out_err;
}
/* fall through */

case NLA_STRING:
if (attrlen < 1)
goto out_err;

if (pt->len) {
char *buf = nla_data(nla);

if (buf[attrlen - 1] == '\0')
attrlen--;

if (attrlen > pt->len)
goto out_err;
}
break;

case NLA_BINARY:
if (pt->len && attrlen > pt->len)
goto out_err;
break;

case NLA_NESTED:
/* a nested attributes is allowed to be empty; if its not,
* it must have a size of at least NLA_HDRLEN.
*/
if (attrlen == 0)
break;
if (attrlen < NLA_HDRLEN)
goto out_err;
if (pt->nested_policy) {
err = __nla_validate_parse(nla_data(nla), nla_len(nla),
pt->len, pt->nested_policy,
validate, extack, NULL,
depth + 1);
if (err < 0) {
/*
* return directly to preserve the inner
* error message/attribute pointer
*/
return err;
}
}
break;
case NLA_NESTED_ARRAY:
/* a nested array attribute is allowed to be empty; if its not,
* it must have a size of at least NLA_HDRLEN.
*/
if (attrlen == 0)
break;
if (attrlen < NLA_HDRLEN)
goto out_err;
if (pt->nested_policy) {
int err;

err = nla_validate_array(nla_data(nla), nla_len(nla),
pt->len, pt->nested_policy,
extack, validate, depth);
if (err < 0) {
/*
* return directly to preserve the inner
* error message/attribute pointer
*/
return err;
}
}
break;

case NLA_UNSPEC:
if (validate & NL_VALIDATE_UNSPEC) {
NL_SET_ERR_MSG_ATTR(extack, nla,
"Unsupported attribute");
return -EINVAL;
}
/* fall through */
case NLA_MIN_LEN:
if (attrlen < pt->len)
goto out_err;
break;

case NLA_EXACT_LEN:
if (pt->validation_type != NLA_VALIDATE_WARN_TOO_LONG) {
if (attrlen != pt->len)
goto out_err;
break;
}
/* fall through */
default:
if (pt->len)
minlen = pt->len;
else
minlen = nla_attr_minlen[pt->type];

if (attrlen < minlen)
goto out_err;
}

/* further validation */
switch (pt->validation_type) {
case NLA_VALIDATE_NONE:
/* nothing to do */
break;
case NLA_VALIDATE_RANGE_PTR:
case NLA_VALIDATE_RANGE:
case NLA_VALIDATE_MIN:
case NLA_VALIDATE_MAX:
err = nla_validate_int_range(pt, nla, extack);
if (err)
return err;
break;
case NLA_VALIDATE_FUNCTION:
if (pt->validate) {
err = pt->validate(nla, extack);
if (err)
return err;
}
break;
}

return 0;
out_err:
NL_SET_ERR_MSG_ATTR(extack, nla, "Attribute failed policy validation");
return err;
}

expr校验

既然netfilter实际上是一个执行由用户提供的指令的vm, 就必然需要对用户数据的校验.
基本有这样几处:

  1. validate_nla中根据nft_policy对属性进行校验.
    比如之前提过的, NFTA_RULE_USERDATA属性的长度有NFT_USERDATA_MAXLEN的限制.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    static const struct nla_policy nft_rule_policy[NFTA_RULE_MAX + 1] = {
    [NFTA_RULE_TABLE] = { .type = NLA_STRING,
    .len = NFT_TABLE_MAXNAMELEN - 1 },
    [NFTA_RULE_CHAIN] = { .type = NLA_STRING,
    .len = NFT_CHAIN_MAXNAMELEN - 1 },
    [NFTA_RULE_HANDLE] = { .type = NLA_U64 },
    [NFTA_RULE_EXPRESSIONS] = { .type = NLA_NESTED },
    [NFTA_RULE_COMPAT] = { .type = NLA_NESTED },
    [NFTA_RULE_POSITION] = { .type = NLA_U64 },
    [NFTA_RULE_USERDATA] = { .type = NLA_BINARY,
    .len = NFT_USERDATA_MAXLEN },
    [NFTA_RULE_ID] = { .type = NLA_U32 },
    [NFTA_RULE_POSITION_ID] = { .type = NLA_U32 },
    };
  2. nft_xxx_init中构造expr时的校验.
    expr求值的过程中不是直接使用用户提供的属性, 而是在构造函数中校验/转换后存入
    nft_xx_expr的private数据中.

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
static int nft_cmp_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
const struct nlattr * const tb[])
{
struct nft_cmp_expr *priv = nft_expr_priv(expr);
struct nft_data_desc desc;
int err;

err = nft_data_init(NULL, &priv->data, sizeof(priv->data), &desc,
tb[NFTA_CMP_DATA]);
if (err < 0)
return err;

if (desc.type != NFT_DATA_VALUE) {
err = -EINVAL;
nft_data_release(&priv->data, desc.type);
return err;
}

priv->sreg = nft_parse_register(tb[NFTA_CMP_SREG]);
err = nft_validate_register_load(priv->sreg, desc.len);
if (err < 0)
return err;

priv->op = ntohl(nla_get_be32(tb[NFTA_CMP_OP]));
priv->len = desc.len;
return 0;
}
  1. nft_xxx_eval求值时校验.
    由于一些与packet相关的信息只能在求值时获取, 所以一些校验工作需要推迟到求值时.
    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
    void nft_payload_eval(const struct nft_expr *expr,
    struct nft_regs *regs,
    const struct nft_pktinfo *pkt)
    {
    const struct nft_payload *priv = nft_expr_priv(expr);
    const struct sk_buff *skb = pkt->skb;
    u32 *dest = &regs->data[priv->dreg];
    int offset;

    dest[priv->len / NFT_REG32_SIZE] = 0;
    switch (priv->base) {
    case NFT_PAYLOAD_LL_HEADER:
    if (!skb_mac_header_was_set(skb))
    goto err;

    if (skb_vlan_tag_present(skb)) {
    if (!nft_payload_copy_vlan(dest, skb,
    priv->offset, priv->len))
    goto err;
    return;
    }
    offset = skb_mac_header(skb) - skb->data;
    break;
    case NFT_PAYLOAD_NETWORK_HEADER:
    offset = skb_network_offset(skb);
    break;
    case NFT_PAYLOAD_TRANSPORT_HEADER:
    if (!pkt->tprot_set)
    goto err;
    offset = pkt->xt.thoff;
    break;
    default:
    BUG();
    }
    offset += priv->offset;

    if (skb_copy_bits(skb, offset, dest, priv->len) < 0)
    goto err;
    return;
    err:
    regs->verdict.code = NFT_BREAK;
    }
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 翰青HanQi

请我喝杯咖啡吧~

支付宝
微信