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(nlsk, skb, nlh, 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(nlskb, NFNLA_HOOK_CHAIN_INFO);
64	int ret;
65
66	if (!nest)
67		return NULL;
68
69	ret = nla_put_be32(nlskb, NFNLA_HOOK_INFO_TYPE, htonl(t));
70	if (ret == 0)
71		return nest;
72
73	nla_nest_cancel(nlskb, 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, NFNL_HOOK_TYPE_BPF);
92	if (!nest)
93		return -EMSGSIZE;
94
95	nest2 = nla_nest_start(nlskb, NFNLA_HOOK_INFO_DESC);
96	if (!nest2)
97		goto cancel_nest;
98
99	ret = nla_put_be32(nlskb, NFNLA_HOOK_BPF_ID, htonl(prog->aux->id));
100	if (ret)
101		goto cancel_nest;
102
103	nla_nest_end(nlskb, nest2);
104	nla_nest_end(nlskb, nest);
105	return 0;
106
107cancel_nest:
108	nla_nest_cancel(nlskb, 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(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, NFNL_HOOK_TYPE_NFTABLES);
128	if (!nest)
129		return -EMSGSIZE;
130
131	nest2 = nla_nest_start(nlskb, NFNLA_HOOK_INFO_DESC);
132	if (!nest2)
133		goto cancel_nest;
134
135	ret = nla_put_string(nlskb, NFNLA_CHAIN_TABLE, chain->table->name);
136	if (ret)
137		goto cancel_nest;
138
139	ret = nla_put_string(nlskb, NFNLA_CHAIN_NAME, chain->name);
140	if (ret)
141		goto cancel_nest;
142
143	ret = nla_put_u8(nlskb, NFNLA_CHAIN_FAMILY, chain->table->family);
144	if (ret)
145		goto cancel_nest;
146
147	nla_nest_end(nlskb, nest2);
148	nla_nest_end(nlskb, nest);
149	return ret;
150
151cancel_nest:
152	nla_nest_cancel(nlskb, 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, 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(nlskb, portid, seq, event,
171			   NLM_F_MULTI, family, NFNETLINK_V0, 0);
172	if (!nlh)
173		goto nla_put_failure;
174
175#ifdef CONFIG_KALLSYMS
176	ret = snprintf(sym, sizeof(sym), "%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(nlskb, NFNLA_HOOK_MODULE_NAME, module_name);
193			if (ret)
194				goto nla_put_failure;
195		}
196	}
197
198	ret = nla_put_string(nlskb, NFNLA_HOOK_FUNCTION_NAME, 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(nlskb, NFNLA_HOOK_HOOKNUM, htonl(hooknum));
209	if (ret)
210		goto nla_put_failure;
211
212	ret = nla_put_be32(nlskb, 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, ops->priv);
219		break;
220	case NF_HOOK_OP_BPF:
221		ret = nfnl_hook_put_bpf_prog_info(nlskb, ctx, seq, 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(nlskb, nlh);
234	return 0;
235nla_put_failure:
236	nlmsg_trim(nlskb, 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(-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(-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(-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(-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(-EOPNOTSUPP);
277
278		if (!dev)
279			return ERR_PTR(-ENODEV);
280
281		netdev = dev_get_by_name_rcu(net, dev);
282		if (!netdev)
283			return ERR_PTR(-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(-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(cb->nlh);
306	struct nfnl_dump_hook_data *ctx = cb->data;
307	int err, family = nfmsg->nfgen_family;
308	struct net *net = sock_net(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(family, ctx->hook, net, ctx->devname);
316	if (!e)
317		goto done;
318
319	if (IS_ERR(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[i], family,
331					 cb->nlh->nlmsg_seq);
332		if (err)
333			break;
334	}
335
336done:
337	nl_dump_check_consistent(cb, nlmsg_hdr(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(cb->nlh);
346	const struct nlattr * const *nla = cb->data;
347	struct nfnl_dump_hook_data *ctx = NULL;
348	struct net *net = sock_net(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(name, nla[NFNLA_HOOK_DEV], sizeof(name));
363	}
364
365	rcu_read_lock();
366	/* Not dereferenced; for consistency check only */
367	head = nfnl_hook_entries_head(family, hooknum, net, name);
368	rcu_read_unlock();
369
370	if (head && IS_ERR(head))
371		return PTR_ERR(head);
372
373	ctx = kzalloc(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(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(info->sk, skb, info->nlh, &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(&nfhook_subsys);
436}
437
438static void __exit nfnetlink_hook_exit(void)
439{
440	nfnetlink_subsys_unregister(&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