From 530b0a5ff2ad68cc0421f10dd93568945cc671c9 Mon Sep 17 00:00:00 2001 From: Ville Vesilehto Date: Thu, 16 Jul 2026 03:54:46 +0300 Subject: [PATCH] Merge commit from fork DoH, DoQ, and DNS-over-gRPC unpack messages without the acceptance checks used by UDP and TCP. An unauthenticated request with a large QDCOUNT can therefore force excessive allocations while names are decoded and exhaust server memory. Enforce the same request policy across all server transports. Signed-off-by: Ville Vesilehto --- core/dnsserver/server_grpc.go | 4 ++-- core/dnsserver/server_quic.go | 4 ++-- plugin/pkg/dnsutil/message.go | 24 ++++++++++++++++++++++++ plugin/pkg/dnsutil/message_test.go | 29 +++++++++++++++++++++++++++++ plugin/pkg/doh/doh.go | 13 +++++++++---- 5 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 plugin/pkg/dnsutil/message.go create mode 100644 plugin/pkg/dnsutil/message_test.go diff --git a/core/dnsserver/server_grpc.go b/core/dnsserver/server_grpc.go index 42f54eaff..972a3b2c0 100644 --- a/core/dnsserver/server_grpc.go +++ b/core/dnsserver/server_grpc.go @@ -9,6 +9,7 @@ import ( "github.com/coredns/caddy" "github.com/coredns/coredns/pb" + "github.com/coredns/coredns/plugin/pkg/dnsutil" "github.com/coredns/coredns/plugin/pkg/reuseport" "github.com/coredns/coredns/plugin/pkg/transport" @@ -177,8 +178,7 @@ func (s *ServergRPC) Query(ctx context.Context, in *pb.DnsPacket) (*pb.DnsPacket if len(in.GetMsg()) > dns.MaxMsgSize { return nil, fmt.Errorf("dns message exceeds size limit: %d", len(in.GetMsg())) } - msg := new(dns.Msg) - err := msg.Unpack(in.GetMsg()) + msg, err := dnsutil.UnpackRequest(in.GetMsg()) if err != nil { return nil, err } diff --git a/core/dnsserver/server_quic.go b/core/dnsserver/server_quic.go index aff71e626..9c89334b9 100644 --- a/core/dnsserver/server_quic.go +++ b/core/dnsserver/server_quic.go @@ -11,6 +11,7 @@ import ( "time" "github.com/coredns/coredns/plugin/metrics/vars" + "github.com/coredns/coredns/plugin/pkg/dnsutil" clog "github.com/coredns/coredns/plugin/pkg/log" cproxyproto "github.com/coredns/coredns/plugin/pkg/proxyproto" "github.com/coredns/coredns/plugin/pkg/reuseport" @@ -211,8 +212,7 @@ func (s *ServerQUIC) serveQUICStream(stream *quic.Stream, conn *quic.Conn) { return } - req := &dns.Msg{} - err = req.Unpack(buf) + req, err := dnsutil.UnpackRequest(buf) if err != nil { clog.Debugf("unpacking quic packet: %s", err) s.closeQUICConn(conn, DoQCodeProtocolError) diff --git a/plugin/pkg/dnsutil/message.go b/plugin/pkg/dnsutil/message.go new file mode 100644 index 000000000..c2da9dba5 --- /dev/null +++ b/plugin/pkg/dnsutil/message.go @@ -0,0 +1,24 @@ +package dnsutil + +import ( + "encoding/binary" + "errors" + + "github.com/miekg/dns" +) + +var errRequestRejected = errors.New("dns request rejected") + +// UnpackRequest unpacks a request after applying the default miekg/dns request policy. +func UnpackRequest(msg []byte) (*dns.Msg, error) { + var header dns.Header + if _, err := binary.Decode(msg, binary.BigEndian, &header); err != nil { + return nil, dns.ErrBuf + } + if dns.DefaultMsgAcceptFunc(header) != dns.MsgAccept { + return nil, errRequestRejected + } + + request := new(dns.Msg) + return request, request.Unpack(msg) +} diff --git a/plugin/pkg/dnsutil/message_test.go b/plugin/pkg/dnsutil/message_test.go new file mode 100644 index 000000000..a913a5165 --- /dev/null +++ b/plugin/pkg/dnsutil/message_test.go @@ -0,0 +1,29 @@ +package dnsutil + +import ( + "testing" + + "github.com/miekg/dns" +) + +func TestUnpackRequest(t *testing.T) { + request := new(dns.Msg) + request.SetQuestion("example.org.", dns.TypeA) + + wire, err := request.Pack() + if err != nil { + t.Fatal(err) + } + if _, err := UnpackRequest(wire); err != nil { + t.Fatalf("UnpackRequest() rejected a valid request: %v", err) + } + + request.Question = append(request.Question, request.Question[0]) + wire, err = request.Pack() + if err != nil { + t.Fatal(err) + } + if _, err := UnpackRequest(wire); err == nil { + t.Fatal("UnpackRequest() accepted multiple questions") + } +} diff --git a/plugin/pkg/doh/doh.go b/plugin/pkg/doh/doh.go index 45d25a0f3..a5c46202d 100644 --- a/plugin/pkg/doh/doh.go +++ b/plugin/pkg/doh/doh.go @@ -9,6 +9,8 @@ import ( "net/http" "strings" + "github.com/coredns/coredns/plugin/pkg/dnsutil" + "github.com/miekg/dns" ) @@ -106,7 +108,12 @@ func RequestToMsgWire(req *http.Request) (*dns.Msg, []byte, error) { // requestToMsgPost extracts the dns message from the request body. func requestToMsgPost(req *http.Request) (*dns.Msg, []byte, error) { defer req.Body.Close() - return toMsgWire(req.Body) + buf, err := io.ReadAll(http.MaxBytesReader(nil, req.Body, maxDNSQuerySize)) + if err != nil { + return nil, nil, err + } + m, err := dnsutil.UnpackRequest(buf) + return m, buf, err } const maxDNSQuerySize = 65536 @@ -149,9 +156,7 @@ func base64ToMsgWire(b64 string) (*dns.Msg, []byte, error) { return nil, nil, err } - m := new(dns.Msg) - err = m.Unpack(buf) - + m, err := dnsutil.UnpackRequest(buf) return m, buf, err }