linux
local_bh_enable() is called with interrupts disabled when HIGHMEM
Function udp_poll() acquires a spin lock via spin_lock_irq() before operating on the socket buffer. When a datagram is received, the checksum computation will call local_bh_enable() if HIGHMEM is enabled. But local_bh_enable() should be called with IRQs enabled, and thus the WARN(). Note that spin_lock_irq() takes a lock and disables interrupts.
Bug fixed by commit 208d89843b7
Type | WeakAssertionViolation |
Config | "HIGHMEM" (1st degree) |
Fix-in | code |
Location | kernel/ |
#include <stdlib.h> #define local_irq_enable() #define local_irq_disable() #define local_bh_enable() #define local_bh_disable() static inline void spin_lock_irq() { local_irq_disable(); } static inline void spin_unlock_irq() { local_irq_enable(); } static inline void spin_lock_bh() { local_bh_disable(); } static inline void spin_unlock_bh() { local_bh_enable(); } static inline void kunmap_skb_frag() { #ifdef CONFIG_HIGHMEM local_bh_enable(); // ERROR #endif } unsigned int skb_checksum() { unsigned int csum = 0; while (rand() % 2) { if (rand() % 2) { kunmap_skb_frag(); } } return csum; } static int udp_checksum_complete() { return skb_checksum(); } unsigned int udp_poll() { unsigned int mask = 0; spin_lock_irq(); while (rand() % 2) { udp_checksum_complete(); } spin_unlock_irq(); return mask; } int main(int argc, char** argv) { udp_poll(); return 0; }
diff --git a/simple/208d898.c b/simple/208d898.c --- a/simple/208d898.c +++ b/simple/208d898.c @@ -55,11 +55,11 @@ { unsigned int mask = 0; - spin_lock_irq(); + spin_lock_bh(); while (rand() % 2) { udp_checksum_complete(); } - spin_unlock_irq(); + spin_unlock_bh(); return mask; }
#include <stdlib.h> #define local_irq_enable() #define local_irq_disable() #define local_bh_enable() #define local_bh_disable() int main(int argc, char** argv) { // udp_poll(); unsigned int mask = 0; local_bh_disable(); while (rand() % 2) { unsigned int csum = 0; while (rand() % 2) { if (rand() % 2) { #ifdef CONFIG_HIGHMEM local_bh_enable(); // ERROR #endif } } } local_bh_enable(); return 0; }
. call net/ipv4/udp.c:1325: udp_poll() . 1337: spin_lock_irq(&rcvq->lock); // spin_lock_irq implies local_irq_disable() . 1339: if (udp_checksum_complete(skb)) { .. 766: udp_checksum_complete() .. 769: __udp_checksum_complete(skb); ... call net/ipv4/udp.c:761:__udp_checksum_complete(); ... 763: return (unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum)); .... call net/core/skbuff.c:1076: skb_checksum() .... 1110: kunmap_skb_frag(vaddr); ..... call include/linux/skbuff.h:1155:kunmap_skb_frag() ..... [HIGHMEM] 138: local_bh_enable(); ...... call kernel/softirq.c:138:local_bh_enable() ...... ERROR 140: WARN_ON(irqs_disabled())