โฌข DragonFlyBSD Kernel Audit
DF-0453 / harness.c
โ† back to finding โ†“ download raw
/*
 * 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;
}