File Index
Files and directories
Sign in
FILE

nfnetlink_hook.c

File preview with syntax highlighting.

linux-6.19.14/net/netfilter/nfnetlink_hook.c C
  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_info_desc(struct sk_buff *nlskb, const char *tname,
113				       const char *name, u8 family)
114{
115	struct nlattr *nest;
116
117	nest = nla_nest_start(nlskb, NFNLA_HOOK_INFO_DESC);
118	if (!nest ||
119	    nla_put_string(nlskb, NFNLA_CHAIN_TABLE, tname) ||
120	    nla_put_string(nlskb, NFNLA_CHAIN_NAME, name) ||
121	    nla_put_u8(nlskb, NFNLA_CHAIN_FAMILY, family)) {
122		nla_nest_cancel(nlskb, nest);
123		return -EMSGSIZE;
124	}
125	nla_nest_end(nlskb, nest);
126	return 0;
127}
128
129static int nfnl_hook_put_nft_chain_info(struct sk_buff *nlskb,
130					const struct nfnl_dump_hook_data *ctx,
131					unsigned int seq,
132					struct nft_chain *chain)
133{
134	struct net *net = sock_net(nlskb->sk);
135	struct nlattr *nest;
136	int ret = 0;
137
138	if (WARN_ON_ONCE(!chain))
139		return 0;
140
141	if (!nft_is_active(net, chain))
142		return 0;
143
144	nest = nfnl_start_info_type(nlskb, NFNL_HOOK_TYPE_NFTABLES);
145	if (!nest)
146		return -EMSGSIZE;
147
148	ret = nfnl_hook_put_nft_info_desc(nlskb, chain->table->name,
149					  chain->name, chain->table->family);
150	if (ret) {
151		nla_nest_cancel(nlskb, nest);
152		return ret;
153	}
154
155	nla_nest_end(nlskb, nest);
156	return 0;
157}
158
159static int nfnl_hook_put_nft_ft_info(struct sk_buff *nlskb,
160				     const struct nfnl_dump_hook_data *ctx,
161				     unsigned int seq,
162				     struct nf_flowtable *nf_ft)
163{
164	struct nft_flowtable *ft =
165		container_of(nf_ft, struct nft_flowtable, data);
166	struct net *net = sock_net(nlskb->sk);
167	struct nlattr *nest;
168	int ret = 0;
169
170	if (WARN_ON_ONCE(!nf_ft))
171		return 0;
172
173	if (!nft_is_active(net, ft))
174		return 0;
175
176	nest = nfnl_start_info_type(nlskb, NFNL_HOOK_TYPE_NFT_FLOWTABLE);
177	if (!nest)
178		return -EMSGSIZE;
179
180	ret = nfnl_hook_put_nft_info_desc(nlskb, ft->table->name,
181					  ft->name, ft->table->family);
182	if (ret) {
183		nla_nest_cancel(nlskb, nest);
184		return ret;
185	}
186
187	nla_nest_end(nlskb, nest);
188	return 0;
189}
190
191static int nfnl_hook_dump_one(struct sk_buff *nlskb,
192			      const struct nfnl_dump_hook_data *ctx,
193			      const struct nf_hook_ops *ops,
194			      int family, unsigned int seq)
195{
196	u16 event = nfnl_msg_type(NFNL_SUBSYS_HOOK, NFNL_MSG_HOOK_GET);
197	unsigned int portid = NETLINK_CB(nlskb).portid;
198	struct nlmsghdr *nlh;
199	int ret = -EMSGSIZE;
200	u32 hooknum;
201#ifdef CONFIG_KALLSYMS
202	char sym[KSYM_SYMBOL_LEN];
203	char *module_name;
204#endif
205	nlh = nfnl_msg_put(nlskb, portid, seq, event,
206			   NLM_F_MULTI, family, NFNETLINK_V0, 0);
207	if (!nlh)
208		goto nla_put_failure;
209
210#ifdef CONFIG_KALLSYMS
211	ret = snprintf(sym, sizeof(sym), "%ps", ops->hook);
212	if (ret >= sizeof(sym)) {
213		ret = -EINVAL;
214		goto nla_put_failure;
215	}
216
217	module_name = strstr(sym, " [");
218	if (module_name) {
219		char *end;
220
221		*module_name = '\0';
222		module_name += 2;
223		end = strchr(module_name, ']');
224		if (end) {
225			*end = 0;
226
227			ret = nla_put_string(nlskb, NFNLA_HOOK_MODULE_NAME, module_name);
228			if (ret)
229				goto nla_put_failure;
230		}
231	}
232
233	ret = nla_put_string(nlskb, NFNLA_HOOK_FUNCTION_NAME, sym);
234	if (ret)
235		goto nla_put_failure;
236#endif
237
238	if (ops->pf == NFPROTO_INET && ops->hooknum == NF_INET_INGRESS)
239		hooknum = NF_NETDEV_INGRESS;
240	else
241		hooknum = ops->hooknum;
242
243	ret = nla_put_be32(nlskb, NFNLA_HOOK_HOOKNUM, htonl(hooknum));
244	if (ret)
245		goto nla_put_failure;
246
247	ret = nla_put_be32(nlskb, NFNLA_HOOK_PRIORITY, htonl(ops->priority));
248	if (ret)
249		goto nla_put_failure;
250
251	switch (ops->hook_ops_type) {
252	case NF_HOOK_OP_NF_TABLES:
253		ret = nfnl_hook_put_nft_chain_info(nlskb, ctx, seq, ops->priv);
254		break;
255	case NF_HOOK_OP_BPF:
256		ret = nfnl_hook_put_bpf_prog_info(nlskb, ctx, seq, ops->priv);
257		break;
258	case NF_HOOK_OP_NFT_FT:
259		ret = nfnl_hook_put_nft_ft_info(nlskb, ctx, seq, ops->priv);
260		break;
261	case NF_HOOK_OP_UNDEFINED:
262		break;
263	default:
264		WARN_ON_ONCE(1);
265		break;
266	}
267
268	if (ret)
269		goto nla_put_failure;
270
271	nlmsg_end(nlskb, nlh);
272	return 0;
273nla_put_failure:
274	nlmsg_trim(nlskb, nlh);
275	return ret;
276}
277
278static const struct nf_hook_entries *
279nfnl_hook_entries_head(u8 pf, unsigned int hook, struct net *net, const char *dev)
280{
281	const struct nf_hook_entries *hook_head = NULL;
282#if defined(CONFIG_NETFILTER_INGRESS) || defined(CONFIG_NETFILTER_EGRESS)
283	struct net_device *netdev;
284#endif
285
286	switch (pf) {
287	case NFPROTO_IPV4:
288		if (hook >= ARRAY_SIZE(net->nf.hooks_ipv4))
289			return ERR_PTR(-EINVAL);
290		hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]);
291		break;
292	case NFPROTO_IPV6:
293		if (hook >= ARRAY_SIZE(net->nf.hooks_ipv6))
294			return ERR_PTR(-EINVAL);
295		hook_head = rcu_dereference(net->nf.hooks_ipv6[hook]);
296		break;
297	case NFPROTO_ARP:
298#ifdef CONFIG_NETFILTER_FAMILY_ARP
299		if (hook >= ARRAY_SIZE(net->nf.hooks_arp))
300			return ERR_PTR(-EINVAL);
301		hook_head = rcu_dereference(net->nf.hooks_arp[hook]);
302#endif
303		break;
304	case NFPROTO_BRIDGE:
305#ifdef CONFIG_NETFILTER_FAMILY_BRIDGE
306		if (hook >= ARRAY_SIZE(net->nf.hooks_bridge))
307			return ERR_PTR(-EINVAL);
308		hook_head = rcu_dereference(net->nf.hooks_bridge[hook]);
309#endif
310		break;
311#if defined(CONFIG_NETFILTER_INGRESS) || defined(CONFIG_NETFILTER_EGRESS)
312	case NFPROTO_NETDEV:
313		if (hook >= NF_NETDEV_NUMHOOKS)
314			return ERR_PTR(-EOPNOTSUPP);
315
316		if (!dev)
317			return ERR_PTR(-ENODEV);
318
319		netdev = dev_get_by_name_rcu(net, dev);
320		if (!netdev)
321			return ERR_PTR(-ENODEV);
322
323#ifdef CONFIG_NETFILTER_INGRESS
324		if (hook == NF_NETDEV_INGRESS)
325			return rcu_dereference(netdev->nf_hooks_ingress);
326#endif
327#ifdef CONFIG_NETFILTER_EGRESS
328		if (hook == NF_NETDEV_EGRESS)
329			return rcu_dereference(netdev->nf_hooks_egress);
330#endif
331		fallthrough;
332#endif
333	default:
334		return ERR_PTR(-EPROTONOSUPPORT);
335	}
336
337	return hook_head;
338}
339
340static int nfnl_hook_dump(struct sk_buff *nlskb,
341			  struct netlink_callback *cb)
342{
343	struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
344	struct nfnl_dump_hook_data *ctx = cb->data;
345	int err, family = nfmsg->nfgen_family;
346	struct net *net = sock_net(nlskb->sk);
347	struct nf_hook_ops * const *ops;
348	const struct nf_hook_entries *e;
349	unsigned int i = cb->args[0];
350
351	rcu_read_lock();
352
353	e = nfnl_hook_entries_head(family, ctx->hook, net, ctx->devname);
354	if (!e)
355		goto done;
356
357	if (IS_ERR(e)) {
358		cb->seq++;
359		goto done;
360	}
361
362	if ((unsigned long)e != ctx->headv || i >= e->num_hook_entries)
363		cb->seq++;
364
365	ops = nf_hook_entries_get_hook_ops(e);
366
367	for (; i < e->num_hook_entries; i++) {
368		err = nfnl_hook_dump_one(nlskb, ctx, ops[i], family,
369					 cb->nlh->nlmsg_seq);
370		if (err)
371			break;
372	}
373
374done:
375	nl_dump_check_consistent(cb, nlmsg_hdr(nlskb));
376	rcu_read_unlock();
377	cb->args[0] = i;
378	return nlskb->len;
379}
380
381static int nfnl_hook_dump_start(struct netlink_callback *cb)
382{
383	const struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
384	const struct nlattr * const *nla = cb->data;
385	struct nfnl_dump_hook_data *ctx = NULL;
386	struct net *net = sock_net(cb->skb->sk);
387	u8 family = nfmsg->nfgen_family;
388	char name[IFNAMSIZ] = "";
389	const void *head;
390	u32 hooknum;
391
392	hooknum = ntohl(nla_get_be32(nla[NFNLA_HOOK_HOOKNUM]));
393	if (hooknum > 255)
394		return -EINVAL;
395
396	if (family == NFPROTO_NETDEV) {
397		if (!nla[NFNLA_HOOK_DEV])
398			return -EINVAL;
399
400		nla_strscpy(name, nla[NFNLA_HOOK_DEV], sizeof(name));
401	}
402
403	rcu_read_lock();
404	/* Not dereferenced; for consistency check only */
405	head = nfnl_hook_entries_head(family, hooknum, net, name);
406	rcu_read_unlock();
407
408	if (head && IS_ERR(head))
409		return PTR_ERR(head);
410
411	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
412	if (!ctx)
413		return -ENOMEM;
414
415	strscpy(ctx->devname, name, sizeof(ctx->devname));
416	ctx->headv = (unsigned long)head;
417	ctx->hook = hooknum;
418
419	cb->seq = 1;
420	cb->data = ctx;
421
422	return 0;
423}
424
425static int nfnl_hook_dump_stop(struct netlink_callback *cb)
426{
427	kfree(cb->data);
428	return 0;
429}
430
431static int nfnl_hook_get(struct sk_buff *skb,
432			 const struct nfnl_info *info,
433			 const struct nlattr * const nla[])
434{
435	if (!nla[NFNLA_HOOK_HOOKNUM])
436		return -EINVAL;
437
438	if (info->nlh->nlmsg_flags & NLM_F_DUMP) {
439		struct netlink_dump_control c = {
440			.start = nfnl_hook_dump_start,
441			.done = nfnl_hook_dump_stop,
442			.dump = nfnl_hook_dump,
443			.module = THIS_MODULE,
444			.data = (void *)nla,
445		};
446
447		return nf_netlink_dump_start_rcu(info->sk, skb, info->nlh, &c);
448	}
449
450	return -EOPNOTSUPP;
451}
452
453static const struct nfnl_callback nfnl_hook_cb[NFNL_MSG_HOOK_MAX] = {
454	[NFNL_MSG_HOOK_GET] = {
455		.call		= nfnl_hook_get,
456		.type		= NFNL_CB_RCU,
457		.attr_count	= NFNLA_HOOK_MAX,
458		.policy		= nfnl_hook_nla_policy
459	},
460};
461
462static const struct nfnetlink_subsystem nfhook_subsys = {
463	.name				= "nfhook",
464	.subsys_id			= NFNL_SUBSYS_HOOK,
465	.cb_count			= NFNL_MSG_HOOK_MAX,
466	.cb				= nfnl_hook_cb,
467};
468
469MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_HOOK);
470
471static int __init nfnetlink_hook_init(void)
472{
473	return nfnetlink_subsys_register(&nfhook_subsys);
474}
475
476static void __exit nfnetlink_hook_exit(void)
477{
478	nfnetlink_subsys_unregister(&nfhook_subsys);
479}
480
481module_init(nfnetlink_hook_init);
482module_exit(nfnetlink_hook_exit);
483
484MODULE_LICENSE("GPL");
485MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
486MODULE_DESCRIPTION("nfnetlink_hook: list registered netfilter hooks");
~/ Read-only file preview.