1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) 2021 Red Hat GmbH
4 *
5 * Author: Florian Westphal <fw@strlen.de>
6 */
7
8#include <linux/bpf.h>
9#include <linux/module.h>
10#include <linux/kallsyms.h>
11#include <linux/kernel.h>
12#include <linux/types.h>
13#include <linux/skbuff.h>
14#include <linux/errno.h>
15#include <linux/netlink.h>
16#include <linux/slab.h>
17
18#include <linux/netfilter.h>
19
20#include <linux/netfilter/nfnetlink.h>
21#include <linux/netfilter/nfnetlink_hook.h>
22
23#include <net/netfilter/nf_tables.h>
24#include <net/sock.h>
25
26static const struct nla_policy nfnl_hook_nla_policy[NFNLA_HOOK_MAX + 1] = {
27 [NFNLA_HOOK_HOOKNUM] = { .type = NLA_U32 },
28 [NFNLA_HOOK_PRIORITY] = { .type = NLA_U32 },
29 [NFNLA_HOOK_DEV] = { .type = NLA_STRING,
30 .len = IFNAMSIZ - 1 },
31 [NFNLA_HOOK_FUNCTION_NAME] = { .type = NLA_NUL_STRING,
32 .len = KSYM_NAME_LEN, },
33 [NFNLA_HOOK_MODULE_NAME] = { .type = NLA_NUL_STRING,
34 .len = MODULE_NAME_LEN, },
35 [NFNLA_HOOK_CHAIN_INFO] = { .type = NLA_NESTED, },
36};
37
38static int nf_netlink_dump_start_rcu(struct sock *nlsk, struct sk_buff *skb,
39 const struct nlmsghdr *nlh,
40 struct netlink_dump_control *c)
41{
42 int err;
43
44 if (!try_module_get(THIS_MODULE))
45 return -EINVAL;
46
47 rcu_read_unlock();
48 err = netlink_dump_start(ssk: nlsk, skb, nlh, control: c);
49 rcu_read_lock();
50 module_put(THIS_MODULE);
51
52 return err;
53}
54
55struct nfnl_dump_hook_data {
56 char devname[IFNAMSIZ];
57 unsigned long headv;
58 u8 hook;
59};
60
61static struct nlattr *nfnl_start_info_type(struct sk_buff *nlskb, enum nfnl_hook_chaintype t)
62{
63 struct nlattr *nest = nla_nest_start(skb: nlskb, attrtype: NFNLA_HOOK_CHAIN_INFO);
64 int ret;
65
66 if (!nest)
67 return NULL;
68
69 ret = nla_put_be32(skb: nlskb, attrtype: NFNLA_HOOK_INFO_TYPE, htonl(t));
70 if (ret == 0)
71 return nest;
72
73 nla_nest_cancel(skb: nlskb, start: nest);
74 return NULL;
75}
76
77static int nfnl_hook_put_bpf_prog_info(struct sk_buff *nlskb,
78 const struct nfnl_dump_hook_data *ctx,
79 unsigned int seq,
80 const struct bpf_prog *prog)
81{
82 struct nlattr *nest, *nest2;
83 int ret;
84
85 if (!IS_ENABLED(CONFIG_NETFILTER_BPF_LINK))
86 return 0;
87
88 if (WARN_ON_ONCE(!prog))
89 return 0;
90
91 nest = nfnl_start_info_type(nlskb, t: NFNL_HOOK_TYPE_BPF);
92 if (!nest)
93 return -EMSGSIZE;
94
95 nest2 = nla_nest_start(skb: nlskb, attrtype: NFNLA_HOOK_INFO_DESC);
96 if (!nest2)
97 goto cancel_nest;
98
99 ret = nla_put_be32(skb: nlskb, attrtype: NFNLA_HOOK_BPF_ID, htonl(prog->aux->id));
100 if (ret)
101 goto cancel_nest;
102
103 nla_nest_end(skb: nlskb, start: nest2);
104 nla_nest_end(skb: nlskb, start: nest);
105 return 0;
106
107cancel_nest:
108 nla_nest_cancel(skb: nlskb, start: nest);
109 return -EMSGSIZE;
110}
111
112static int nfnl_hook_put_nft_chain_info(struct sk_buff *nlskb,
113 const struct nfnl_dump_hook_data *ctx,
114 unsigned int seq,
115 struct nft_chain *chain)
116{
117 struct net *net = sock_net(sk: nlskb->sk);
118 struct nlattr *nest, *nest2;
119 int ret = 0;
120
121 if (WARN_ON_ONCE(!chain))
122 return 0;
123
124 if (!nft_is_active(net, chain))
125 return 0;
126
127 nest = nfnl_start_info_type(nlskb, t: NFNL_HOOK_TYPE_NFTABLES);
128 if (!nest)
129 return -EMSGSIZE;
130
131 nest2 = nla_nest_start(skb: nlskb, attrtype: NFNLA_HOOK_INFO_DESC);
132 if (!nest2)
133 goto cancel_nest;
134
135 ret = nla_put_string(skb: nlskb, attrtype: NFNLA_CHAIN_TABLE, str: chain->table->name);
136 if (ret)
137 goto cancel_nest;
138
139 ret = nla_put_string(skb: nlskb, attrtype: NFNLA_CHAIN_NAME, str: chain->name);
140 if (ret)
141 goto cancel_nest;
142
143 ret = nla_put_u8(skb: nlskb, attrtype: NFNLA_CHAIN_FAMILY, value: chain->table->family);
144 if (ret)
145 goto cancel_nest;
146
147 nla_nest_end(skb: nlskb, start: nest2);
148 nla_nest_end(skb: nlskb, start: nest);
149 return ret;
150
151cancel_nest:
152 nla_nest_cancel(skb: nlskb, start: nest);
153 return -EMSGSIZE;
154}
155
156static int nfnl_hook_dump_one(struct sk_buff *nlskb,
157 const struct nfnl_dump_hook_data *ctx,
158 const struct nf_hook_ops *ops,
159 int family, unsigned int seq)
160{
161 u16 event = nfnl_msg_type(NFNL_SUBSYS_HOOK, msg_type: NFNL_MSG_HOOK_GET);
162 unsigned int portid = NETLINK_CB(nlskb).portid;
163 struct nlmsghdr *nlh;
164 int ret = -EMSGSIZE;
165 u32 hooknum;
166#ifdef CONFIG_KALLSYMS
167 char sym[KSYM_SYMBOL_LEN];
168 char *module_name;
169#endif
170 nlh = nfnl_msg_put(skb: nlskb, portid, seq, type: event,
171 NLM_F_MULTI, family, NFNETLINK_V0, res_id: 0);
172 if (!nlh)
173 goto nla_put_failure;
174
175#ifdef CONFIG_KALLSYMS
176 ret = snprintf(buf: sym, size: sizeof(sym), fmt: "%ps", ops->hook);
177 if (ret >= sizeof(sym)) {
178 ret = -EINVAL;
179 goto nla_put_failure;
180 }
181
182 module_name = strstr(sym, " [");
183 if (module_name) {
184 char *end;
185
186 *module_name = '\0';
187 module_name += 2;
188 end = strchr(module_name, ']');
189 if (end) {
190 *end = 0;
191
192 ret = nla_put_string(skb: nlskb, attrtype: NFNLA_HOOK_MODULE_NAME, str: module_name);
193 if (ret)
194 goto nla_put_failure;
195 }
196 }
197
198 ret = nla_put_string(skb: nlskb, attrtype: NFNLA_HOOK_FUNCTION_NAME, str: sym);
199 if (ret)
200 goto nla_put_failure;
201#endif
202
203 if (ops->pf == NFPROTO_INET && ops->hooknum == NF_INET_INGRESS)
204 hooknum = NF_NETDEV_INGRESS;
205 else
206 hooknum = ops->hooknum;
207
208 ret = nla_put_be32(skb: nlskb, attrtype: NFNLA_HOOK_HOOKNUM, htonl(hooknum));
209 if (ret)
210 goto nla_put_failure;
211
212 ret = nla_put_be32(skb: nlskb, attrtype: NFNLA_HOOK_PRIORITY, htonl(ops->priority));
213 if (ret)
214 goto nla_put_failure;
215
216 switch (ops->hook_ops_type) {
217 case NF_HOOK_OP_NF_TABLES:
218 ret = nfnl_hook_put_nft_chain_info(nlskb, ctx, seq, chain: ops->priv);
219 break;
220 case NF_HOOK_OP_BPF:
221 ret = nfnl_hook_put_bpf_prog_info(nlskb, ctx, seq, prog: ops->priv);
222 break;
223 case NF_HOOK_OP_UNDEFINED:
224 break;
225 default:
226 WARN_ON_ONCE(1);
227 break;
228 }
229
230 if (ret)
231 goto nla_put_failure;
232
233 nlmsg_end(skb: nlskb, nlh);
234 return 0;
235nla_put_failure:
236 nlmsg_trim(skb: nlskb, mark: nlh);
237 return ret;
238}
239
240static const struct nf_hook_entries *
241nfnl_hook_entries_head(u8 pf, unsigned int hook, struct net *net, const char *dev)
242{
243 const struct nf_hook_entries *hook_head = NULL;
244#if defined(CONFIG_NETFILTER_INGRESS) || defined(CONFIG_NETFILTER_EGRESS)
245 struct net_device *netdev;
246#endif
247
248 switch (pf) {
249 case NFPROTO_IPV4:
250 if (hook >= ARRAY_SIZE(net->nf.hooks_ipv4))
251 return ERR_PTR(error: -EINVAL);
252 hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]);
253 break;
254 case NFPROTO_IPV6:
255 if (hook >= ARRAY_SIZE(net->nf.hooks_ipv6))
256 return ERR_PTR(error: -EINVAL);
257 hook_head = rcu_dereference(net->nf.hooks_ipv6[hook]);
258 break;
259 case NFPROTO_ARP:
260#ifdef CONFIG_NETFILTER_FAMILY_ARP
261 if (hook >= ARRAY_SIZE(net->nf.hooks_arp))
262 return ERR_PTR(error: -EINVAL);
263 hook_head = rcu_dereference(net->nf.hooks_arp[hook]);
264#endif
265 break;
266 case NFPROTO_BRIDGE:
267#ifdef CONFIG_NETFILTER_FAMILY_BRIDGE
268 if (hook >= ARRAY_SIZE(net->nf.hooks_bridge))
269 return ERR_PTR(error: -EINVAL);
270 hook_head = rcu_dereference(net->nf.hooks_bridge[hook]);
271#endif
272 break;
273#if defined(CONFIG_NETFILTER_INGRESS) || defined(CONFIG_NETFILTER_EGRESS)
274 case NFPROTO_NETDEV:
275 if (hook >= NF_NETDEV_NUMHOOKS)
276 return ERR_PTR(error: -EOPNOTSUPP);
277
278 if (!dev)
279 return ERR_PTR(error: -ENODEV);
280
281 netdev = dev_get_by_name_rcu(net, name: dev);
282 if (!netdev)
283 return ERR_PTR(error: -ENODEV);
284
285#ifdef CONFIG_NETFILTER_INGRESS
286 if (hook == NF_NETDEV_INGRESS)
287 return rcu_dereference(netdev->nf_hooks_ingress);
288#endif
289#ifdef CONFIG_NETFILTER_EGRESS
290 if (hook == NF_NETDEV_EGRESS)
291 return rcu_dereference(netdev->nf_hooks_egress);
292#endif
293 fallthrough;
294#endif
295 default:
296 return ERR_PTR(error: -EPROTONOSUPPORT);
297 }
298
299 return hook_head;
300}
301
302static int nfnl_hook_dump(struct sk_buff *nlskb,
303 struct netlink_callback *cb)
304{
305 struct nfgenmsg *nfmsg = nlmsg_data(nlh: cb->nlh);
306 struct nfnl_dump_hook_data *ctx = cb->data;
307 int err, family = nfmsg->nfgen_family;
308 struct net *net = sock_net(sk: nlskb->sk);
309 struct nf_hook_ops * const *ops;
310 const struct nf_hook_entries *e;
311 unsigned int i = cb->args[0];
312
313 rcu_read_lock();
314
315 e = nfnl_hook_entries_head(pf: family, hook: ctx->hook, net, dev: ctx->devname);
316 if (!e)
317 goto done;
318
319 if (IS_ERR(ptr: e)) {
320 cb->seq++;
321 goto done;
322 }
323
324 if ((unsigned long)e != ctx->headv || i >= e->num_hook_entries)
325 cb->seq++;
326
327 ops = nf_hook_entries_get_hook_ops(e);
328
329 for (; i < e->num_hook_entries; i++) {
330 err = nfnl_hook_dump_one(nlskb, ctx, ops: ops[i], family,
331 seq: cb->nlh->nlmsg_seq);
332 if (err)
333 break;
334 }
335
336done:
337 nl_dump_check_consistent(cb, nlh: nlmsg_hdr(skb: nlskb));
338 rcu_read_unlock();
339 cb->args[0] = i;
340 return nlskb->len;
341}
342
343static int nfnl_hook_dump_start(struct netlink_callback *cb)
344{
345 const struct nfgenmsg *nfmsg = nlmsg_data(nlh: cb->nlh);
346 const struct nlattr * const *nla = cb->data;
347 struct nfnl_dump_hook_data *ctx = NULL;
348 struct net *net = sock_net(sk: cb->skb->sk);
349 u8 family = nfmsg->nfgen_family;
350 char name[IFNAMSIZ] = "";
351 const void *head;
352 u32 hooknum;
353
354 hooknum = ntohl(nla_get_be32(nla[NFNLA_HOOK_HOOKNUM]));
355 if (hooknum > 255)
356 return -EINVAL;
357
358 if (family == NFPROTO_NETDEV) {
359 if (!nla[NFNLA_HOOK_DEV])
360 return -EINVAL;
361
362 nla_strscpy(dst: name, nla: nla[NFNLA_HOOK_DEV], dstsize: sizeof(name));
363 }
364
365 rcu_read_lock();
366 /* Not dereferenced; for consistency check only */
367 head = nfnl_hook_entries_head(pf: family, hook: hooknum, net, dev: name);
368 rcu_read_unlock();
369
370 if (head && IS_ERR(ptr: head))
371 return PTR_ERR(ptr: head);
372
373 ctx = kzalloc(size: sizeof(*ctx), GFP_KERNEL);
374 if (!ctx)
375 return -ENOMEM;
376
377 strscpy(ctx->devname, name, sizeof(ctx->devname));
378 ctx->headv = (unsigned long)head;
379 ctx->hook = hooknum;
380
381 cb->seq = 1;
382 cb->data = ctx;
383
384 return 0;
385}
386
387static int nfnl_hook_dump_stop(struct netlink_callback *cb)
388{
389 kfree(objp: cb->data);
390 return 0;
391}
392
393static int nfnl_hook_get(struct sk_buff *skb,
394 const struct nfnl_info *info,
395 const struct nlattr * const nla[])
396{
397 if (!nla[NFNLA_HOOK_HOOKNUM])
398 return -EINVAL;
399
400 if (info->nlh->nlmsg_flags & NLM_F_DUMP) {
401 struct netlink_dump_control c = {
402 .start = nfnl_hook_dump_start,
403 .done = nfnl_hook_dump_stop,
404 .dump = nfnl_hook_dump,
405 .module = THIS_MODULE,
406 .data = (void *)nla,
407 };
408
409 return nf_netlink_dump_start_rcu(nlsk: info->sk, skb, nlh: info->nlh, c: &c);
410 }
411
412 return -EOPNOTSUPP;
413}
414
415static const struct nfnl_callback nfnl_hook_cb[NFNL_MSG_HOOK_MAX] = {
416 [NFNL_MSG_HOOK_GET] = {
417 .call = nfnl_hook_get,
418 .type = NFNL_CB_RCU,
419 .attr_count = NFNLA_HOOK_MAX,
420 .policy = nfnl_hook_nla_policy
421 },
422};
423
424static const struct nfnetlink_subsystem nfhook_subsys = {
425 .name = "nfhook",
426 .subsys_id = NFNL_SUBSYS_HOOK,
427 .cb_count = NFNL_MSG_HOOK_MAX,
428 .cb = nfnl_hook_cb,
429};
430
431MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_HOOK);
432
433static int __init nfnetlink_hook_init(void)
434{
435 return nfnetlink_subsys_register(n: &nfhook_subsys);
436}
437
438static void __exit nfnetlink_hook_exit(void)
439{
440 nfnetlink_subsys_unregister(n: &nfhook_subsys);
441}
442
443module_init(nfnetlink_hook_init);
444module_exit(nfnetlink_hook_exit);
445
446MODULE_LICENSE("GPL");
447MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
448MODULE_DESCRIPTION("nfnetlink_hook: list registered netfilter hooks");
449

source code of linux/net/netfilter/nfnetlink_hook.c