Skip to content
  • Russell King's avatar
    kernel: fix hlist_bl again · 32385c7c
    Russell King authored
    
    
    __d_rehash is dereferencing an almost-NULL pointer on my ARM926.
    CONFIG_SMP=n and CONFIG_DEBUG_SPINLOCK=y.
    
    The faulting instruction is:    strne   r3, [r2, #4]
    and as can be seen from the register dump below, r2 is 0x00000001, hence
    the faulting 0x00000005 address.
    
    __d_rehash is essentially:
    
           spin_lock_bucket(b);
           entry->d_flags &= ~DCACHE_UNHASHED;
           hlist_bl_add_head_rcu(&entry->d_hash, &b->head);
           spin_unlock_bucket(b);
    
    which is:
    
           bit_spin_lock(0, (unsigned long *)&b->head.first);
           entry->d_flags &= ~DCACHE_UNHASHED;
           hlist_bl_add_head_rcu(&entry->d_hash, &b->head);
           __bit_spin_unlock(0, (unsigned long *)&b->head.first);
    
    bit_spin_lock(0, ptr) sets bit 0 of *ptr, in this case b->head.first if
    CONFIG_SMP or CONFIG_DEBUG_SPINLOCK is set:
    
    #if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
           while (unlikely(test_and_set_bit_lock(bitnum, addr))) {
                   while (test_bit(bitnum, addr)) {
                           preempt_enable();
                           cpu_relax();
                           preempt_disable();
                   }
           }
    #endif
    
    So, b->head.first starts off NULL, and becomes a non-NULL (address 1).
    hlist_bl_add_head_rcu() does this:
    
    static inline void hlist_bl_add_head_rcu(struct hlist_bl_node *n,
                                           struct hlist_bl_head *h)
    {
           first = hlist_bl_first(h);
           n->next = first;
           if (first)
                   first->pprev = &n->next;
    
    It is the store to first->pprev which is faulting.
    
    hlist_bl_first():
    
    static inline struct hlist_bl_node *hlist_bl_first(struct hlist_bl_head *h)
    {
           return (struct hlist_bl_node *)
                   ((unsigned long)h->first & ~LIST_BL_LOCKMASK);
    }
    
    but:
    #if defined(CONFIG_SMP)
    #define LIST_BL_LOCKMASK        1UL
    #else
    #define LIST_BL_LOCKMASK        0UL
    #endif
    
    So, we have one piece of code which sets bit 0 of addresses, and another
    bit of code which doesn't clear it before dereferencing the pointer if
    !CONFIG_SMP && CONFIG_DEBUG_SPINLOCK.  With the patch below, I can again
    sucessfully boot the kernel on my Versatile PB/926 platform.
    
    Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
    32385c7c