mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-07-16 13:17:19 +02:00
fix(doh,doq): reject oversized upstream DNS responses
DoH, DoH3, and DoQ response paths previously used io.ReadAll on attacker-controlled upstream responses before enforcing any protocol-level size limit. A malicious or compromised upstream could return an oversized body or stream and force ctrld to buffer unbounded data before eventually failing DNS parsing. Cap DoH/DoH3 response bodies at dns.MaxMsgSize and cap DoQ streams at the 2-byte length prefix plus dns.MaxMsgSize. Also limit non-200 DoH error bodies so error formatting cannot consume large upstream responses.
This commit is contained in:
committed by
Cuong Manh Le
parent
35455eb0b9
commit
3fe9b27fb4
@@ -25,6 +25,16 @@ const (
|
||||
dohOsHeader = "x-cd-os"
|
||||
dohClientIDPrefHeader = "x-cd-cpref"
|
||||
headerApplicationDNS = "application/dns-message"
|
||||
|
||||
// dohMaxResponseSize caps the response body read from a DoH/DoH3
|
||||
// upstream. A DNS message is bounded by the protocol's 16-bit length
|
||||
// field; anything larger cannot be a valid response. The cap stops a
|
||||
// malicious or compromised upstream from driving ctrld into unbounded
|
||||
// memory growth via io.ReadAll on attacker-controlled bytes.
|
||||
dohMaxResponseSize = dns.MaxMsgSize
|
||||
// dohMaxErrorBodySize bounds how much of a non-200 response body is
|
||||
// read for inclusion in the returned error.
|
||||
dohMaxErrorBodySize = 1024
|
||||
)
|
||||
|
||||
// EncodeOsNameMap provides mapping from OS name to a shorter string, used for encoding x-cd-os value.
|
||||
@@ -130,13 +140,17 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, dohMaxErrorBodySize))
|
||||
return nil, fmt.Errorf("wrong response from DOH server, got: %s, status: %d", string(body), resp.StatusCode)
|
||||
}
|
||||
|
||||
buf, err := io.ReadAll(io.LimitReader(resp.Body, dohMaxResponseSize+1))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read message from response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("wrong response from DOH server, got: %s, status: %d", string(buf), resp.StatusCode)
|
||||
if len(buf) > dohMaxResponseSize {
|
||||
return nil, fmt.Errorf("DoH response exceeds %d-byte maximum DNS message size", dohMaxResponseSize)
|
||||
}
|
||||
|
||||
answer := new(dns.Msg)
|
||||
|
||||
Reference in New Issue
Block a user