Skip to content

Data races in DNSCaching: shared cache slice mutation and unsafe rand.Rand #754

@queelius

Description

@queelius

Bug Description

The DNSCaching option has two data races that are triggered under concurrent load (i.e., normal vegeta operation):

1. Shared DNS cache slice mutated in-place

resolver.LookupHost() returns a direct reference to the cached []string slice. The DialContext closure then mutates this shared slice via:

  • rng.Shuffle(len(ips), ...) -- reorders elements in-place
  • firstOfEachIPFamily(ips) -- uses the ips[:0] append trick, overwriting the backing array

When multiple goroutines resolve the same host concurrently, they all get the same slice and race on its elements. This can corrupt the DNS cache entries, leading to connections to wrong IPs.

2. Unsafe rand.Rand shared across goroutines

A private rand.New(rand.NewSource(...)) is created once and used inside the DialContext closure. rand.Rand is explicitly documented as not safe for concurrent use. Under concurrent dial attempts, this is a data race.

Reproduction

func TestDNSCaching_ConcurrentDialRace(t *testing.T) {
    srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
    defer srv.Close()
    u, _ := url.Parse(srv.URL)
    target := "http://localhost:" + u.Port()

    atk := NewAttacker(DNSCaching(0))
    tr := NewStaticTargeter(Target{Method: "GET", URL: target})
    a := &attack{name: "dns-race-test", began: time.Now()}

    var wg sync.WaitGroup
    for i := 0; i < 20; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atk.hit(tr, a)
        }()
    }
    wg.Wait()
}

Running with go test -race triggers:

WARNING: DATA RACE
Read at ... firstOfEachIPFamily() attack.go:419
Previous write at ... firstOfEachIPFamily() attack.go:426

Fix

  1. Copy the ips slice before mutating it
  2. Use the global rand.Shuffle (concurrency-safe) instead of a private rand.Rand

PR forthcoming.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions