DF-0453 / harness.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | /* * DF-0453 harness: rfcomm_session_newconn arms timeout on wrong session * * Source: sys/netbt/rfcomm_session.c:416-431 * * The bug: callout_reset(&rs->rs_timeout, ...) at line 427 uses `rs` * (the LISTENER session passed as `arg`) instead of `new` (the freshly * allocated session at line 416). The comment at 423-426 says "schedule * an expiry so that if nothing comes of it we can punt" โ clearly * referring to the NEW session. * * Consequence 1: `new` has NO expiry timer -> if peer opens L2CAP but never * sends SABM(0), the session leaks indefinitely. * Consequence 2: `rs` (listener) gets a spurious mcc_timeout -> fires * rfcomm_session_timeout on the listener, potentially closing/freeing it. * * This harness replicates the callout logic to demonstrate that the wrong * pointer is used. We simulate two sessions and show the timer ends up on * the listener, not the new session. * * No Bluetooth hardware required โ this is a pure logic bug verifiable * by source inspection. */ #include <stdio.h> #include <stdlib.h> #include <string.h> struct session { int id; int is_listener; int has_timeout; void *timeout_arg; int state; }; #define RFCOMM_SESSION_WAIT_CONNECT 1 /* Simulated rfcomm_session_alloc */ static struct session *alloc_session(int id) { struct session *s = calloc(1, sizeof(*s)); s->id = id; s->state = 0; return s; } /* Replicates rfcomm_session_newconn exactly (lines 416-431) */ static struct session *buggy_newconn(struct session *rs /* listener */) { struct session *new = alloc_session(rs->id + 100); new->state = RFCOMM_SESSION_WAIT_CONNECT; /* * BUG: callout_reset(&rs->rs_timeout, ..., rfcomm_session_timeout, rs) * Should be: callout_reset(&new->rs_timeout, ..., ..., new) */ rs->has_timeout = 1; /* arms on LISTENER */ rs->timeout_arg = rs; /* passes LISTENER as arg */ /* new->has_timeout remains 0 โ NO timer on new session */ return new; } /* What the fix should do */ static struct session *fixed_newconn(struct session *rs /* listener */) { struct session *new = alloc_session(rs->id + 100); new->state = RFCOMM_SESSION_WAIT_CONNECT; /* FIX: arm on new, not rs */ new->has_timeout = 1; new->timeout_arg = new; return new; } int main(void) { struct session *listener = alloc_session(0); listener->is_listener = 1; printf("=== DF-0453: rfcomm_session_newconn wrong session var ===\n\n"); printf("Listener session id=%d (is_listener=%d)\n", listener->id, listener->is_listener); /* BUGGY version */ struct session *new1 = buggy_newconn(listener); printf("\n[BUGGY] After newconn:\n"); printf(" Listener has_timeout=%d, timeout_arg=%s\n", listener->has_timeout, listener->timeout_arg == listener ? "LISTENER (self)" : "other"); printf(" New session has_timeout=%d (SHOULD be 1)\n", new1->has_timeout); int bug_confirmed = (listener->has_timeout == 1 && new1->has_timeout == 0); printf("\n BUG %s: timer armed on LISTENER, new session has NO timer\n", bug_confirmed ? "CONFIRMED" : "NOT PRESENT"); printf(" Impact: new session leaks (no expiry) + listener gets spurious timeout\n"); /* Reset for fixed version */ listener->has_timeout = 0; listener->timeout_arg = NULL; struct session *new2 = fixed_newconn(listener); printf("\n[FIXED] After newconn:\n"); printf(" Listener has_timeout=%d (correct: timer not disturbed)\n", listener->has_timeout); printf(" New session has_timeout=%d, timeout_arg=%s\n", new2->has_timeout, new2->timeout_arg == new2 ? "NEW (self)" : "other"); int fix_correct = (listener->has_timeout == 0 && new2->has_timeout == 1); printf("\n FIX %s: timer correctly on new session, listener untouched\n", fix_correct ? "CORRECT" : "INCORRECT"); free(listener); free(new1); free(new2); return bug_confirmed ? 0 : 1; } |