Skip to content

net/http: racing write to t.ProxyConnectHeader in dialConn when proxy URL includes auth credentials #36431

Closed
@newmanifold

Description

@newmanifold

What version of Go are you using (go version)?

$ go version
go version go1.13.5 linux/amd64

Does this issue reproduce with the latest release?

1.13.5, yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/nuts/.cache/go-build"
GOENV="/home/nuts/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/nuts/golang-test/go-src"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/nuts/golang-test/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/nuts/golang-test/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build701551313=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Ran this code. although it uses elazarl/goproxy package, behavour can be reproduced without this package.

package main

import (
	"net/http"
	"net/url"
	"time"
	"sync"
	"io/ioutil"
	"github.com/elazarl/goproxy"
	"fmt"
)

func ProxyFunc(req *http.Request) (*url.URL, error) {
	return url.Parse(req.RemoteAddr)
}

func prepareTransport() *http.Transport {
	return &http.Transport{
		Proxy: ProxyFunc,
		//DisableKeepAlives: true,
		ProxyConnectHeader: http.Header{
			"User-Agent": { "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0" },
			"Proxy-Connection": {"keep-alive"},
			"Connection": {"keep-alive"},
		},
	}
}

func sendRequest(client *http.Client, proxy *url.URL, u *url.URL) {
	for {
		req := http.Request{
			URL: u,
			RemoteAddr: proxy.String(),
			Method: "GET",
		}

		if res, err := client.Do(&req); err == nil {
			defer res.Body.Close()
			_, _ = ioutil.ReadAll(res.Body)

		}
	}
}

func spawnProxies(port string) {
	proxy := goproxy.NewProxyHttpServer()
	proxy.Verbose = false
	fmt.Println(http.ListenAndServe(":" + port, proxy))
}

func main() {
	numProxies := 4
	client := http.Client{
		Transport: prepareTransport(),
		Timeout: time.Second * time.Duration(5),
	}

	u, _ := url.Parse("https://httpbin.org/")

	proxyPorts := make([]string, 0, numProxies)

	for i := 0; i < numProxies; i++ {
		port := fmt.Sprint("800", i)
		proxyPorts = append(proxyPorts, port)
		go spawnProxies(port)
	}

	var wg sync.WaitGroup

	wg.Add(numProxies)

	for _, port := range proxyPorts {
		proxy, _ := url.Parse("http://test:[email protected]:" + port)
		go func() {
			defer wg.Done()
			sendRequest(&client, proxy, u)
		}()
	}

	wg.Wait()
}

What did you expect to see?

Above code to run without data race warning.

What did you see instead?

==================
WARNING: DATA RACE
Write at 0x00c00008b530 by goroutine 22:
  runtime.mapassign_faststr()
      /home/nuts/golang-test/go/src/runtime/map_faststr.go:202 +0x0
  net/http.(*Transport).dialConn()
      /home/nuts/golang-test/go/src/net/textproto/header.go:22 +0x1d4f
  net/http.(*Transport).dialConnFor()
      /home/nuts/golang-test/go/src/net/http/transport.go:1308 +0x14f

Previous write at 0x00c00008b530 by goroutine 21:
  runtime.mapassign_faststr()
      /home/nuts/golang-test/go/src/runtime/map_faststr.go:202 +0x0
  net/http.(*Transport).dialConn()
      /home/nuts/golang-test/go/src/net/textproto/header.go:22 +0x1d4f
  net/http.(*Transport).dialConnFor()
      /home/nuts/golang-test/go/src/net/http/transport.go:1308 +0x14f

Goroutine 22 (running) created at:
  net/http.(*Transport).queueForDial()
      /home/nuts/golang-test/go/src/net/http/transport.go:1277 +0x68a
  net/http.(*Transport).getConn()
      /home/nuts/golang-test/go/src/net/http/transport.go:1231 +0x785
  net/http.(*Transport).roundTrip()
      /home/nuts/golang-test/go/src/net/http/transport.go:522 +0x83d
  net/http.(*Transport).RoundTrip()
      /home/nuts/golang-test/go/src/net/http/roundtrip.go:17 +0x42
  net/http.send()
      /home/nuts/golang-test/go/src/net/http/client.go:250 +0x6a8
  net/http.(*Client).send()
      /home/nuts/golang-test/go/src/net/http/client.go:174 +0x1ca
  net/http.(*Client).do()
      /home/nuts/golang-test/go/src/net/http/client.go:641 +0x2cc
  main.sendRequest()
      /home/nuts/golang-test/go/src/net/http/client.go:509 +0x11f
  main.main.func1()
      /home/nuts/golang-test/t.go:74 +0x80

Goroutine 21 (running) created at:
  net/http.(*Transport).queueForDial()
      /home/nuts/golang-test/go/src/net/http/transport.go:1277 +0x68a
  net/http.(*Transport).getConn()
      /home/nuts/golang-test/go/src/net/http/transport.go:1231 +0x785
  net/http.(*Transport).roundTrip()
      /home/nuts/golang-test/go/src/net/http/transport.go:522 +0x83d
  net/http.(*Transport).RoundTrip()
      /home/nuts/golang-test/go/src/net/http/roundtrip.go:17 +0x42
  net/http.send()
      /home/nuts/golang-test/go/src/net/http/client.go:250 +0x6a8
  net/http.(*Client).send()
      /home/nuts/golang-test/go/src/net/http/client.go:174 +0x1ca
  net/http.(*Client).do()
      /home/nuts/golang-test/go/src/net/http/client.go:641 +0x2cc
  main.sendRequest()
      /home/nuts/golang-test/go/src/net/http/client.go:509 +0x11f
  main.main.func1()
      /home/nuts/golang-test/t.go:74 +0x80
==================
==================
WARNING: DATA RACE
Write at 0x00c0000b84f0 by goroutine 22:
  net/http.(*Transport).dialConn()
      /home/nuts/golang-test/go/src/net/textproto/header.go:22 +0x1d67
  net/http.(*Transport).dialConnFor()
      /home/nuts/golang-test/go/src/net/http/transport.go:1308 +0x14f

Previous write at 0x00c0000b84f0 by goroutine 21:
  net/http.(*Transport).dialConn()
      /home/nuts/golang-test/go/src/net/textproto/header.go:22 +0x1d67
  net/http.(*Transport).dialConnFor()
      /home/nuts/golang-test/go/src/net/http/transport.go:1308 +0x14f

Goroutine 22 (running) created at:
  net/http.(*Transport).queueForDial()
      /home/nuts/golang-test/go/src/net/http/transport.go:1277 +0x68a
  net/http.(*Transport).getConn()
      /home/nuts/golang-test/go/src/net/http/transport.go:1231 +0x785
  net/http.(*Transport).roundTrip()
      /home/nuts/golang-test/go/src/net/http/transport.go:522 +0x83d
  net/http.(*Transport).RoundTrip()
      /home/nuts/golang-test/go/src/net/http/roundtrip.go:17 +0x42
  net/http.send()
      /home/nuts/golang-test/go/src/net/http/client.go:250 +0x6a8
  net/http.(*Client).send()
      /home/nuts/golang-test/go/src/net/http/client.go:174 +0x1ca
  net/http.(*Client).do()
      /home/nuts/golang-test/go/src/net/http/client.go:641 +0x2cc
  main.sendRequest()
      /home/nuts/golang-test/go/src/net/http/client.go:509 +0x11f
  main.main.func1()
      /home/nuts/golang-test/t.go:74 +0x80

Goroutine 21 (running) created at:
  net/http.(*Transport).queueForDial()
      /home/nuts/golang-test/go/src/net/http/transport.go:1277 +0x68a
  net/http.(*Transport).getConn()
      /home/nuts/golang-test/go/src/net/http/transport.go:1231 +0x785
  net/http.(*Transport).roundTrip()
      /home/nuts/golang-test/go/src/net/http/transport.go:522 +0x83d
  net/http.(*Transport).RoundTrip()
      /home/nuts/golang-test/go/src/net/http/roundtrip.go:17 +0x42
  net/http.send()
      /home/nuts/golang-test/go/src/net/http/client.go:250 +0x6a8
  net/http.(*Client).send()
      /home/nuts/golang-test/go/src/net/http/client.go:174 +0x1ca
  net/http.(*Client).do()
      /home/nuts/golang-test/go/src/net/http/client.go:641 +0x2cc
  main.sendRequest()
      /home/nuts/golang-test/go/src/net/http/client.go:509 +0x11f
  main.main.func1()
      /home/nuts/golang-test/t.go:74 +0x80

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsFixThe path to resolution is known, but the work has not been done.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions