GitHub Security Advisory: GHSA-63cw-r7xf-jmwr
Package: github.com/coredns/coredns (GO)
Severity: HIGH (CVSS 7.5)
CWE: CWE-400: Uncontrolled Resource Consumption
Vulnerable versions: < 1.14.3
Patched in: 1.14.3

Summary: CoreDNS DoH GET oversized dns= query parameter causes pre-validation CPU and memory amplification

Details:
### Summary

CoreDNS's DNS-over-HTTPS (DoH) GET path accepts oversized `dns=` query values and performs substantial request parsing, query unescaping, base64 decoding, and message unpacking work before returning `400 Bad Request`.

A remote, unauthenticated attacker can repeatedly send oversized DoH GET requests to `/dns-query?dns=...` and force high CPU usage, large transient allocations, elevated garbage-collection pressure, and increased resident memory consumption even though the requests are ultimately rejected.

This is a denial-of-service issue caused by expensive pre-validation processing on the DoH GET path.

### Details

The vulnerable flow is in `plugin/pkg/doh/doh.go`:

- `RequestToMsg()` dispatches GET requests to `requestToMsgGet()`:
  - `plugin/pkg/doh/doh.go:79-89`
- `requestToMsgGet()` calls `req.URL.Query()`, extracts `dns`, and passes it directly to `base64ToMsg()`:
  - `plugin/pkg/doh/doh.go:99-108`
- `base64ToMsg()` decodes the full attacker-controlled value via `b64Enc.DecodeString()` and only then attempts to unpack it into a DNS message:
  - `plugin/pkg/doh/doh.go:121-130`

Relevant snippet:

```go
func requestToMsgGet(req *http.Request) (*dns.Msg, error) {
    values := req.URL.Query()
    b64, ok := values["dns"]
    if !ok {
        return nil, fmt.Errorf("no 'dns' query parameter found")
    }
    if len(b64) != 1 {
        return nil, fmt.Errorf("multiple 'dns' query values found")
    }
    return base64ToMsg(b64[0])
}

func base64ToMsg(b64 string) (*dns.Msg, error) {
    buf, err := b64Enc.DecodeString(b64)
    if err != nil {
        return nil, err
    }

    m := new(dns.Msg)
    err = m.Unpack(buf)

    return m, err
}
````

By contrast, the POST path applies a bounded read before unpacking:

```go
func toMsg(r io.ReadCloser) (*dns.Msg, error) {
    buf, err := io.ReadAll(http.MaxBytesReader(nil, r, 65536))
    if err != nil {
        return nil, err
    }
    m := new(dns.Msg)
    err = m.Unpack(buf)
    return m, err
}
```

So, POST is explicitly size-bounded, while GET is not equivalently bounded before expensive parsing and decoding work occurs.

In addition, the HTTPS server is created in `core/dnsserver/server_https.go:87-92` without an explicit early GET-path size guard in this path:

```go
srv := &http.Server{
    ReadTimeout:  s.ReadTimeout,
    WriteTimeout: s.WriteTimeout,
    IdleTimeout:  s.IdleTimeout,
    ErrorLog:     stdlog.New(&loggerAdapter{}, "", 0),
}
```

As a result, oversized DoH GET request targets are processed through:

1. HTTP request-line parsing
2. URL query parsing / unescaping
3. DoH GET extraction
4. base64 decoding
5. DNS message unpacking

before the request is rejected.

### Root cause

The root cause is missing early size validation on the DoH GET path.

More specifically:

* `requestToMsgGet()` performs `req.URL.Query()` on attacker-controlled oversized request targets.
* The extracted `dns` value is passed to `base64ToMsg()` without an encoded-length or decoded-length bound