Devices going away have to grab the nfnl_lock from the netdev event path
to avoid races with control plane updates.

However, netlink dumps in netfilter do not hold nfnl_lock mutex. Cache
the device name into the objects to avoid an use-after-free situation
for a device that is going away.

Signed-off-by: Pablo Neira Ayuso <pa...@netfilter.org>
---
 include/net/netfilter/nf_tables.h |  4 ++++
 net/netfilter/nf_tables_api.c     | 15 +++++++++------
 2 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/include/net/netfilter/nf_tables.h 
b/include/net/netfilter/nf_tables.h
index 663b015dace5..30eb0652b025 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -1068,6 +1068,8 @@ struct nft_object_ops {
 int nft_register_obj(struct nft_object_type *obj_type);
 void nft_unregister_obj(struct nft_object_type *obj_type);
 
+#define NFT_FLOWTABLE_DEVICE_MAX       8
+
 /**
  *     struct nft_flowtable - nf_tables flow table
  *
@@ -1080,6 +1082,7 @@ void nft_unregister_obj(struct nft_object_type *obj_type);
  *     @genmask: generation mask
  *     @use: number of references to this flow table
  *     @handle: unique object handle
+ *     @dev_name: array of device names
  *     @data: rhashtable and garbage collector
  *     @ops: array of hooks
  */
@@ -1093,6 +1096,7 @@ struct nft_flowtable {
        u32                             genmask:2,
                                        use:30;
        u64                             handle;
+       char                            *dev_name[NFT_FLOWTABLE_DEVICE_MAX];
        /* runtime data below here */
        struct nf_hook_ops              *ops ____cacheline_aligned;
        struct nf_flowtable             data;
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 14777c404071..977d43e00f98 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -4932,8 +4932,6 @@ nf_tables_flowtable_lookup_byhandle(const struct 
nft_table *table,
        return ERR_PTR(-ENOENT);
 }
 
-#define NFT_FLOWTABLE_DEVICE_MAX       8
-
 static int nf_tables_parse_devices(const struct nft_ctx *ctx,
                                   const struct nlattr *attr,
                                   struct net_device *dev_array[], int *len)
@@ -5006,7 +5004,7 @@ static int nf_tables_flowtable_parse_hook(const struct 
nft_ctx *ctx,
        err = nf_tables_parse_devices(ctx, tb[NFTA_FLOWTABLE_HOOK_DEVS],
                                      dev_array, &n);
        if (err < 0)
-               goto err1;
+               return err;
 
        ops = kzalloc(sizeof(struct nf_hook_ops) * n, GFP_KERNEL);
        if (!ops) {
@@ -5026,6 +5024,8 @@ static int nf_tables_flowtable_parse_hook(const struct 
nft_ctx *ctx,
                flowtable->ops[i].priv          = &flowtable->data.rhashtable;
                flowtable->ops[i].hook          = flowtable->data.type->hook;
                flowtable->ops[i].dev           = dev_array[i];
+               flowtable->dev_name[i]          = kstrdup(dev_array[i]->name,
+                                                         GFP_KERNEL);
        }
 
        err = 0;
@@ -5203,8 +5203,10 @@ static int nf_tables_newflowtable(struct net *net, 
struct sock *nlsk,
 err5:
        i = flowtable->ops_len;
 err4:
-       for (k = i - 1; k >= 0; k--)
+       for (k = i - 1; k >= 0; k--) {
+               kfree(flowtable->dev_name[k]);
                nf_unregister_net_hook(net, &flowtable->ops[k]);
+       }
 
        kfree(flowtable->ops);
 err3:
@@ -5294,9 +5296,9 @@ static int nf_tables_fill_flowtable_info(struct sk_buff 
*skb, struct net *net,
                goto nla_put_failure;
 
        for (i = 0; i < flowtable->ops_len; i++) {
-               if (flowtable->ops[i].dev &&
+               if (flowtable->dev_name[i][0] &&
                    nla_put_string(skb, NFTA_DEVICE_NAME,
-                                  flowtable->ops[i].dev->name))
+                                  flowtable->dev_name[i]))
                        goto nla_put_failure;
        }
        nla_nest_end(skb, nest_devs);
@@ -5538,6 +5540,7 @@ static void nft_flowtable_event(unsigned long event, 
struct net_device *dev,
                        continue;
 
                nf_unregister_net_hook(dev_net(dev), &flowtable->ops[i]);
+               flowtable->dev_name[i][0] = '\0';
                flowtable->ops[i].dev = NULL;
                break;
        }
-- 
2.11.0

Reply via email to