at master 11 kB view raw
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");