Null map
| Vulnerability potential | Low |
| DDoS potential | Medium |
The map might be null
Impact
In Go a map declared but never initialized has the value nil. Reading from a
nil map is safe — lookups return the element type’s zero value and len is
0 — but writing to a nil map (m[k] = v) or deleting through it in
a way that mutates triggers a runtime panic: assignment to entry in nil map.
The trap is that the read side works, so a nil map can pass through a lot of
code unnoticed until the first write, which then aborts the goroutine. If the
panic is not recovered it crashes the whole program. The defect typically comes
from declaring var m map[K]V (zero value nil) instead of
m := make(map[K]V), from a struct field whose map was never initialized in
the constructor, or from a function that returns a nil map on an error path
that callers then write into.
Vulnerability potential
This is principally an availability defect.
- A write to a
nilmap panics; an unrecovered panic terminates the process, so any code path where attacker-influenced input causes a previously-only-read map to be written becomes a denial-of-service trigger. - In a server using
recoverper request, the panic unwinds the current goroutine and may leave locks held or shared state half-updated, an indirect correctness/consistency risk.
There is no memory-corruption angle: Go detects the condition and panics deterministically rather than performing an unsafe write.
Technical details
A Go map value is a pointer to a runtime hmap header; the zero value of that
pointer is nil. Lookups (mapaccess) special-case the nil header and
return the zero value, which is why reads never fail. Assignment goes through
mapassign, which must allocate a bucket and therefore needs a real hmap;
on a nil map it cannot, so the runtime calls panic with
assignment to entry in nil map.
nil map vs empty map
var m map[string]int yields a nil map: readable, not writable.
m := make(map[string]int) (or a map literal map[string]int{}) yields an
initialized, empty, writable map. The two are indistinguishable on read, which is
exactly why the bug hides. m == nil is the explicit test.
Common sources
Struct fields (type S struct { cache map[K]V }) default their map to nil
unless the constructor calls make; functions that return map[K]V should
return an initialized empty map rather than nil if callers will write to it.
Catching the issue
Tooling
go vet does not flag this directly, but staticcheck and golangci-lint
(with the nilness analyzer and others) detect writes to maps that may be
nil along a path. The Go runtime’s panic message names the file and line, so
a single test that exercises the write path surfaces it immediately.
Design rules
Always initialize a map with make before writing; initialize map fields in
constructors; never return a nil map from a function whose contract allows
the caller to add entries. Guard uncertain maps with
if m == nil { m = make(map[K]V) } before the first write.
How to reproduce
Run the program; it panics with assignment to entry in nil map on the write,
even though the preceding read succeeded.
package main
import "fmt"
func main() {
var m map[string]int // nil map (zero value)
fmt.Println("read ok:", m["missing"]) // reads return zero value: 0
m["key"] = 1 // panic: assignment to entry in nil map
fmt.Println(m)
}