• Florian Westphal's avatar
    xfrm: policy: fix bydst hlist corruption on hash rebuild · fd709721
    Florian Westphal authored
    syzbot reported following spat:
    
    BUG: KASAN: use-after-free in __write_once_size include/linux/compiler.h:221
    BUG: KASAN: use-after-free in hlist_del_rcu include/linux/rculist.h:455
    BUG: KASAN: use-after-free in xfrm_hash_rebuild+0xa0d/0x1000 net/xfrm/xfrm_policy.c:1318
    Write of size 8 at addr ffff888095e79c00 by task kworker/1:3/8066
    Workqueue: events xfrm_hash_rebuild
    Call Trace:
     __write_once_size include/linux/compiler.h:221 [inline]
     hlist_del_rcu include/linux/rculist.h:455 [inline]
     xfrm_hash_rebuild+0xa0d/0x1000 net/xfrm/xfrm_policy.c:1318
     process_one_work+0x814/0x1130 kernel/workqueue.c:2269
    Allocated by task 8064:
     __kmalloc+0x23c/0x310 mm/slab.c:3669
     kzalloc include/linux/slab.h:742 [inline]
     xfrm_hash_alloc+0x38/0xe0 net/xfrm/xfrm_hash.c:21
     xfrm_policy_init net/xfrm/xfrm_policy.c:4036 [inline]
     xfrm_net_init+0x269/0xd60 net/xfrm/xfrm_policy.c:4120
     ops_init+0x336/0x420 net/core/net_namespace.c:130
     setup_net+0x212/0x690 net/core/net_namespace.c:316
    
    The faulting address is the address of the old chain head,
    free'd by xfrm_hash_resize().
    
    In xfrm_hash_rehash(), chain heads get re-initialized without
    any hlist_del_rcu:
    
     for (i = hmask; i >= 0; i--)
        INIT_HLIST_HEAD(odst + i);
    
    Then, hlist_del_rcu() gets called on the about to-be-reinserted policy
    when iterating the per-net list of policies.
    
    hlist_del_rcu() will then make chain->first be nonzero again:
    
    static inline void __hlist_del(struct hlist_node *n)
    {
       struct hlist_node *next = n->next;   // address of next element in list
       struct hlist_node **pprev = n->pprev;// location of previous elem, this
                                            // can point at chain->first
            WRITE_ONCE(*pprev, next);       // chain->first points to next elem
            if (next)
                    next->pprev = pprev;
    
    Then, when we walk chainlist to find insertion point, we may find a
    non-empty list even though we're supposedly reinserting the first
    policy to an empty chain.
    
    To fix this first unlink all exact and inexact policies instead of
    zeroing the list heads.
    
    Add the commands equivalent to the syzbot reproducer to xfrm_policy.sh,
    without fix KASAN catches the corruption as it happens, SLUB poisoning
    detects it a bit later.
    
    Reported-by: syzbot+0165480d4ef07360eeda@syzkaller.appspotmail.com
    Fixes: 1548bc4e ("xfrm: policy: delete inexact policies from inexact list on hash rebuild")
    Signed-off-by: default avatarFlorian Westphal <fw@strlen.de>
    Signed-off-by: default avatarSteffen Klassert <steffen.klassert@secunet.com>
    fd709721
xfrm_policy.sh 13 KB