From: Florian Westphal <f...@strlen.de>

[ Upstream commit 2c82c7e724ff51cab78e1afd5c2aaa31994fe41e ]

We can oops in nf_tables_fill_rule_info().

Its not possible to fetch previous element in rcu-protected lists
when deletions are not prevented somehow: list_del_rcu poisons
the ->prev pointer value.

Before rcu-conversion this was safe as dump operations did hold
nfnetlink mutex.

Pass previous rule as argument, obtained by keeping a pointer to
the previous rule during traversal.

Fixes: d9adf22a291883 ("netfilter: nf_tables: use call_rcu in netlink dumps")
Signed-off-by: Florian Westphal <f...@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pa...@netfilter.org>
Signed-off-by: Sasha Levin <sas...@kernel.org>
---
 net/netfilter/nf_tables_api.c | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 1606eaa5ae0d..041a81185c6a 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -2256,13 +2256,13 @@ static int nf_tables_fill_rule_info(struct sk_buff 
*skb, struct net *net,
                                    u32 flags, int family,
                                    const struct nft_table *table,
                                    const struct nft_chain *chain,
-                                   const struct nft_rule *rule)
+                                   const struct nft_rule *rule,
+                                   const struct nft_rule *prule)
 {
        struct nlmsghdr *nlh;
        struct nfgenmsg *nfmsg;
        const struct nft_expr *expr, *next;
        struct nlattr *list;
-       const struct nft_rule *prule;
        u16 type = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, event);
 
        nlh = nlmsg_put(skb, portid, seq, type, sizeof(struct nfgenmsg), flags);
@@ -2282,8 +2282,7 @@ static int nf_tables_fill_rule_info(struct sk_buff *skb, 
struct net *net,
                         NFTA_RULE_PAD))
                goto nla_put_failure;
 
-       if ((event != NFT_MSG_DELRULE) && (rule->list.prev != &chain->rules)) {
-               prule = list_prev_entry(rule, list);
+       if (event != NFT_MSG_DELRULE && prule) {
                if (nla_put_be64(skb, NFTA_RULE_POSITION,
                                 cpu_to_be64(prule->handle),
                                 NFTA_RULE_PAD))
@@ -2330,7 +2329,7 @@ static void nf_tables_rule_notify(const struct nft_ctx 
*ctx,
 
        err = nf_tables_fill_rule_info(skb, ctx->net, ctx->portid, ctx->seq,
                                       event, 0, ctx->family, ctx->table,
-                                      ctx->chain, rule);
+                                      ctx->chain, rule, NULL);
        if (err < 0) {
                kfree_skb(skb);
                goto err;
@@ -2355,12 +2354,13 @@ static int __nf_tables_dump_rules(struct sk_buff *skb,
                                  const struct nft_chain *chain)
 {
        struct net *net = sock_net(skb->sk);
+       const struct nft_rule *rule, *prule;
        unsigned int s_idx = cb->args[0];
-       const struct nft_rule *rule;
 
+       prule = NULL;
        list_for_each_entry_rcu(rule, &chain->rules, list) {
                if (!nft_is_active(net, rule))
-                       goto cont;
+                       goto cont_skip;
                if (*idx < s_idx)
                        goto cont;
                if (*idx > s_idx) {
@@ -2372,11 +2372,13 @@ static int __nf_tables_dump_rules(struct sk_buff *skb,
                                        NFT_MSG_NEWRULE,
                                        NLM_F_MULTI | NLM_F_APPEND,
                                        table->family,
-                                       table, chain, rule) < 0)
+                                       table, chain, rule, prule) < 0)
                        return 1;
 
                nl_dump_check_consistent(cb, nlmsg_hdr(skb));
 cont:
+               prule = rule;
+cont_skip:
                (*idx)++;
        }
        return 0;
@@ -2532,7 +2534,7 @@ static int nf_tables_getrule(struct net *net, struct sock 
*nlsk,
 
        err = nf_tables_fill_rule_info(skb2, net, NETLINK_CB(skb).portid,
                                       nlh->nlmsg_seq, NFT_MSG_NEWRULE, 0,
-                                      family, table, chain, rule);
+                                      family, table, chain, rule, NULL);
        if (err < 0)
                goto err;
 
-- 
2.20.1

Reply via email to