xfrm_hash_rebuild() re-inserts existing policies into the hashtables,
so it should not insert a same policy in the same place twice. This
means we have to pass excl==1 to xfrm_policy_inexact_insert() and ignore
the -EEXIST error. Otherwise we end up having an entry in the hashtable
points to itself, which leads to a use-after-free as reported by syzbot.

Inside xfrm_policy_inexact_insert(), xfrm_policy_insert_list() could
only return either a NULL pointer, a valid non-NULL pointer, or an error
pointer (-EEXIST) when excl==1. Testing delpol && excl for -EEXIST
is incorrect as it could return a valid pointer for excl case too,
testing IS_ERR(delpol) is correct.

Reported-and-tested-by: syzbot+9d971dd21eb265670...@syzkaller.appspotmail.com
Fixes: 24969facd704 ("xfrm: policy: store inexact policies in an rhashtable")
Cc: Florian Westphal <f...@strlen.de>
Signed-off-by: Cong Wang <xiyou.wangc...@gmail.com>
---
 net/xfrm/xfrm_policy.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 934492bad8e0..c77c44b975ca 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -1193,9 +1193,9 @@ xfrm_policy_inexact_insert(struct xfrm_policy *policy, u8 
dir, int excl)
        }
 
        delpol = xfrm_policy_insert_list(chain, policy, excl);
-       if (delpol && excl) {
+       if (IS_ERR(delpol)) {
                __xfrm_policy_inexact_prune_bin(bin, false);
-               return ERR_PTR(-EEXIST);
+               return delpol;
        }
 
        chain = &net->xfrm.policy_inexact[dir];
@@ -1314,9 +1314,10 @@ static void xfrm_hash_rebuild(struct work_struct *work)
                chain = policy_hash_bysel(net, &policy->selector,
                                          policy->family, dir);
                if (!chain) {
-                       void *p = xfrm_policy_inexact_insert(policy, dir, 0);
+                       void *p = xfrm_policy_inexact_insert(policy, dir, 1);
 
-                       WARN_ONCE(IS_ERR(p), "reinsert: %ld\n", PTR_ERR(p));
+                       if (IS_ERR(p) && PTR_ERR(p) != -EEXIST)
+                               WARN_ONCE(1, "reinsert fails: %ld\n", 
PTR_ERR(p));
                        continue;
                }
 
-- 
2.19.2

Reply via email to