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 
26 static 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 
nf_netlink_dump_start_rcu(struct sock * nlsk,struct sk_buff * skb,const struct nlmsghdr * nlh,struct netlink_dump_control * c)38 static 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 
55 struct nfnl_dump_hook_data {
56 	char devname[IFNAMSIZ];
57 	unsigned long headv;
58 	u8 hook;
59 };
60 
nfnl_start_info_type(struct sk_buff * nlskb,enum nfnl_hook_chaintype t)61 static 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 
nfnl_hook_put_bpf_prog_info(struct sk_buff * nlskb,const struct nfnl_dump_hook_data * ctx,unsigned int seq,const struct bpf_prog * prog)77 static 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 
107 cancel_nest:
108 	nla_nest_cancel(nlskb, nest);
109 	return -EMSGSIZE;
110 }
111 
nfnl_hook_put_nft_info_desc(struct sk_buff * nlskb,const char * tname,const char * name,u8 family)112 static 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 
nfnl_hook_put_nft_chain_info(struct sk_buff * nlskb,const struct nfnl_dump_hook_data * ctx,unsigned int seq,struct nft_chain * chain)129 static 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 
nfnl_hook_put_nft_ft_info(struct sk_buff * nlskb,const struct nfnl_dump_hook_data * ctx,unsigned int seq,struct nf_flowtable * nf_ft)159 static 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 
nfnl_hook_dump_one(struct sk_buff * nlskb,const struct nfnl_dump_hook_data * ctx,const struct nf_hook_ops * ops,int family,unsigned int seq)191 static 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;
273 nla_put_failure:
274 	nlmsg_trim(nlskb, nlh);
275 	return ret;
276 }
277 
278 static const struct nf_hook_entries *
nfnl_hook_entries_head(u8 pf,unsigned int hook,struct net * net,const char * dev)279 nfnl_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 
nfnl_hook_dump(struct sk_buff * nlskb,struct netlink_callback * cb)340 static 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 
374 done:
375 	nl_dump_check_consistent(cb, nlmsg_hdr(nlskb));
376 	rcu_read_unlock();
377 	cb->args[0] = i;
378 	return nlskb->len;
379 }
380 
nfnl_hook_dump_start(struct netlink_callback * cb)381 static 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 
nfnl_hook_dump_stop(struct netlink_callback * cb)425 static int nfnl_hook_dump_stop(struct netlink_callback *cb)
426 {
427 	kfree(cb->data);
428 	return 0;
429 }
430 
nfnl_hook_get(struct sk_buff * skb,const struct nfnl_info * info,const struct nlattr * const nla[])431 static 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 
453 static 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 
462 static 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 
469 MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_HOOK);
470 
nfnetlink_hook_init(void)471 static int __init nfnetlink_hook_init(void)
472 {
473 	return nfnetlink_subsys_register(&nfhook_subsys);
474 }
475 
nfnetlink_hook_exit(void)476 static void __exit nfnetlink_hook_exit(void)
477 {
478 	nfnetlink_subsys_unregister(&nfhook_subsys);
479 }
480 
481 module_init(nfnetlink_hook_init);
482 module_exit(nfnetlink_hook_exit);
483 
484 MODULE_LICENSE("GPL");
485 MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
486 MODULE_DESCRIPTION("nfnetlink_hook: list registered netfilter hooks");
487