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_chain_info(struct sk_buff * nlskb,const struct nfnl_dump_hook_data * ctx,unsigned int seq,struct nft_chain * chain)112 static 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 
151 cancel_nest:
152 	nla_nest_cancel(nlskb, nest);
153 	return -EMSGSIZE;
154 }
155 
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)156 static 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;
235 nla_put_failure:
236 	nlmsg_trim(nlskb, nlh);
237 	return ret;
238 }
239 
240 static const struct nf_hook_entries *
nfnl_hook_entries_head(u8 pf,unsigned int hook,struct net * net,const char * dev)241 nfnl_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 
nfnl_hook_dump(struct sk_buff * nlskb,struct netlink_callback * cb)302 static 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 
336 done:
337 	nl_dump_check_consistent(cb, nlmsg_hdr(nlskb));
338 	rcu_read_unlock();
339 	cb->args[0] = i;
340 	return nlskb->len;
341 }
342 
nfnl_hook_dump_start(struct netlink_callback * cb)343 static 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 
nfnl_hook_dump_stop(struct netlink_callback * cb)387 static int nfnl_hook_dump_stop(struct netlink_callback *cb)
388 {
389 	kfree(cb->data);
390 	return 0;
391 }
392 
nfnl_hook_get(struct sk_buff * skb,const struct nfnl_info * info,const struct nlattr * const nla[])393 static 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 
415 static 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 
424 static 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 
431 MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_HOOK);
432 
nfnetlink_hook_init(void)433 static int __init nfnetlink_hook_init(void)
434 {
435 	return nfnetlink_subsys_register(&nfhook_subsys);
436 }
437 
nfnetlink_hook_exit(void)438 static void __exit nfnetlink_hook_exit(void)
439 {
440 	nfnetlink_subsys_unregister(&nfhook_subsys);
441 }
442 
443 module_init(nfnetlink_hook_init);
444 module_exit(nfnetlink_hook_exit);
445 
446 MODULE_LICENSE("GPL");
447 MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
448 MODULE_DESCRIPTION("nfnetlink_hook: list registered netfilter hooks");
449