From 9853e0a54fd92e36d484ffaa1a48614674a83783 Mon Sep 17 00:00:00 2001 From: Sean Smith Date: Mon, 22 Jun 2026 13:53:24 -0500 Subject: [PATCH] Fix timeout and panic-recovery bugs in connection processing Three related correctness fixes in processing.go: - TCP dialer: re-rooting the connection context at the main context dropped the per-target SessionTimeout (and leaked the previous cancel func), so the target timeout was no longer enforced after the dial. Preserve SessionTimeout and cancel the prior context. - TLS wrapper: the handshake timeout block was gated on ConnectTimeout but applied TLSHandshakeTimeout. This disabled the handshake timeout when ConnectTimeout was 0, and could apply a zero (already-expired) deadline. Gate on TLSHandshakeTimeout instead. - grabTarget: recovering from a scanner panic in a deferred function unwound grabTarget and returned a nil *Grab, discarding all results. Recover inside a per-scanner closure so the panic is recorded and BuildGrabFromInputResponse is still reached. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- processing.go | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/processing.go b/processing.go index 2340636f..6aeab666 100644 --- a/processing.go +++ b/processing.go @@ -106,8 +106,17 @@ func GetDefaultTCPDialer(flags *BaseFlags) func(ctx context.Context, t *ScanTarg return nil, err } if castConn, ok := conn.(*TimeoutConnection); ok { - // Reset the context on the connection to the main one, not the handshake one - castConn.ctx = ctx + // Re-root the connection's context at the main context rather than the + // handshake context, but preserve the per-target SessionTimeout so the + // target timeout is still enforced after the dial completes. + if castConn.Cancel != nil { + castConn.Cancel() + } + if castConn.SessionTimeout > 0 { + castConn.ctx, castConn.Cancel = context.WithTimeout(ctx, castConn.SessionTimeout) + } else { + castConn.ctx, castConn.Cancel = context.WithCancel(ctx) + } if err = castConn.SaturateTimeoutsToReadAndWriteTimeouts(); err != nil { return nil, err @@ -154,7 +163,7 @@ func GetDefaultTLSWrapper(baseFlags *BaseFlags, tlsFlags *TLSFlags) func(ctx con flags: tlsFlags, } handshakeCtx := ctx - if baseFlags.ConnectTimeout > 0 { + if tlsFlags.TLSHandshakeTimeout > 0 { var cancel context.CancelFunc handshakeCtx, cancel = context.WithTimeout(ctx, tlsFlags.TLSHandshakeTimeout) defer cancel() @@ -257,18 +266,25 @@ func grabTarget(ctx context.Context, input ScanTarget, m *Monitor) *Grab { if input.Tag != trigger { continue } - defer func(name string) { - if e := recover(); e != nil { - log.Errorf("Panic on scanner %s when scanning target %s: %#v\n%s", name, input.String(), e, debug.Stack()) - errMsg := fmt.Sprintf("panic: %v", e) - moduleResult[name] = ScanResponse{ - Status: SCAN_UNKNOWN_ERROR, - Protocol: name, - Error: &errMsg, + // Run each scanner in a closure with its own panic recovery so a panic in + // one scanner is recorded as an error result and the loop still continues + // to BuildGrabFromInputResponse, rather than unwinding grabTarget and + // discarding all results. + name, res := func() (name string, res ScanResponse) { + defer func() { + if e := recover(); e != nil { + log.Errorf("Panic on scanner %s when scanning target %s: %#v\n%s", scannerName, input.String(), e, debug.Stack()) + errMsg := fmt.Sprintf("panic: %v", e) + name = scannerName + res = ScanResponse{ + Status: SCAN_UNKNOWN_ERROR, + Protocol: scannerName, + Error: &errMsg, + } } - } - }(scannerName) - name, res := RunScanner(ctx, *scanner, m, input) + }() + return RunScanner(ctx, *scanner, m, input) + }() moduleResult[name] = res if res.Error != nil && !config.Multiple.ContinueOnError { break