Skip to content

goroutine leaks flagged in Interactsh Client/Server package #1245

@knakul853

Description

@knakul853

Interactsh version:

  • v1.2.4 (and main)

Current Behavior:

Client: New() starts a keepalive goroutine when KeepAliveInterval > 0. If Close() isn’t called (e.g., short-lived tests), the goroutine leaks (goleak flags github.com/projectdiscovery/interactsh/pkg/client.New.func1).

Server: HTTPServer.ListenAndServe spawns serving goroutines. Without an exported Close(), tests/tools can’t gracefully stop; goroutines remain in Accept/Serve and goleak flags them.

Example stack (trimmed):

    request_test.go:527: found unexpected goroutines:
        [Goroutine 59 in state select, with github.com/projectdiscovery/interactsh/pkg/client.(*Client).StartPolling.func1 on top of the stack:
        github.com/projectdiscovery/interactsh/pkg/client.(*Client).StartPolling.func1()
        	/Users/runner/go/pkg/mod/github.com/projectdiscovery/[email protected]/pkg/client/client.go:379 +0xe0
        created by github.com/projectdiscovery/interactsh/pkg/client.(*Client).StartPolling in goroutine 55
        	/Users/runner/go/pkg/mod/github.com/projectdiscovery/[email protected]/pkg/client/client.go:373 +0x1f4
         Goroutine 58 in state select, with github.com/projectdiscovery/interactsh/pkg/client.New.func1 on top of the stack:
        github.com/projectdiscovery/interactsh/pkg/client.New.func1()
        	/Users/runner/go/pkg/mod/github.com/projectdiscovery/[email protected]/pkg/client/client.go:205 +0x11c
        created by github.com/projectdiscovery/interactsh/pkg/client.New in goroutine 55
        	/Users/runner/go/pkg/mod/github.com/projectdiscovery/[email protected]/pkg/client/client.go:199 +0xcc0
        ]
--- FAIL: TestExecuteParallelHTTP_GoroutineLeaks (0.79s)

Expected Behavior:

  • New() should not start any background goroutines.
  • Keepalive should start only when StartPolling() is called and be terminated by StopPolling()/Close().
  • No leaked goroutines when constructing a client or after a clean shutdown.

Steps To Reproduce:

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"time"

	"github.com/projectdiscovery/interactsh/pkg/client"
	iserver "github.com/projectdiscovery/interactsh/pkg/server"
	"go.uber.org/goleak"
)

func reproClient() error {
	mux := http.NewServeMux()
	mux.HandleFunc("/register", func(w http.ResponseWriter, _ *http.Request) {
		_ = json.NewEncoder(w).Encode(map[string]string{"message": "registration successful"})
	})
	ts := httptest.NewServer(mux)
	defer ts.Close()

	opts := &client.Options{ServerURL: ts.URL, KeepAliveInterval: 100 * time.Millisecond}
	c, err := client.New(opts)
	if err != nil {
		return err
	}
	_ = c

	time.Sleep(300 * time.Millisecond)
	if err := goleak.Find(); err != nil {
		return err
	}
	return nil
}

func reproServer() error {
	opts := &iserver.Options{
		Domains:                  []string{"example.com"},
		ListenIP:                 "127.0.0.1",
		HttpPort:                 0,
		HttpsPort:                0,
		CorrelationIdLength:      8,
		CorrelationIdNonceLength: 6,
	}
	s, err := iserver.NewHTTPServer(opts)
	if err != nil {
		return err
	}
	httpAlive := make(chan bool, 1)
	httpsAlive := make(chan bool, 1)
	go s.ListenAndServe(nil, httpAlive, httpsAlive)
	select {
	case <-httpAlive:
	case <-time.After(500 * time.Millisecond):
		return fmt.Errorf("server did not start")
	}
	time.Sleep(300 * time.Millisecond)
	if err := goleak.Find(); err != nil {
		return err
	}
	return nil
}

func main() {
	mode := flag.String("mode", "client", "repro mode: client or server")
	flag.Parse()

	var err error
	switch *mode {
	case "client":
		fmt.Println("Reproducing client goroutine leak (pre-fix versions):")
		err = reproClient()
	case "server":
		fmt.Println("Reproducing server goroutine leak when not closed:")
		err = reproServer()
	default:
		fmt.Println("unknown mode; use -mode=client or -mode=server")
		os.Exit(2)
	}
	if err != nil {
		fmt.Println("LEAK DETECTED:", err)
		os.Exit(1)
	}
	fmt.Println("No leaks detected.")
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type: BugInconsistencies or issues which will cause an issue or problem for users or implementors.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions