From bef527d7ef7c7b01184633efdd1cc167d9b55ce6 Mon Sep 17 00:00:00 2001 From: zhaog100 Date: Wed, 18 Mar 2026 10:33:44 +0800 Subject: [PATCH] [BOUNTY #819] Fix indefinite hanging on certain hosts Add per-connection timeout to prevent tlsx from hanging indefinitely during long-running scans. - Add connectWithTimeout() wrapper in runner that enforces timeout via context, preventing goroutines from blocking forever on stalled connections (especially with cipher/version enumeration) - Fix openssl client dial using context.TODO() (no timeout) to use context.Background() with explicit timeout derived from options.Timeout Closes #819 --- internal/runner/runner.go | 40 +++++++++++++++++++++++++++++++++++-- pkg/tlsx/openssl/openssl.go | 4 +++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 91e80136..d72d0a29 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -396,9 +396,26 @@ func (r *Runner) processInputElementWorker(inputs chan taskInput, wg *sync.WaitG gologger.Info().Msgf("Processing input %s:%s", task.host, task.port) } - response, err := tlsxService.ConnectWithOptions(task.host, task.ip, task.port, clients.ConnectOptions{SNI: task.sni}) + var response *clients.Response + var err error + // Use a per-connection timeout to prevent indefinite hangs + connTimeout := time.Duration(r.options.Timeout) * time.Second + if connTimeout <= 0 { + connTimeout = 10 * time.Second + } + // For enum modes that make multiple connections, allow more time + if r.options.TlsVersionsEnum || r.options.TlsCiphersEnum { + connTimeout *= 3 + } + ctx, cancel := context.WithTimeout(context.Background(), connTimeout) + response, err = r.connectWithTimeout(ctx, tlsxService, task.host, task.ip, task.port, task.sni) + cancel() if err != nil { - gologger.Warning().Msgf("Could not connect input %s: %s", task.Address(), err) + if ctx.Err() == context.DeadlineExceeded { + gologger.Warning().Msgf("Could not connect input %s: timeout after %v", task.Address(), connTimeout) + } else { + gologger.Warning().Msgf("Could not connect input %s: %s", task.Address(), err) + } } if response == nil { @@ -418,6 +435,25 @@ func (r *Runner) processInputElementWorker(inputs chan taskInput, wg *sync.WaitG } } +// connectWithTimeout wraps ConnectWithOptions with a context timeout to prevent indefinite hangs +func (r *Runner) connectWithTimeout(ctx context.Context, tlsxService *tlsx.Service, host, ip, port, sni string) (*clients.Response, error) { + type result struct { + response *clients.Response + err error + } + ch := make(chan result, 1) + go func() { + resp, err := tlsxService.ConnectWithOptions(host, ip, port, clients.ConnectOptions{SNI: sni}) + ch <- result{resp, err} + }() + select { + case res := <-ch: + return res.response, res.err + case <-ctx.Done(): + return nil, ctx.Err() + } +} + // normalizeAndQueueInputs normalizes the inputs and queues them for execution func (r *Runner) normalizeAndQueueInputs(inputs chan taskInput) error { // Process Normal Inputs diff --git a/pkg/tlsx/openssl/openssl.go b/pkg/tlsx/openssl/openssl.go index 81783e0d..9dd9cc3f 100644 --- a/pkg/tlsx/openssl/openssl.go +++ b/pkg/tlsx/openssl/openssl.go @@ -46,6 +46,8 @@ func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.C if c.options.Timeout < 3 { c.options.Timeout = 3 } + dialCtx, dialCancel := context.WithTimeout(context.Background(), time.Duration(c.options.Timeout)*time.Second) + defer dialCancel() // validate dialer before using if c.dialer == nil { var err error @@ -56,7 +58,7 @@ func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.C } // There is no guarantee that dialed ip is same as ip used by openssl // this is only used to avoid inconsistencies - rawConn, err := c.dialer.Dial(context.TODO(), "tcp", opensslOpts.Address) + rawConn, err := c.dialer.Dial(dialCtx, "tcp", opensslOpts.Address) if err != nil || rawConn == nil { return nil, errorutils.NewWithErr(err).WithTag(PkgTag, "fastdialer").Msgf("could not dial address:%v", opensslOpts.Address) //nolint }