Resource Allocation Without Limits in net/http
Package
The Go standard library provides convenient functions such as
net/http.ListenAndServe
, net/http.ListenAndServeTLS
, net/http.Serve
, and
net/http.ServeTLS
to quickly start HTTP and HTTPS servers. However, a
significant security issue with these functions is that they do not allow
developers to specify critical timeout values—such as ReadTimeout,
WriteTimeout, or IdleTimeout—on the server. By default, these timeouts are
unset (zero), meaning the server will wait indefinitely for clients to
send or receive data. This behavior can be exploited by malicious actors
using techniques like Slowloris attacks, where an attacker intentionally
opens many connections and sends data very slowly to exhaust server
resources. Without timeouts, each connection can tie up a goroutine and
file descriptor, leading to resource exhaustion and making the server
susceptible to denial-of-service (DoS) attacks.
Beyond malicious intent, the absence of timeouts also increases the risk
from buggy or misbehaving clients that inadvertently leave connections open,
potentially causing the same resource exhaustion problem. In production
environments, it is critical to protect against both unintentional and
intentional abuse by configuring sensible timeouts on all HTTP servers.
Since the shortcut functions (ListenAndServe
, etc.) do not provide any
parameters for timeout configuration, developers must instead use an
http.Server struct and explicitly set the timeout fields. Failing to do so
can compromise both the availability and stability of the server, making
this an important security and operational concern.
Examples
package main
import (
"io"
"log"
"net/http"
)
func main() {
helloHandler := func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "Hello, world!\n")
}
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Remediation
To mitigate resource exhaustion risks, replace with http.Server or similar with proper timeout values.
package main
import (
"io"
"log"
"net/http"
"time"
)
func main() {
helloHandler := func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "Hello, world!\n")
}
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Fatal(server.ListenAndServe())
}
False Positives
In the case of a false positive the rule can be suppressed. Simply add a
trailing or preceding comment line with either the rule ID (GO007
) or
rule category name (resource_allocation_without_limits
).
- Using rule ID
- Using category name
package main
import (
"io"
"log"
"net/http"
)
func main() {
helloHandler := func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "Hello, world!\n")
}
http.HandleFunc("/hello", helloHandler)
// suppress: GO007
log.Fatal(http.ListenAndServe(":8080", nil))
}
package main
import (
"io"
"log"
"net/http"
)
func main() {
helloHandler := func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "Hello, world!\n")
}
http.HandleFunc("/hello", helloHandler)
// suppress: resource_allocation_without_limits
log.Fatal(http.ListenAndServe(":8080", nil))
}