nfnetlink_hook.c (9425B)
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/module.h> 9#include <linux/kallsyms.h> 10#include <linux/kernel.h> 11#include <linux/types.h> 12#include <linux/skbuff.h> 13#include <linux/errno.h> 14#include <linux/netlink.h> 15#include <linux/slab.h> 16 17#include <linux/netfilter.h> 18 19#include <linux/netfilter/nfnetlink.h> 20#include <linux/netfilter/nfnetlink_hook.h> 21 22#include <net/netfilter/nf_tables.h> 23#include <net/sock.h> 24 25static const struct nla_policy nfnl_hook_nla_policy[NFNLA_HOOK_MAX + 1] = { 26 [NFNLA_HOOK_HOOKNUM] = { .type = NLA_U32 }, 27 [NFNLA_HOOK_PRIORITY] = { .type = NLA_U32 }, 28 [NFNLA_HOOK_DEV] = { .type = NLA_STRING, 29 .len = IFNAMSIZ - 1 }, 30 [NFNLA_HOOK_FUNCTION_NAME] = { .type = NLA_NUL_STRING, 31 .len = KSYM_NAME_LEN, }, 32 [NFNLA_HOOK_MODULE_NAME] = { .type = NLA_NUL_STRING, 33 .len = MODULE_NAME_LEN, }, 34 [NFNLA_HOOK_CHAIN_INFO] = { .type = NLA_NESTED, }, 35}; 36 37static int nf_netlink_dump_start_rcu(struct sock *nlsk, struct sk_buff *skb, 38 const struct nlmsghdr *nlh, 39 struct netlink_dump_control *c) 40{ 41 int err; 42 43 if (!try_module_get(THIS_MODULE)) 44 return -EINVAL; 45 46 rcu_read_unlock(); 47 err = netlink_dump_start(nlsk, skb, nlh, c); 48 rcu_read_lock(); 49 module_put(THIS_MODULE); 50 51 return err; 52} 53 54struct nfnl_dump_hook_data { 55 char devname[IFNAMSIZ]; 56 unsigned long headv; 57 u8 hook; 58}; 59 60static int nfnl_hook_put_nft_chain_info(struct sk_buff *nlskb, 61 const struct nfnl_dump_hook_data *ctx, 62 unsigned int seq, 63 const struct nf_hook_ops *ops) 64{ 65 struct net *net = sock_net(nlskb->sk); 66 struct nlattr *nest, *nest2; 67 struct nft_chain *chain; 68 int ret = 0; 69 70 if (ops->hook_ops_type != NF_HOOK_OP_NF_TABLES) 71 return 0; 72 73 chain = ops->priv; 74 if (WARN_ON_ONCE(!chain)) 75 return 0; 76 77 if (!nft_is_active(net, chain)) 78 return 0; 79 80 nest = nla_nest_start(nlskb, NFNLA_HOOK_CHAIN_INFO); 81 if (!nest) 82 return -EMSGSIZE; 83 84 ret = nla_put_be32(nlskb, NFNLA_HOOK_INFO_TYPE, 85 htonl(NFNL_HOOK_TYPE_NFTABLES)); 86 if (ret) 87 goto cancel_nest; 88 89 nest2 = nla_nest_start(nlskb, NFNLA_HOOK_INFO_DESC); 90 if (!nest2) 91 goto cancel_nest; 92 93 ret = nla_put_string(nlskb, NFNLA_CHAIN_TABLE, chain->table->name); 94 if (ret) 95 goto cancel_nest; 96 97 ret = nla_put_string(nlskb, NFNLA_CHAIN_NAME, chain->name); 98 if (ret) 99 goto cancel_nest; 100 101 ret = nla_put_u8(nlskb, NFNLA_CHAIN_FAMILY, chain->table->family); 102 if (ret) 103 goto cancel_nest; 104 105 nla_nest_end(nlskb, nest2); 106 nla_nest_end(nlskb, nest); 107 return ret; 108 109cancel_nest: 110 nla_nest_cancel(nlskb, nest); 111 return -EMSGSIZE; 112} 113 114static int nfnl_hook_dump_one(struct sk_buff *nlskb, 115 const struct nfnl_dump_hook_data *ctx, 116 const struct nf_hook_ops *ops, 117 int family, unsigned int seq) 118{ 119 u16 event = nfnl_msg_type(NFNL_SUBSYS_HOOK, NFNL_MSG_HOOK_GET); 120 unsigned int portid = NETLINK_CB(nlskb).portid; 121 struct nlmsghdr *nlh; 122 int ret = -EMSGSIZE; 123 u32 hooknum; 124#ifdef CONFIG_KALLSYMS 125 char sym[KSYM_SYMBOL_LEN]; 126 char *module_name; 127#endif 128 nlh = nfnl_msg_put(nlskb, portid, seq, event, 129 NLM_F_MULTI, family, NFNETLINK_V0, 0); 130 if (!nlh) 131 goto nla_put_failure; 132 133#ifdef CONFIG_KALLSYMS 134 ret = snprintf(sym, sizeof(sym), "%ps", ops->hook); 135 if (ret >= sizeof(sym)) { 136 ret = -EINVAL; 137 goto nla_put_failure; 138 } 139 140 module_name = strstr(sym, " ["); 141 if (module_name) { 142 char *end; 143 144 *module_name = '\0'; 145 module_name += 2; 146 end = strchr(module_name, ']'); 147 if (end) { 148 *end = 0; 149 150 ret = nla_put_string(nlskb, NFNLA_HOOK_MODULE_NAME, module_name); 151 if (ret) 152 goto nla_put_failure; 153 } 154 } 155 156 ret = nla_put_string(nlskb, NFNLA_HOOK_FUNCTION_NAME, sym); 157 if (ret) 158 goto nla_put_failure; 159#endif 160 161 if (ops->pf == NFPROTO_INET && ops->hooknum == NF_INET_INGRESS) 162 hooknum = NF_NETDEV_INGRESS; 163 else 164 hooknum = ops->hooknum; 165 166 ret = nla_put_be32(nlskb, NFNLA_HOOK_HOOKNUM, htonl(hooknum)); 167 if (ret) 168 goto nla_put_failure; 169 170 ret = nla_put_be32(nlskb, NFNLA_HOOK_PRIORITY, htonl(ops->priority)); 171 if (ret) 172 goto nla_put_failure; 173 174 ret = nfnl_hook_put_nft_chain_info(nlskb, ctx, seq, ops); 175 if (ret) 176 goto nla_put_failure; 177 178 nlmsg_end(nlskb, nlh); 179 return 0; 180nla_put_failure: 181 nlmsg_trim(nlskb, nlh); 182 return ret; 183} 184 185static const struct nf_hook_entries * 186nfnl_hook_entries_head(u8 pf, unsigned int hook, struct net *net, const char *dev) 187{ 188 const struct nf_hook_entries *hook_head = NULL; 189#if defined(CONFIG_NETFILTER_INGRESS) || defined(CONFIG_NETFILTER_EGRESS) 190 struct net_device *netdev; 191#endif 192 193 switch (pf) { 194 case NFPROTO_IPV4: 195 if (hook >= ARRAY_SIZE(net->nf.hooks_ipv4)) 196 return ERR_PTR(-EINVAL); 197 hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]); 198 break; 199 case NFPROTO_IPV6: 200 if (hook >= ARRAY_SIZE(net->nf.hooks_ipv6)) 201 return ERR_PTR(-EINVAL); 202 hook_head = rcu_dereference(net->nf.hooks_ipv6[hook]); 203 break; 204 case NFPROTO_ARP: 205#ifdef CONFIG_NETFILTER_FAMILY_ARP 206 if (hook >= ARRAY_SIZE(net->nf.hooks_arp)) 207 return ERR_PTR(-EINVAL); 208 hook_head = rcu_dereference(net->nf.hooks_arp[hook]); 209#endif 210 break; 211 case NFPROTO_BRIDGE: 212#ifdef CONFIG_NETFILTER_FAMILY_BRIDGE 213 if (hook >= ARRAY_SIZE(net->nf.hooks_bridge)) 214 return ERR_PTR(-EINVAL); 215 hook_head = rcu_dereference(net->nf.hooks_bridge[hook]); 216#endif 217 break; 218#if IS_ENABLED(CONFIG_DECNET) 219 case NFPROTO_DECNET: 220 if (hook >= ARRAY_SIZE(net->nf.hooks_decnet)) 221 return ERR_PTR(-EINVAL); 222 hook_head = rcu_dereference(net->nf.hooks_decnet[hook]); 223 break; 224#endif 225#if defined(CONFIG_NETFILTER_INGRESS) || defined(CONFIG_NETFILTER_EGRESS) 226 case NFPROTO_NETDEV: 227 if (hook >= NF_NETDEV_NUMHOOKS) 228 return ERR_PTR(-EOPNOTSUPP); 229 230 if (!dev) 231 return ERR_PTR(-ENODEV); 232 233 netdev = dev_get_by_name_rcu(net, dev); 234 if (!netdev) 235 return ERR_PTR(-ENODEV); 236 237#ifdef CONFIG_NETFILTER_INGRESS 238 if (hook == NF_NETDEV_INGRESS) 239 return rcu_dereference(netdev->nf_hooks_ingress); 240#endif 241#ifdef CONFIG_NETFILTER_EGRESS 242 if (hook == NF_NETDEV_EGRESS) 243 return rcu_dereference(netdev->nf_hooks_egress); 244#endif 245 fallthrough; 246#endif 247 default: 248 return ERR_PTR(-EPROTONOSUPPORT); 249 } 250 251 return hook_head; 252} 253 254static int nfnl_hook_dump(struct sk_buff *nlskb, 255 struct netlink_callback *cb) 256{ 257 struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh); 258 struct nfnl_dump_hook_data *ctx = cb->data; 259 int err, family = nfmsg->nfgen_family; 260 struct net *net = sock_net(nlskb->sk); 261 struct nf_hook_ops * const *ops; 262 const struct nf_hook_entries *e; 263 unsigned int i = cb->args[0]; 264 265 rcu_read_lock(); 266 267 e = nfnl_hook_entries_head(family, ctx->hook, net, ctx->devname); 268 if (!e) 269 goto done; 270 271 if (IS_ERR(e)) { 272 cb->seq++; 273 goto done; 274 } 275 276 if ((unsigned long)e != ctx->headv || i >= e->num_hook_entries) 277 cb->seq++; 278 279 ops = nf_hook_entries_get_hook_ops(e); 280 281 for (; i < e->num_hook_entries; i++) { 282 err = nfnl_hook_dump_one(nlskb, ctx, ops[i], family, 283 cb->nlh->nlmsg_seq); 284 if (err) 285 break; 286 } 287 288done: 289 nl_dump_check_consistent(cb, nlmsg_hdr(nlskb)); 290 rcu_read_unlock(); 291 cb->args[0] = i; 292 return nlskb->len; 293} 294 295static int nfnl_hook_dump_start(struct netlink_callback *cb) 296{ 297 const struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh); 298 const struct nlattr * const *nla = cb->data; 299 struct nfnl_dump_hook_data *ctx = NULL; 300 struct net *net = sock_net(cb->skb->sk); 301 u8 family = nfmsg->nfgen_family; 302 char name[IFNAMSIZ] = ""; 303 const void *head; 304 u32 hooknum; 305 306 hooknum = ntohl(nla_get_be32(nla[NFNLA_HOOK_HOOKNUM])); 307 if (hooknum > 255) 308 return -EINVAL; 309 310 if (family == NFPROTO_NETDEV) { 311 if (!nla[NFNLA_HOOK_DEV]) 312 return -EINVAL; 313 314 nla_strscpy(name, nla[NFNLA_HOOK_DEV], sizeof(name)); 315 } 316 317 rcu_read_lock(); 318 /* Not dereferenced; for consistency check only */ 319 head = nfnl_hook_entries_head(family, hooknum, net, name); 320 rcu_read_unlock(); 321 322 if (head && IS_ERR(head)) 323 return PTR_ERR(head); 324 325 ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); 326 if (!ctx) 327 return -ENOMEM; 328 329 strscpy(ctx->devname, name, sizeof(ctx->devname)); 330 ctx->headv = (unsigned long)head; 331 ctx->hook = hooknum; 332 333 cb->seq = 1; 334 cb->data = ctx; 335 336 return 0; 337} 338 339static int nfnl_hook_dump_stop(struct netlink_callback *cb) 340{ 341 kfree(cb->data); 342 return 0; 343} 344 345static int nfnl_hook_get(struct sk_buff *skb, 346 const struct nfnl_info *info, 347 const struct nlattr * const nla[]) 348{ 349 if (!nla[NFNLA_HOOK_HOOKNUM]) 350 return -EINVAL; 351 352 if (info->nlh->nlmsg_flags & NLM_F_DUMP) { 353 struct netlink_dump_control c = { 354 .start = nfnl_hook_dump_start, 355 .done = nfnl_hook_dump_stop, 356 .dump = nfnl_hook_dump, 357 .module = THIS_MODULE, 358 .data = (void *)nla, 359 }; 360 361 return nf_netlink_dump_start_rcu(info->sk, skb, info->nlh, &c); 362 } 363 364 return -EOPNOTSUPP; 365} 366 367static const struct nfnl_callback nfnl_hook_cb[NFNL_MSG_HOOK_MAX] = { 368 [NFNL_MSG_HOOK_GET] = { 369 .call = nfnl_hook_get, 370 .type = NFNL_CB_RCU, 371 .attr_count = NFNLA_HOOK_MAX, 372 .policy = nfnl_hook_nla_policy 373 }, 374}; 375 376static const struct nfnetlink_subsystem nfhook_subsys = { 377 .name = "nfhook", 378 .subsys_id = NFNL_SUBSYS_HOOK, 379 .cb_count = NFNL_MSG_HOOK_MAX, 380 .cb = nfnl_hook_cb, 381}; 382 383MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_HOOK); 384 385static int __init nfnetlink_hook_init(void) 386{ 387 return nfnetlink_subsys_register(&nfhook_subsys); 388} 389 390static void __exit nfnetlink_hook_exit(void) 391{ 392 nfnetlink_subsys_unregister(&nfhook_subsys); 393} 394 395module_init(nfnetlink_hook_init); 396module_exit(nfnetlink_hook_exit); 397 398MODULE_LICENSE("GPL"); 399MODULE_AUTHOR("Florian Westphal <fw@strlen.de>"); 400MODULE_DESCRIPTION("nfnetlink_hook: list registered netfilter hooks");