linux
Unexpected decrement of the preemption counter
__run_timers() executes a task and expects that the preemption count before the execution equals the preemption count after the execution. When this task is inet_twdr_hangman() the condition is violated; if TCP_MD5SIG is enabled we call tcp_put_md5sig_pool(), which entails a put_cpu().
Bug fixed by commit 657e9649e74
Type | FatalAssertionViolation |
Config | "TCP_MD5SIG && PREEMPT" (2nd degree) |
Fix-in | code |
Location | net/ipv4 |
#include <assert.h> __attribute__ ((noinline)) int nondet() { return 42; } static int preempt_counter = 0; #define preempt_count() (preempt_counter) #ifdef CONFIG_PREEMPT #define put_cpu() (preempt_counter--) #else #define put_cpu() #endif #ifdef CONFIG_TCP_MD5SIG #define free_cpu() #endif static inline void tcp_free_md5sig_pool(void) { free_cpu(); } static inline void tcp_put_md5sig_pool(void) { put_cpu(); // (9) } void tcp_twsk_destructor() { #ifdef CONFIG_TCP_MD5SIG if (nondet()) tcp_put_md5sig_pool(); // (8) #endif } static inline void twsk_destructor() { tcp_twsk_destructor(); // (7) } static void inet_twsk_free() { twsk_destructor(); // (6) } void inet_twsk_put() { inet_twsk_free(); // (5) } static int inet_twdr_do_twkill_work() { inet_twsk_put(); // (4) return 0; } void inet_twdr_hangman() { inet_twdr_do_twkill_work(); // (3) } static inline void __run_timers() { int preempt_count = preempt_count(); inet_twdr_hangman(); // (2) if (preempt_count != preempt_count()) { assert(0); // (10) ERROR } } int main(void) { __run_timers(); // (1) return 0; }
diff --git a/simple/657e964.c b/simple/657e964.c --- a/simple/657e964.c +++ b/simple/657e964.c @@ -37,7 +37,7 @@ { #ifdef CONFIG_TCP_MD5SIG if (unk()) - tcp_put_md5sig_pool(); + tcp_free_md5sig_pool(); #endif }
#include <assert.h> #include <stdbool.h> #ifdef UNK_TRUE #define unk() 1 #else #include <stdlib.h> #define unk() (rand() % 2) #endif int main(int argc, char** argv) { // __run_timers(); int preempt_counter = 0; int preempt_count = preempt_counter; // inet_twdr_hangman(); #ifdef CONFIG_TCP_MD5SIG if (unk()) #ifdef CONFIG_PREEMPT preempt_counter-- #else #endif #endif if (preempt_count != preempt_counter) { assert(false); // ERROR } return 0; }
. // net/ipv4/tcp_minisocks.c:45: sets a tw_timer to inet_twdr_hangman . call kernel/timer.c:929:__run_timers() . // eventually the timer is executed . 964: int preempt_count = preempt_count(); . 987: fn(data); .. dyn-call net/ipv4/inet_timewait_sock.c:201:inet_twdr_hangman() .. 213: if (inet_twdr_do_twkill_work(twdr, twdr->slot)) { ... call net/ipv4/inet_timewait_sock.c:153:inet_twdr_do_twkill_work() ... 177: inet_twsk_put(tw); .... call net/ipv4/inet_timewait_sock.c:65:inet_twsk_put() .... 68: inet_twsk_free(tw); ..... call net/ipv4/inet_timewait_sock.c:53:inet_twsk_free() ..... 56: twsk_destructor((struct sock *)tw); ...... call include/net/timewait_sock.h:33:twsk_destructor() ...... 39: sk->sk_prot->twsk_prot->twsk_destructor(sk); ....... dyn-call net/ipv4/tcp_minisocks.c:361:tcp_twsk_destructor() ....... [TCP_MD5SIG] 366: tcp_put_md5sig_pool(); ........ call include/net/tcp.h:1217:tcp_put_md5sig_pool() ........ 1220: put_cpu(); // if CONFIG_PREEMPT put_cpu() decrements preempt_counter . 991: if (preempt_count != preempt_count()) { // thus this test succeeds . ERROR 997: BUG();