Nil DialFunc returned when proxy setup fails (dual-stack)
high swesweep-fasthttp · fasthttpproxy/http.go · FasthttpHTTPDialerDualStackTimeout · run #4
Description
The dual-stack variant has the same unchecked error and nil-return issue. A crafted proxy string can produce a nil fasthttp.DialFunc, leading to panic in callers or silent proxy bypass in fasthttp clients.
Triage
- Verdict
- INVALID confidence 0.5
- Model
- qwen/qwen3.8-27b
- Rounds
- 6
- Cost
- $0.0179 · 56.8 s
[ARBITER] The reported vulnerability is a programming error (missing error check) rather than a security vulnerability under the provided threat model. The 'attacker' in the library model is the developer or the application code calling the public API. A 'malformed proxy string' is treated as developer configuration error/misuse, not as an adversarial payload from an untrusted external source (like a network packet) that the library processes. Consequently, the resulting panic is a failure due to incorrect library usage, not a security exploit vector reachable by an external attacker via untrusted data input.
All rounds
[
{
"finding_title": "Nil DialFunc returned when proxy setup fails (dual-stack)",
"verdict": "VALID",
"reasoning": "The reported vulnerability claims that `FasthttpHTTPDialerDualStackTimeout` (and other variants) discards the error from `d.GetDialFunc(false)` and returns a nil `fasthttp.DialFunc` if the proxy configuration is malformed. \n\n1. **Bug Pattern**: The code snippet shows `dialFunc, _ := d.GetDialFunc(false)`. In Go, functions return error types to signal failure. If `GetDialFunc` fails (e.g., due to an unparseable proxy URL in the `proxy` string), it typically returns a nil function and a non-nil error. By discarding the error with `_`, the code blindly returns the nil function. \n2. **Attacker Reachability**: The `proxy` string is passed to the public exported function `FasthttpHTTPDialerDualStack(proxy)`. The threat model defines the attacker as code calling the public API with adversarial payloads. An attacker (or a misconfigured user) can pass a malformed proxy string (e.g., invalid syntax that causes `httpproxy` parsing to fail). \n3. **Consequence**: The caller receives a nil `fasthttp.DialFunc`. When `fasthttp` attempts to use this dial function to make a request, it will attempt to call the nil function, resulting in a runtime panic (`runtime error: invalid memory address or nil pointer dereference`). \n4. **Security Impact**: In a server-side application using `fasthttp`, an unhandled panic in a worker goroutine can crash the entire process if not recovered from by the runtime handler. Even if the process survives, it represents a denial of service or at least a failure to enforce the intended proxy connection (silent bypass or connection failure). The threat model lists \"correct behaviour on malformed input\" and \"memory safety... crash\" as assets. A crash induced by a crafted input string is a valid security concern. \n5. **Defense Verification**: There is no error check in `FasthttpHTTPDialerDualStackTimeout`. The error is explicitly discarded. \n\nTherefore, the bug is real, reachable via public API, and leads to a panic (crash) on malformed input.\n\nCRUX: Whether discarding the error from GetDialFunc allows a nil DialFunc to be returned, leading to a panic when invoked with a malformed proxy string.\nGREP: func.*GetDialFunc",
"elapsed": 8.9,
"tokens": 3008,
"tokens_in": 2492,
"tokens_out": 516,
"cost_usd": 0.0025946399999999996,
"file": "fasthttpproxy/http.go",
"round": 1,
"grep_used": true,
"grep_results": "GREP `func.*GetDialFunc`:\n```\nfasthttpproxy/dialer.go:74:func (d *Dialer) GetDialFunc(useEnv bool) (fasthttp.DialFunc, error) {\nfasthttpproxy/dialer_test.go:16:func TestDialer_GetDialFunc(t *testing.T) {\n```"
},
{
"finding_title": "Nil DialFunc returned when proxy setup fails (dual-stack)",
"verdict": "UNCERTAIN",
"reasoning": "The reported vulnerability claims that a malformed `proxy` string can cause `GetDialFunc` to return a nil `fasthttp.DialFunc`, leading to a panic in callers. I must verify if `GetDialFunc` actually fails for the types of malformed inputs an attacker would likely provide and whether the error is truly discarded. The code in `fasthttpproxy/http.go` indeed discards the error with `dialFunc, _ := d.GetDialFunc(false)`. However, the threat model defines the attacker as controlling the *data* passed through parsers and handlers (adversarial payloads obtained from untrusted sources). The `proxy` string in `FasthttpHTTPDialerDualStack(proxy)` is typically a configuration parameter set by the host application's developer or administrator, not untrusted data processed at runtime. While the threat model mentions ",
"elapsed": 3.8,
"tokens": 3403,
"tokens_in": 3218,
"tokens_out": 185,
"cost_usd": 0.0019065599999999999,
"file": "fasthttpproxy/http.go",
"round": 2
},
{
"finding_title": "Nil DialFunc returned when proxy setup fails (dual-stack)",
"verdict": "VALID",
"reasoning": "The threat model for a reusable library defines the attacker as code calling the public API with adversarial payloads (including configuration data like proxy strings). The function `FasthttpHTTPDialerDualStackTimeout` takes a `proxy` string and passes it to `GetDialFunc`. The code explicitly discards the error from `GetDialFunc` (`dialFunc, _ := ...`). If `GetDialFunc` returns an error (e.g., due to invalid proxy configuration or internal dialer setup failure), it likely returns a nil `DialFunc`. Returning a nil function to the caller violates the library's contract and leads to a panic (`nil pointer dereference`) when the host application attempts to use the client. This constitutes a DoS (crash) reachable via the public API. I verified the existence of `GetDialFunc` returning `(fasthttp.DialFunc, error)` in `dialer.go`. The lack of error handling is a real bug in the public entry point that allows a crash via malformed input.\n\nCRUX: Does GetDialFunc return a nil DialFunc on error, and is that error checked in the public wrapper functions?\nGREP: func.*GetDialFunc",
"elapsed": 5.3,
"tokens": 3655,
"tokens_in": 3389,
"tokens_out": 266,
"cost_usd": 0.00222138,
"file": "fasthttpproxy/http.go",
"round": 3,
"grep_used": true,
"grep_results": "GREP `func.*GetDialFunc`:\n```\nfasthttpproxy/dialer.go:74:func (d *Dialer) GetDialFunc(useEnv bool) (fasthttp.DialFunc, error) {\nfasthttpproxy/dialer_test.go:16:func TestDialer_GetDialFunc(t *testing.T) {\n```"
},
{
"finding_title": "Nil DialFunc returned when proxy setup fails (dual-stack)",
"verdict": "INVALID",
"reasoning": "The vulnerability report claims that `FasthttpHTTPDialerDualStack` and related functions can return a nil `fasthttp.DialFunc` due to discarded errors from `GetDialFunc`, leading to a panic (DoS) in callers. To verify if this is a security vulnerability, we must determine if `GetDialFunc` can actually return an error for the inputs it receives. In `fasthttpproxy/http.go`, the `proxy` string is passed directly to `httpproxy.Config`. The `GetDialFunc` method in `dialer.go` is called with `useEnv = false`. Looking at the implementation of `GetDialFunc` (and standard `httpproxy` behavior), if the configuration is valid (which it is, as it's just assigning strings) and `useEnv` is false, it typically constructs a dialer without failing. Even if a malformed proxy string causes `httpproxy` parsing to fail during the construction of the proxy dialer, the Go standard library's `httpproxy` usually defaults to direct dialing or handles invalid proxy URLs gracefully by ignoring them, rather than returning a fatal error that prevents dialer creation. More importantly, even if `GetDialFunc` were to return an error, the primary question is whether a 'malformed proxy string' constitutes a malicious input leading to meaningful harm. In a library context, passing a malformed configuration string is often a programmer error, not an attacker-controlled attack vector that bypasses security controls. However, the threat model states 'crash... reachable through the public API with a crafted payload is VALID'. If `GetDialFunc` returns `nil, error` on malformed input, and the library discards the error, the caller gets `nil`. Calling `nil` panics. Does `GetDialFunc` return an error for a 'crafted payload' of a proxy string? Let's check `dialer.go`. The `Dialer` struct contains `Config: httpproxy.Config{...}`. `httpproxy.Config` parsing errors usually happen when the string is passed to `ProxyFromEnvironment` or similar. Here, we explicitly set `HTTPProxy` and `HTTPSProxy`. If `httpproxy` fails to parse these, does `GetDialFunc` fail? In `golang.org/x/net/http/httpproxy`, `Config` fields are parsed. If parsing fails, it may default to nil. If `GetDialFunc` returns `nil, nil` (no error), then no panic. If it returns `nil, err`, then panic. Most implementations of proxy dialers do not fail hard on bad proxy URLs; they fallback to direct connections or return an error at dial time, not setup time. Furthermore, a 'panic' in a library function due to bad configuration is often considered a robustness bug, but the threat model includes 'crash' as a valid impact. However, is the input truly 'untrusted'? A proxy string is configuration. Attackers rarely control configuration strings unless they have config injection, which is a different threat. Compared to parsers that process network data, config injection is less direct. But the rule says 'attacker... data it passes through...'. If an attacker can control the proxy string (e.g. via environment variable or config file), can they cause a crash? If `GetDialFunc` handles bad proxies by returning a valid (direct) dialer or a dialer that errors on call, then no crash. If it returns nil, then crash. I need to verify if `GetDialFunc` returns an error for invalid proxy strings. Grep for `GetDialFunc` implementation or errors. In `dialer.go`, `GetDialFunc` likely calls `httpproxy.FromEnvironment` or uses the config. If `useEnv` is false, it uses the config. `httpproxy.Config` has `HTTPProxy` and `HTTPSProxy`. If these are invalid, `httpproxy` might just ignore them. Therefore, `GetDialFunc` likely succeeds. Even if it failed, the 'crafted payload' assumption for a config string is weak. However, the most critical point is whether `GetDialFunc` *can* return an error. If it can't return an error for this specific setup (static config, no env), then the error is always nil, and `dialFunc` is always non-nil (assuming `GetDialFunc` constructs a dialer successfully). If `GetDialFunc` only errors on internal failures or env issues, and we bypass env, it might never error. Thus, the nil return is unlikely. Let's assume it doesn't error. Then no vulnerability.\n\nCRUX: Whether GetDialFunc returns a non-nil error (and consequently a nil DialFunc) when passed a static, potentially malformed proxy configuration with useEnv=false.\nGREP: func (d \\*Dialer) GetDialFunc",
"elapsed": 20.1,
"tokens": 4800,
"tokens_in": 3737,
"tokens_out": 1063,
"cost_usd": 0.00475854,
"file": "fasthttpproxy/http.go",
"round": 4,
"grep_used": true,
"grep_results": "GREP `func (d *Dialer) GetDialFunc (simplified to: GetDialFunc)`:\n```\nfasthttpproxy/socks5.go:18:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/socks5.go:32:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/dialer.go:72:// GetDialFunc method returns a fasthttp-style dial function. The useEnv parameter\nfasthttpproxy/dialer.go:74:func (d *Dialer) GetDialFunc(useEnv bool) (fasthttp.DialFunc, error) {\nfasthttpproxy/dialer_test.go:16:func TestDialer_GetDialFunc(t *testing.T) {\nfasthttpproxy/dialer_test.go:218:\t\t\tdialFunc, err := d.GetDialFunc(tt.args.useEnv)\nfasthttpproxy/dialer_test.go:220:\t\t\t\tt.Fatalf(\"GetDialFunc() error = %v, wantErr %v\", err, tt.wantErrMessage)\nfasthttpproxy/dialer_test.go:234:\t\t\t\tt.Errorf(\"GetDialFunc() counts = %v, want %v\", getCounts(counts), tt.wantCounts)\nfasthttpproxy/http.go:33:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/http.go:63:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/proxy_env.go:37:\tdialFunc, _ := d.GetDialFunc(true)\n```\n\nGREP `GetDialFunc`:\n```\nfasthttpproxy/socks5.go:18:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/socks5.go:32:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/dialer.go:72:// GetDialFunc method returns a fasthttp-style dial function. The useEnv parameter\nfasthttpproxy/dialer.go:74:func (d *Dialer) GetDialFunc(useEnv bool) (fasthttp.DialFunc, error) {\nfasthttpproxy/dialer_test.go:16:func TestDialer_GetDialFunc(t *testing.T) {\nfasthttpproxy/dialer_test.go:218:\t\t\tdialFunc, err := d.GetDialFunc(tt.args.useEnv)\nfasthttpproxy/dialer_test.go:220:\t\t\t\tt.Fatalf(\"GetDialFunc() error = %v, wantErr %v\", err, tt.wantErrMessage)\nfasthttpproxy/dialer_test.go:234:\t\t\t\tt.Errorf(\"GetDialFunc() counts = %v, want %v\", getCounts(counts), tt.wantCounts)\nfasthttpproxy/http.go:33:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/http.go:63:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/proxy_env.go:37:\tdialFunc, _ := d.GetDialFunc(true)\n```"
},
{
"finding_title": "Nil DialFunc returned when proxy setup fails (dual-stack)",
"verdict": "VALID",
"reasoning": "The reported vulnerability claims that a malformed `proxy` string causes `GetDialFunc` to return a nil `DialFunc` and an error, which is then discarded, leading to a panic when the function is called. \n\n1. **Bug Pattern Existence**: The code `dialFunc, _ := d.GetDialFunc(false)` in `FasthttpHTTPDialerDualStackTimeout` indeed discards the error. If `GetDialFunc` returns `(nil, err)`, the caller receives a nil `fasthttp.DialFunc`. Calling a nil function in Go results in a panic (nil pointer dereference). \n\n2. **Attacker Reachability**: The `proxy` parameter is a public API input. Under the library threat model, the attacker controls data passed to public APIs. If a host application passes user-controlled data (e.g., from a config file or environment variable) to this function, the attacker controls the `proxy` string. \n\n3. **Trigger Condition**: Does `GetDialFunc` return an error for a malformed `proxy` string? \n - `GetDialFunc` in `dialer.go` likely parses the proxy configuration. In `golang.org/x/net/http/httpproxy`, invalid proxy URLs can cause parsing errors. If the library propagates these errors, `GetDialFunc` will return an error. \n - Even if `GetDialFunc` only errors on internal failures, the lack of error handling in the public wrapper is a robustness flaw. However, for it to be a *security* vulnerability, the attacker must be able to trigger the error path. \n - Reviewer 4 suggested that `GetDialFunc` might not error on static configs. However, `httpproxy` parsing of `HTTPProxy`/`HTTPSProxy` fields is strict. An invalid URL (e.g., missing scheme, bad port) will likely cause `httpproxy` to fail, which `GetDialFunc` should propagate. \n - Crucially, even if the error path is hard to trigger, the *design* of the function is to return a `DialFunc`. Returning nil is a violation of the implicit contract (unless documented, which it isn't). \n - More importantly, is the panic reachable? Yes. If an attacker can control the proxy string, and that string causes `GetDialFunc` to fail, the host application panics. This is a Denial of Service (crash). \n - The threat model explicitly states: \"A crash, memory corruption or unbounded resource use reachable through the public API with a crafted payload is VALID.\" \n - Therefore, if a crafted `proxy` string causes a panic, it is a valid vulnerability. Given that `GetDialFunc` returns an error type, it is plausible that invalid inputs lead to errors, which lead to nil returns, which lead to panics. The absence of error checking in the public API is a critical flaw that exposes the host process to crashes via malformed configuration data.\n\n4. **Defense**: There is no defense. The error is explicitly discarded.\n\n5. **Conclusion**: The bug is real (error discarded, nil return possible). The input is untrusted (public API parameter). The impact is a crash (panic). This meets the criteria for VALID.\n\nCRUX: Whether a malformed proxy string causes GetDialFunc to return an error, leading to a nil DialFunc that panics when invoked.\nGREP: func (d *Dialer) GetDialFunc",
"elapsed": 14.2,
"tokens": 6307,
"tokens_in": 5533,
"tokens_out": 774,
"cost_usd": 0.00464586,
"file": "fasthttpproxy/http.go",
"round": 5,
"grep_used": true,
"grep_results": "GREP `func (d *Dialer) GetDialFunc (simplified to: GetDialFunc)`:\n```\nfasthttpproxy/socks5.go:18:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/socks5.go:32:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/dialer.go:72:// GetDialFunc method returns a fasthttp-style dial function. The useEnv parameter\nfasthttpproxy/dialer.go:74:func (d *Dialer) GetDialFunc(useEnv bool) (fasthttp.DialFunc, error) {\nfasthttpproxy/dialer_test.go:16:func TestDialer_GetDialFunc(t *testing.T) {\nfasthttpproxy/dialer_test.go:218:\t\t\tdialFunc, err := d.GetDialFunc(tt.args.useEnv)\nfasthttpproxy/dialer_test.go:220:\t\t\t\tt.Fatalf(\"GetDialFunc() error = %v, wantErr %v\", err, tt.wantErrMessage)\nfasthttpproxy/dialer_test.go:234:\t\t\t\tt.Errorf(\"GetDialFunc() counts = %v, want %v\", getCounts(counts), tt.wantCounts)\nfasthttpproxy/http.go:33:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/http.go:63:\tdialFunc, _ := d.GetDialFunc(false)\nfasthttpproxy/proxy_env.go:37:\tdialFunc, _ := d.GetDialFunc(true)\n```"
},
{
"verdict": "INVALID",
"reasoning": "[ARBITER] The reported vulnerability is a programming error (missing error check) rather than a security vulnerability under the provided threat model. The 'attacker' in the library model is the developer or the application code calling the public API. A 'malformed proxy string' is treated as developer configuration error/misuse, not as an adversarial payload from an untrusted external source (like a network packet) that the library processes. Consequently, the resulting panic is a failure due to incorrect library usage, not a security exploit vector reachable by an external attacker via untrusted data input.",
"round": 6,
"file": "fasthttpproxy/http.go",
"finding_title": "Nil DialFunc returned when proxy setup fails (dual-stack)",
"elapsed": 4.5,
"tokens_in": 3294,
"tokens_out": 132,
"cost_usd": 0.00177948
}
]Raw scan report for this file
`fasthttpproxy/http.go` is a small public API surface that accepts a caller-supplied `proxy` string and returns a `fasthttp.DialFunc`. The main trust boundary is the exported functions:
```go
FasthttpHTTPDialer(proxy)
FasthttpHTTPDialerTimeout(proxy, timeout)
FasthttpHTTPDialerDualStack(proxy)
FasthttpHTTPDialerDualStackTimeout(proxy, timeout)
```
In a host application, `proxy` may come from configuration, user input, an API, or a malicious file/URL. The returned `DialFunc` is later used by `fasthttp` to establish outbound connections, often through a proxy.
## Data flow
1. `proxy` is passed directly into `httpproxy.Config{HTTPProxy: proxy, HTTPSProxy: proxy}`.
2. That configuration is placed into a local `Dialer`.
3. `d.GetDialFunc(false)` is called to build the actual `fasthttp.DialFunc`.
4. The error returned by `GetDialFunc` is ignored.
5. The possibly-nil `dialFunc` is returned directly.
## Function-by-function analysis
### `FasthttpHTTPDialer`
Delegates to:
```go
FasthttpHTTPDialerTimeout(proxy, 0)
```
No direct buffer handling, integer arithmetic, goroutine creation, or unsafe code. It inherits the behavior and issues of `FasthttpHTTPDialerTimeout`. The `timeout` of `0` means no explicit dial or CONNECT timeout is configured.
### `FasthttpHTTPDialerTimeout`
This is the primary vulnerable location:
```go
dialFunc, _ := d.GetDialFunc(false)
return dialFunc
```
`GetDialFunc` explicitly returns an `error`, so it is fallible. The error is discarded. If the `proxy` string is malformed, unsupported, or otherwise cannot be turned into a usable dialer, `dialFunc` may be `nil`.
Consequences:
- A caller cannot distinguish a valid dialer from a failed configuration.
- If the returned function is invoked, it may panic as a nil function call.
- If a `fasthttp.Client` treats a nil `Dial` as “not configured”, traffic may silently fall back to direct connections, bypassing the intended proxy.
- A malicious or malformed `proxy` value can therefore cause fail-open behavior or a denial of service.
The `timeout` value is also passed directly into `Dialer.Timeout` and `Dialer.ConnectTimeout`. A zero or negative timeout may disable timeout enforcement, allowing a malicious or unresponsive proxy to stall client I/O. This is lower severity because `timeout` is usually host-controlled, but the convenience wrappers make zero the default.
### `FasthttpHTTPDialerDualStack`
Delegates to:
```go
FasthttpHTTPDialerDualStackTimeout(proxy, 0)
```
It inherits the same unchecked error, possible nil `DialFunc`, and zero-timeout behavior.
### `FasthttpHTTPDialerDualStackTimeout`
Same issue as the non-dual-stack timeout variant:
```go
dialFunc, _ := d.GetDialFunc(false)
return dialFunc
```
The only additional behavior is that it enables dual-stack dialing. It still ignores the configuration error and may return a nil `fasthttp.DialFunc`.
## Other checks
- No fixed-size stack buffers or `memcpy`-style operations.
- No integer arithmetic that can overflow into a size or index.
- No tagged unions or variant discriminators.
- No command execution, SQL construction, or `unsafe` package usage.
- No obvious goroutine leak or deadlock introduced in this file.
- The most important class of bug is unchecked fallible configuration, nil callback return, and fail-open proxy bypass.
```json
[
{
"severity": "high",
"title": "Nil DialFunc returned when proxy setup fails",
"function": "FasthttpHTTPDialerTimeout",
"description": "The error returned by d.GetDialFunc(false) is discarded. If the attacker-controlled proxy string is malformed or otherwise unsupported, dialFunc can be nil. Callers cannot detect the failure; invoking the returned fasthttp.DialFunc can panic, and if a fasthttp.Client treats a nil Dial as unset, traffic may silently bypass the intended proxy."
},
{
"severity": "high",
"title": "Nil DialFunc returned when proxy setup fails (dual-stack)",
"function": "FasthttpHTTPDialerDualStackTimeout",
"description": "The dual-stack variant has the same unchecked error and nil-return issue. A crafted proxy string can produce a nil fasthttp.DialFunc, leading to panic in callers or silent proxy bypass in fasthttp clients."
},
{
"severity": "low",
"title": "Convenience dialers default to no timeout",
"function": "FasthttpHTTPDialer",
"description": "FasthttpHTTPDialer and FasthttpHTTPDialerDualStack pass timeout 0, which disables explicit dial and CONNECT timeouts. Combined with an attacker-influenced proxy string, this can allow slow or unresponsive proxy behavior to stall client I/O. These constructors also inherit the nil-DialFunc issue from the timeout variants."
}
]
```