#18527·esp-idf

spi_bus_lock: TOCTOU race on lock->acquiring_dev in bg_exit_core causes NULL deref crash (IDFGH-17600)

Author: tinkerbugCreated Apr 28, 2026Updated Sep 17, 2026
LabelsStatus: Reviewing

Environment

  • ESP-IDF: v5.3.2 (race also present in v5.5.3 and master, verified)
  • Target: ESP32-S3
  • Crash mode: Guru Meditation, LoadProhibited
  • Reproducibility: probabilistic, ~one event per several minutes of heavy SPI activity

Bug

In components/esp_hw_support/spi_bus_lock.c::bg_exit_core(), the volatile pointer lock->acquiring_dev is read multiple times without caching:

c
if (lock->acquiring_dev) {                              // L547
    if (status & DEV_BG_MASK(lock->acquiring_dev)) {    // L548 — re-reads
        ...
    } else {
        resume_dev_in_isr(lock->acquiring_dev, do_yield); // L554 — re-reads again
        ret = true;
    }
}

acquiring_dev is declared volatile, so each read goes to memory — but on multi-core ESP32-S3 the pointer can be cleared between L547 and L548 (or L554) by another concurrent SPI completion ISR. When that happens, DEV_BG_MASK(NULL) dereferences NULL->mask (offset 8) and resume_dev_in_isr(NULL, ...) dereferences NULL->semphr (offset 0).

Crash signatures observed

Guru Meditation Error: Core 0 panic'ed (LoadProhibited).
PC: bg_exit_core (spi_bus_lock.c:548)   EXCVADDR: 0x00000008
   spi_bus_lock_bg_exit (spi_bus_lock.c:778)
   spi_intr (spi_master.c:1015)
   _xt_lowint1
   esp_pm_impl_waiti (pm_impl.c:1047)
   esp_vApplicationIdleHook
   prvIdleTask

Identical signature observed at L554 (EXCVADDR=0x00000000) on a separate run.

Suggested fix

Cache the volatile pointer once at the top of the conditional:

c
spi_bus_lock_dev_t *dev = (spi_bus_lock_dev_t *)lock->acquiring_dev;
if (dev) {
    if (status & DEV_BG_MASK(dev)) {
        BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->acq_dev_bg_active);
        ret = false;
    } else {
        resume_dev_in_isr(dev, do_yield);
        ret = true;
    }
}

Reproduction context

LoRa SX126x over SPI2 (RadioLib + EspHal, ~2 Hz TX/RX with frequent setFrequency calls), NimBLE GATT server, SDMMC, PM enabled with DFS. Crash always inside SPI ISR completion path while CPU is in waiti. No minimal reproducer — failure rate ~one per several minutes of mixed SPI/BLE activity. Happy to gather more diagnostics.