fix(tcp): close stop() teardown UAF window — force-delete task on deadline (greptile)

stop()'s join had a fixed deadline (CONNECT_TIMEOUT_MS + 2s); a slow DNS could
keep the task inside connect() past it, so stop() would free the object while the
task still referenced `this`. Extend the deadline well beyond any connect()+DNS,
and if it still expires, vTaskDelete(_task_handle) the task so it can't touch
`this` after return. (The task's own self-delete path sets _task_done first, so
this branch only runs when it has not self-deleted — no double delete.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
This commit is contained in:
torlando-agent[bot]
2026-06-19 23:31:48 -04:00
co-authored by Claude Opus 4.8
parent afd374ff3f
commit 9922130ead
+12 -1
View File
@@ -319,10 +319,21 @@ void TCPClientInterface::task_loop() {
// stop() — returning early would risk a use-after-free on `this`.
_task_running = false;
if (_task_handle != nullptr) {
uint32_t deadline = millis() + CONNECT_TIMEOUT_MS + 2000;
// Wait for the task to leave task_loop() and set _task_done — after that
// it only calls vTaskDelete(nullptr) and never touches `this` again, so
// it's safe to free the object. The deadline is far longer than any
// connect()+DNS (incl. lwIP DNS retries) can take.
uint32_t deadline = millis() + 30000;
while (!_task_done && (int32_t)(millis() - deadline) < 0) {
vTaskDelay(pdMS_TO_TICKS(20));
}
if (!_task_done) {
// Pathological: the task is still inside a hung connect() past the
// deadline. Force-delete it so it cannot reference `this` after we
// return. Safe against its own self-delete: that path sets _task_done
// first, so reaching here means it has not self-deleted.
vTaskDelete(_task_handle);
}
_task_handle = nullptr;
}
_conn_state.store(DISCONNECTED);