diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 55b4cadf290a..6448ded6d0ea 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -1084,6 +1084,7 @@ struct nft_table { u16 family:6, flags:8, genmask:2; + u32 nlpid; char *name; u16 udlen; u8 *udata; diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h index 98272cb5f617..806265887d5c 100644 --- a/include/uapi/linux/netfilter/nf_tables.h +++ b/include/uapi/linux/netfilter/nf_tables.h @@ -97,6 +97,8 @@ enum nft_verdicts { * @NFT_MSG_NEWFLOWTABLE: add new flow table (enum nft_flowtable_attributes) * @NFT_MSG_GETFLOWTABLE: get flow table (enum nft_flowtable_attributes) * @NFT_MSG_DELFLOWTABLE: delete flow table (enum nft_flowtable_attributes) + * @NFT_MSG_SETOWNER: set ruleset owner (enum nft_owner_attributes) + * @NFT_MSG_UNSETOWNER: unset ruleset owner (enum nft_owner_attributes) */ enum nf_tables_msg_types { NFT_MSG_NEWTABLE, @@ -124,6 +126,8 @@ enum nf_tables_msg_types { NFT_MSG_NEWFLOWTABLE, NFT_MSG_GETFLOWTABLE, NFT_MSG_DELFLOWTABLE, + NFT_MSG_SETOWNER, + NFT_MSG_UNSETOWNER, NFT_MSG_MAX, }; @@ -1474,6 +1478,15 @@ enum nft_gen_attributes { }; #define NFTA_GEN_MAX (__NFTA_GEN_MAX - 1) +/** + * enum nft_owner_attributes - nf_tables netlink ownership + */ +enum nft_owner_attributes { + NFTA_OWNER_UNSPEC, + __NFTA_OWNER_MAX +}; +#define NFTA_OWNER_MAX (__NFTA_OWNER_MAX - 1) + /* * enum nft_fib_attributes - nf_tables fib expression netlink attributes * diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 0f58e98542be..558a575558be 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -32,6 +32,29 @@ static LIST_HEAD(nf_tables_destroy_list); static DEFINE_SPINLOCK(nf_tables_destroy_list_lock); static u64 table_handle; +struct nft_owner { + struct list_head list; + possible_net_t net; + u32 nlpid; +}; + +static LIST_HEAD(nft_owner_list); + +static struct nft_owner *nf_tables_set_owner_find(struct net *net, u32 nlpid) +{ + struct nft_owner *owner; + struct net *owner_net; + + list_for_each_entry(owner, &nft_owner_list, list) { + owner_net = read_pnet(&owner->net); + if (owner_net == net && + owner->nlpid == nlpid) + return owner; + } + + return NULL; +} + enum { NFT_VALIDATE_SKIP = 0, NFT_VALIDATE_NEED, @@ -508,7 +531,7 @@ static int nft_delflowtable(struct nft_ctx *ctx, static struct nft_table *nft_table_lookup(const struct net *net, const struct nlattr *nla, - u8 family, u8 genmask) + u8 family, u8 genmask, u32 nlpid) { struct nft_table *table; @@ -519,8 +542,12 @@ static struct nft_table *nft_table_lookup(const struct net *net, lockdep_is_held(&net->nft.commit_mutex)) { if (!nla_strcmp(nla, table->name) && table->family == family && - nft_active_genmask(table, genmask)) + nft_active_genmask(table, genmask)) { + if (nlpid && table->nlpid && table->nlpid != nlpid) + return ERR_PTR(-EPERM); + return table; + } } return ERR_PTR(-ENOENT); @@ -819,7 +846,7 @@ static int nf_tables_gettable(struct net *net, struct sock *nlsk, return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c); } - table = nft_table_lookup(net, nla[NFTA_TABLE_NAME], family, genmask); + table = nft_table_lookup(net, nla[NFTA_TABLE_NAME], family, genmask, 0); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_TABLE_NAME]); return PTR_ERR(table); @@ -1001,7 +1028,8 @@ static int nf_tables_newtable(struct net *net, struct sock *nlsk, lockdep_assert_held(&net->nft.commit_mutex); attr = nla[NFTA_TABLE_NAME]; - table = nft_table_lookup(net, attr, family, genmask); + table = nft_table_lookup(net, attr, family, genmask, + NETLINK_CB(skb).portid); if (IS_ERR(table)) { if (PTR_ERR(table) != -ENOENT) return PTR_ERR(table); @@ -1052,6 +1080,9 @@ static int nf_tables_newtable(struct net *net, struct sock *nlsk, table->flags = flags; table->handle = ++table_handle; + if (nf_tables_set_owner_find(net, NETLINK_CB(skb).portid)) + table->nlpid = NETLINK_CB(skb).portid; + nft_ctx_init(&ctx, net, skb, nlh, family, table, NULL, nla); err = nft_trans_table_add(&ctx, NFT_MSG_NEWTABLE); if (err < 0) @@ -1158,6 +1189,9 @@ static int nft_flush(struct nft_ctx *ctx, int family) if (!nft_is_active_next(ctx->net, table)) continue; + if (table->nlpid && table->nlpid != ctx->portid) + continue; + if (nla[NFTA_TABLE_NAME] && nla_strcmp(nla[NFTA_TABLE_NAME], table->name) != 0) continue; @@ -1194,7 +1228,8 @@ static int nf_tables_deltable(struct net *net, struct sock *nlsk, table = nft_table_lookup_byhandle(net, attr, genmask); } else { attr = nla[NFTA_TABLE_NAME]; - table = nft_table_lookup(net, attr, family, genmask); + table = nft_table_lookup(net, attr, family, genmask, + NETLINK_CB(skb).portid); } if (IS_ERR(table)) { @@ -1577,7 +1612,7 @@ static int nf_tables_getchain(struct net *net, struct sock *nlsk, return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c); } - table = nft_table_lookup(net, nla[NFTA_CHAIN_TABLE], family, genmask); + table = nft_table_lookup(net, nla[NFTA_CHAIN_TABLE], family, genmask, 0); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_CHAIN_TABLE]); return PTR_ERR(table); @@ -2293,7 +2328,8 @@ static int nf_tables_newchain(struct net *net, struct sock *nlsk, lockdep_assert_held(&net->nft.commit_mutex); - table = nft_table_lookup(net, nla[NFTA_CHAIN_TABLE], family, genmask); + table = nft_table_lookup(net, nla[NFTA_CHAIN_TABLE], family, genmask, + NETLINK_CB(skb).portid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_CHAIN_TABLE]); return PTR_ERR(table); @@ -2389,7 +2425,8 @@ static int nf_tables_delchain(struct net *net, struct sock *nlsk, u32 use; int err; - table = nft_table_lookup(net, nla[NFTA_CHAIN_TABLE], family, genmask); + table = nft_table_lookup(net, nla[NFTA_CHAIN_TABLE], family, genmask, + NETLINK_CB(skb).portid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_CHAIN_TABLE]); return PTR_ERR(table); @@ -3035,7 +3072,7 @@ static int nf_tables_getrule(struct net *net, struct sock *nlsk, return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c); } - table = nft_table_lookup(net, nla[NFTA_RULE_TABLE], family, genmask); + table = nft_table_lookup(net, nla[NFTA_RULE_TABLE], family, genmask, 0); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_TABLE]); return PTR_ERR(table); @@ -3173,7 +3210,8 @@ static int nf_tables_newrule(struct net *net, struct sock *nlsk, lockdep_assert_held(&net->nft.commit_mutex); - table = nft_table_lookup(net, nla[NFTA_RULE_TABLE], family, genmask); + table = nft_table_lookup(net, nla[NFTA_RULE_TABLE], family, genmask, + NETLINK_CB(skb).portid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_TABLE]); return PTR_ERR(table); @@ -3397,7 +3435,8 @@ static int nf_tables_delrule(struct net *net, struct sock *nlsk, int family = nfmsg->nfgen_family, err = 0; struct nft_ctx ctx; - table = nft_table_lookup(net, nla[NFTA_RULE_TABLE], family, genmask); + table = nft_table_lookup(net, nla[NFTA_RULE_TABLE], family, genmask, + NETLINK_CB(skb).portid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_TABLE]); return PTR_ERR(table); @@ -3577,7 +3616,7 @@ static int nft_ctx_init_from_setattr(struct nft_ctx *ctx, struct net *net, const struct nlmsghdr *nlh, const struct nlattr * const nla[], struct netlink_ext_ack *extack, - u8 genmask) + u8 genmask, u32 nlpid) { const struct nfgenmsg *nfmsg = nlmsg_data(nlh); int family = nfmsg->nfgen_family; @@ -3585,7 +3624,7 @@ static int nft_ctx_init_from_setattr(struct nft_ctx *ctx, struct net *net, if (nla[NFTA_SET_TABLE] != NULL) { table = nft_table_lookup(net, nla[NFTA_SET_TABLE], family, - genmask); + genmask, nlpid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_SET_TABLE]); return PTR_ERR(table); @@ -3988,7 +4027,7 @@ static int nf_tables_getset(struct net *net, struct sock *nlsk, /* Verify existence before starting dump */ err = nft_ctx_init_from_setattr(&ctx, net, skb, nlh, nla, extack, - genmask); + genmask, NETLINK_CB(skb).portid); if (err < 0) return err; @@ -4217,7 +4256,8 @@ static int nf_tables_newset(struct net *net, struct sock *nlsk, if (nla[NFTA_SET_EXPR]) desc.expr = true; - table = nft_table_lookup(net, nla[NFTA_SET_TABLE], family, genmask); + table = nft_table_lookup(net, nla[NFTA_SET_TABLE], family, genmask, + NETLINK_CB(skb).portid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_SET_TABLE]); return PTR_ERR(table); @@ -4364,7 +4404,7 @@ static int nf_tables_delset(struct net *net, struct sock *nlsk, return -EINVAL; err = nft_ctx_init_from_setattr(&ctx, net, skb, nlh, nla, extack, - genmask); + genmask, NETLINK_CB(skb).portid); if (err < 0) return err; @@ -4552,14 +4592,14 @@ static int nft_ctx_init_from_elemattr(struct nft_ctx *ctx, struct net *net, const struct nlmsghdr *nlh, const struct nlattr * const nla[], struct netlink_ext_ack *extack, - u8 genmask) + u8 genmask, u32 nlpid) { const struct nfgenmsg *nfmsg = nlmsg_data(nlh); int family = nfmsg->nfgen_family; struct nft_table *table; table = nft_table_lookup(net, nla[NFTA_SET_ELEM_LIST_TABLE], family, - genmask); + genmask, nlpid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_SET_ELEM_LIST_TABLE]); return PTR_ERR(table); @@ -4939,7 +4979,7 @@ static int nf_tables_getsetelem(struct net *net, struct sock *nlsk, int rem, err = 0; err = nft_ctx_init_from_elemattr(&ctx, net, skb, nlh, nla, extack, - genmask); + genmask, NETLINK_CB(skb).portid); if (err < 0) return err; @@ -5426,7 +5466,7 @@ static int nf_tables_newsetelem(struct net *net, struct sock *nlsk, return -EINVAL; err = nft_ctx_init_from_elemattr(&ctx, net, skb, nlh, nla, extack, - genmask); + genmask, NETLINK_CB(skb).portid); if (err < 0) return err; @@ -5634,7 +5674,7 @@ static int nf_tables_delsetelem(struct net *net, struct sock *nlsk, int rem, err = 0; err = nft_ctx_init_from_elemattr(&ctx, net, skb, nlh, nla, extack, - genmask); + genmask, NETLINK_CB(skb).portid); if (err < 0) return err; @@ -5937,7 +5977,8 @@ static int nf_tables_newobj(struct net *net, struct sock *nlsk, !nla[NFTA_OBJ_DATA]) return -EINVAL; - table = nft_table_lookup(net, nla[NFTA_OBJ_TABLE], family, genmask); + table = nft_table_lookup(net, nla[NFTA_OBJ_TABLE], family, genmask, + NETLINK_CB(skb).portid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_OBJ_TABLE]); return PTR_ERR(table); @@ -6207,7 +6248,7 @@ static int nf_tables_getobj(struct net *net, struct sock *nlsk, !nla[NFTA_OBJ_TYPE]) return -EINVAL; - table = nft_table_lookup(net, nla[NFTA_OBJ_TABLE], family, genmask); + table = nft_table_lookup(net, nla[NFTA_OBJ_TABLE], family, genmask, 0); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_OBJ_TABLE]); return PTR_ERR(table); @@ -6281,7 +6322,8 @@ static int nf_tables_delobj(struct net *net, struct sock *nlsk, (!nla[NFTA_OBJ_NAME] && !nla[NFTA_OBJ_HANDLE])) return -EINVAL; - table = nft_table_lookup(net, nla[NFTA_OBJ_TABLE], family, genmask); + table = nft_table_lookup(net, nla[NFTA_OBJ_TABLE], family, genmask, + NETLINK_CB(skb).portid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_OBJ_TABLE]); return PTR_ERR(table); @@ -6698,7 +6740,7 @@ static int nf_tables_newflowtable(struct net *net, struct sock *nlsk, return -EINVAL; table = nft_table_lookup(net, nla[NFTA_FLOWTABLE_TABLE], family, - genmask); + genmask, NETLINK_CB(skb).portid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_FLOWTABLE_TABLE]); return PTR_ERR(table); @@ -6882,7 +6924,7 @@ static int nf_tables_delflowtable(struct net *net, struct sock *nlsk, return -EINVAL; table = nft_table_lookup(net, nla[NFTA_FLOWTABLE_TABLE], family, - genmask); + genmask, NETLINK_CB(skb).portid); if (IS_ERR(table)) { NL_SET_BAD_ATTR(extack, nla[NFTA_FLOWTABLE_TABLE]); return PTR_ERR(table); @@ -7090,7 +7132,7 @@ static int nf_tables_getflowtable(struct net *net, struct sock *nlsk, return -EINVAL; table = nft_table_lookup(net, nla[NFTA_FLOWTABLE_TABLE], family, - genmask); + genmask, 0); if (IS_ERR(table)) return PTR_ERR(table); @@ -7306,6 +7348,89 @@ static int nf_tables_getgen(struct net *net, struct sock *nlsk, return err; } +static const struct nla_policy nft_owner_policy[NFTA_OWNER_MAX + 1] = { +}; + +/* XXX locking */ +static int nf_tables_set_owner(struct net *net, struct sock *nlsk, + struct sk_buff *skb, const struct nlmsghdr *nlh, + const struct nlattr * const nla[], + struct netlink_ext_ack *extack) +{ + struct nft_owner *binding; + + if (nf_tables_set_owner_find(net, NETLINK_CB(skb).portid)) + return -EEXIST; + + binding = kmalloc(sizeof(*binding), GFP_KERNEL); + if (!binding) + return -ENOMEM; + + binding->nlpid = NETLINK_CB(skb).portid; + write_pnet(&binding->net, net); + list_add(&binding->list, &nft_owner_list); + + return 0; +} + +/* XXX locking */ +static int nf_tables_unset_owner(struct net *net, struct sock *nlsk, + struct sk_buff *skb, const struct nlmsghdr *nlh, + const struct nlattr * const nla[], + struct netlink_ext_ack *extack) +{ + struct nft_owner *owner; + + owner = nf_tables_set_owner_find(net, NETLINK_CB(skb).portid); + if (!owner) + return -ENOENT; + + list_del(&owner->list); + kfree(owner); + + return 0; +} + +/* XXX locking */ +static int nft_rcv_nl_event(struct notifier_block *this, unsigned long event, + void *ptr) +{ + struct netlink_notify *n = ptr; + struct nft_ctx ctx = {}; + struct nft_table *table; + struct nft_owner *owner; + bool found = false; + + if (event != NETLINK_URELEASE || n->protocol != NETLINK_NETFILTER) + return NOTIFY_DONE; + + list_for_each_entry(owner, &nft_owner_list, list) { + if (n->portid == owner->nlpid) { + found = true; + break; + } + } + + if (!found) + return NOTIFY_DONE; + + list_for_each_entry(table, &n->net->nft.tables, list) { + ctx.family = table->family; + ctx.table = table; + + if (table->nlpid == n->portid) + nft_flush_table(&ctx); + } + list_del(&owner->list); + kfree(owner); + + return NOTIFY_DONE; +} + +static struct notifier_block nft_rtnl_notifier = { + .notifier_call = nft_rcv_nl_event, +}; + static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = { [NFT_MSG_NEWTABLE] = { .call_batch = nf_tables_newtable, @@ -7420,6 +7545,16 @@ static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = { .attr_count = NFTA_FLOWTABLE_MAX, .policy = nft_flowtable_policy, }, + [NFT_MSG_SETOWNER] = { + .call = nf_tables_set_owner, + .attr_count = NFTA_OWNER_MAX, + .policy = nft_owner_policy, + }, + [NFT_MSG_UNSETOWNER] = { + .call = nf_tables_unset_owner, + .attr_count = NFTA_OWNER_MAX, + .policy = nft_owner_policy, + }, }; static int nf_tables_validate(struct net *net) @@ -8865,43 +9000,48 @@ static int __init nf_tables_module_init(void) err = nft_chain_filter_init(); if (err < 0) - goto err1; + goto err_chain_filter; err = nf_tables_core_module_init(); if (err < 0) - goto err2; + goto err_core_module; err = register_netdevice_notifier(&nf_tables_flowtable_notifier); if (err < 0) - goto err3; + goto err_netdev_notifier; err = rhltable_init(&nft_objname_ht, &nft_objname_ht_params); if (err < 0) - goto err4; + goto err_rht_objname; err = nft_offload_init(); if (err < 0) - goto err5; + goto err_offload; + + err = netlink_register_notifier(&nft_rtnl_notifier); + if (err < 0) + goto err_netlink_notifier; /* must be last */ err = nfnetlink_subsys_register(&nf_tables_subsys); if (err < 0) - goto err6; + goto err_nfnl_subsys; nft_chain_route_init(); - return err; -err6: +err_nfnl_subsys: + netlink_unregister_notifier(&nft_rtnl_notifier); +err_netlink_notifier: nft_offload_exit(); -err5: +err_offload: rhltable_destroy(&nft_objname_ht); -err4: +err_rht_objname: unregister_netdevice_notifier(&nf_tables_flowtable_notifier); -err3: +err_netdev_notifier: nf_tables_core_module_exit(); -err2: +err_core_module: nft_chain_filter_fini(); -err1: +err_chain_filter: unregister_pernet_subsys(&nf_tables_net_ops); return err; } @@ -8909,6 +9049,7 @@ static int __init nf_tables_module_init(void) static void __exit nf_tables_module_exit(void) { nfnetlink_subsys_unregister(&nf_tables_subsys); + netlink_register_notifier(&nft_rtnl_notifier); nft_offload_exit(); unregister_netdevice_notifier(&nf_tables_flowtable_notifier); nft_chain_filter_fini();