Skip to content

Commit f6f3284

Browse files
authored
fix(lockdown): re-lock serial console admin auth on USB link drop (#11028)
On lockdown builds (MESHTASTIC_PHONEAPI_ACCESS_CONTROL, nRF52) the SerialConsole is a process-lifetime singleton, so the per-connection admin-auth slot keyed by its inherited PhoneAPI* is reused for every USB/serial client for the whole boot. An operator's admin unlock stayed latched across serial client swaps: an attacker plugging into the USB/serial port before the prior session's 15-minute inactivity timeout inherited admin authorization -- the serial analog of the BLE stale-session reuse bug closed by resetting state in onConnect()/onDisconnect(). Sample the USB-CDC host link (DTR/mount) state each runOnce(); on the link-drop edge call close(), which frees the auth slot and resets PhoneAPI state so whoever connects next re-locks via handleStartConfig()'s !isConnected() branch on their first want_config -- the same physical-link boundary BLE enforces in onConnect(). On the nRF52 TinyUSB (Adafruit) core, (bool)Port == tud_cdc_n_connected(), which goes false on cable unplug or host port-close. Console transports without a real DTR line fall back to the existing inactivity timeout, no worse than before. Entirely nRF52-lockdown-gated; non-lockdown builds are byte-identical.
1 parent d5f78a3 commit f6f3284

1 file changed

Lines changed: 34 additions & 0 deletions

File tree

src/SerialConsole.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@
3030

3131
SerialConsole *console;
3232

33+
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
34+
// Last-seen USB-CDC host link (DTR/mount) state, sampled each runOnce() so a
35+
// physical unplug/replug re-locks the per-connection admin auth (see runOnce()).
36+
// Kept at file scope rather than as a member both because there is exactly one
37+
// console singleton and because adding per-instance members to the PhoneAPI
38+
// hierarchy has historically perturbed nRF52 USB-CDC enumeration (see PhoneAPI.h).
39+
// Only compiled on lockdown (nRF52) builds.
40+
static bool s_serialLinkUp = false;
41+
#endif
42+
3343
/// Create the shared serial console once and register receive wakeups.
3444
void consoleInit()
3545
{
@@ -88,6 +98,30 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
8898
/// Service one serial API iteration and select the next polling interval.
8999
int32_t SerialConsole::runOnce()
90100
{
101+
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
102+
// Lockdown (nRF52) builds only. The SerialConsole is a process-lifetime
103+
// singleton, so its inherited PhoneAPI object - and therefore its entry in
104+
// the per-connection admin-auth slot table (keyed by PhoneAPI*) - is reused
105+
// for every USB/serial client for the whole boot. Nothing re-locks that slot
106+
// when the operator unplugs and a different client plugs in before the
107+
// 15-minute inactivity timeout fires, so a fresh client would inherit the
108+
// prior operator's admin authorization. Re-lock when the physical USB-CDC link
109+
// drops - the serial analog of the BLE onDisconnect() -> close() session reset.
110+
//
111+
// On the nRF52 TinyUSB (Adafruit) core, (bool)Port == tud_cdc_n_connected():
112+
// it goes false on cable unplug or host port-close (DTR de-assert). close()
113+
// frees the auth slot and resets PhoneAPI state, so whoever connects next
114+
// re-locks via handleStartConfig()'s !isConnected() branch on their first
115+
// want_config - the same physical-link boundary BLE enforces in onConnect().
116+
// Console transports without a real DTR line (e.g. a UART USER_DEBUG_PORT) hold
117+
// this constant, so no edge fires and we fall back to the existing inactivity
118+
// timeout - no worse than the pre-fix behavior.
119+
const bool linkUp = static_cast<bool>(Port);
120+
if (s_serialLinkUp && !linkUp)
121+
close();
122+
s_serialLinkUp = linkUp;
123+
#endif
124+
91125
#ifdef HELTEC_MESH_SOLAR
92126
// After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
93127
if (moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port &&

0 commit comments

Comments
 (0)