console: fix vfs tail follow-mode use-after-free on session disconnect#1424
console: fix vfs tail follow-mode use-after-free on session disconnect#1424kezarjg wants to merge 2 commits into
Conversation
|
The fix applies at the wrong level / from the wrong direction. The potential necessity of a clean termination for running commands from a session disconnect applies not only to running command tasks, but to generally all interactive commands started within a shell. There is no requirement for these to use an Most commands registering an insert callback do not need any cleanup though, as they don't allocate any resources (e.g. simple So what's needed here is either an extension of the insert callback handler scheme for passing a termination request (e.g. by introducing a specific session termination character code), or a dedicated registry for a command agnostic termination handler similar to the insert callback registry. Side note: don't add fixes to Regards, |
…connect
An interactive command that has taken over the session input (by registering an
insert callback) may keep resources, or a background task, bound to the
launching session's OvmsWriter. If the SSH/Telnet session is closed or dropped
by any means other than the command's normal exit, the writer (and the transport
it points to) was freed while the command still referenced it:
- "vfs tail <file>" in follow mode (OCS_RunLoop) runs its Service() loop on its
own task, writing through the raw OvmsWriter*. The next writer->write() after
the console was freed dereferenced a zeroed vtable -> LoadProhibited crash
(EXCVADDR 0x10, task "tail", VfsTailCommand::Service()).
- "vfs edit" and "enable" leaked their heap-allocated state (editor_state /
PasswordContext) when the session dropped mid-input.
Add a generic termination-handler registry to OvmsWriter, parallel to the
insert-callback registry: an interactive command registers a termination handler
alongside its insert callback, and a console invokes RunTerminationCallback()
during teardown -- before freeing any transport it owns -- so the handler can
release resources / stop background tasks while the writer is still valid. At
most one handler is active at a time, mirroring the single active insert
callback.
- OvmsCommandTask registers a handler that requests its task to stop and joins
it (cooperative stop+join, no vTaskDelete) before the writer is freed; covers
any follow-mode command, not just vfs tail.
- vfs edit / enable register handlers that free their allocations.
- The y/n insert callbacks with no allocated state (module factory reset, OTA
partition prompts, test echo) need no handler.
Called from ~ConsoleSSH/~ConsoleTelnet before they free their transport, from
~OvmsConsole before it frees the shared queue (covers the Bluetooth console),
with a final backstop in ~OvmsWriter.
Replaces the earlier OvmsCommandTask-specific approach per review: the shared
abstraction for an interactive command owning the session is the insert
callback, not OvmsCommandTask.
f1b0214 to
7778bf1
Compare
|
Reworked along your second suggestion — a dedicated termination-handler registry on
Handlers wired up:
Also dropped the Compiles clean; I'll test on hardware (hw3.3-5 Solterra) this weekend — repeating the mid- |
…nd-reap) The termination-registry cleanup joined the follow-mode command task synchronously during ~ConsoleSSH. But ConsoleSSH::write() takes the Mongoose lock that the teardown thread already holds (it runs on the Mongoose task, under that lock for the whole mg_mgr_poll), so dropping a session mid "vfs tail" deadlocked and wedged the SSH server. Instead, when an SSH connection closes with a follow-mode task still bound: mark the console closing (so its in-flight write()/SendCallback bail without touching the now-freed connection), ask the task to stop WITHOUT joining, and defer the console's destruction to a reap pass on the listener poll once the task has left the write path. SendCallback is re-keyed off the ConsoleSSH* (not the mg_connection) so it can never dereference a freed connection. The shared termination-handler join is unchanged for Telnet/Async/Bluetooth, which do not take the Mongoose lock in their send path. On-vehicle validated (hw3.3-5 Solterra): repeated abrupt SSH disconnects during an active "vfs tail" no longer wedge or crash the module; free heap flat over 15 cycles (no leak); idle tail, Ctrl-C abort and one-shot "vfs tail -N" unaffected.
|
I've pushed a revision (it's the current PR head). Short version: while validating the rework on-vehicle I found the previous approach deadlocks on SSH, so I reworked the teardown to fix it. Wanted to walk you through it plainly and get your read on a couple of design calls, since it's in the area you flagged. Your review points are all addressed. The cleanup is now a generic termination-handler registry, not The deadlock (new; surfaced during testing) The fix; "signal and reap" instead of "stop and wait"
Validated on-vehicle Two calls I'd value your view on:
(Longer term, routing SSH output through a bounded queue the way |
vfs tail <file>is a documented VFS command (user guide; the CAN-logging guide also shows usingtailto follow a growing log). With no line count it enters follow mode by default — "[tail: in follow mode, press Ctrl-C to abort]" — running its Service() loop on its own task, writing to the launching session's OvmsWriter via a raw pointer. The only stop path is the Ctrl-C Terminator callback.If the SSH/Telnet session closes or drops by any means other than Ctrl-C, the console (OvmsWriter) is destroyed while the task keeps running; the next writer->write() dereferences the freed object → LoadProhibited.
Decoded backtrace (two separate occurrences):
EXCVADDR 0x10 / A8=0 = virtual call through a freed (zeroed) vtable.
Fix: OvmsWriter tracks its single active follow-mode task; TerminateCommandTask() sets OCS_StopRequested and waits for the task to exit and release the writer before the transport is freed. Called from ~ConsoleSSH/~ConsoleTelnet, with a backstop in ~OvmsWriter(). Cooperative stop+join (no vTaskDelete) so the task is never force-killed mid-write. Generic to any OvmsCommandTask.
Teardown briefly blocks the close handler until the task exits (≤ one ~250 ms poll); a concurrent Ctrl-C during teardown isn't possible on the disconnect path.
Tested on hw3.3-5 (Solterra): built, flashed, repeatedly dropped an SSH session mid-vfs tail → no crash; normal Ctrl-C exit and one-shot vfs tail -N unaffected.