Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return ErrBuf when records processed < actual records send by upstream #1493

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -852,16 +852,25 @@ func (dns *Msg) unpack(dh Header, msg []byte, off int) (err error) {
}

dns.Answer, off, err = unpackRRslice(int(dh.Ancount), msg, off)
if err == nil && int(dh.Ancount) > int(len(dns.Answer)) {
return ErrBuf
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we return ErrBuf and the code below is only executed when the count is smaller which is weird. So this def. feels wrong. And can't go in as such

Again: an upstream sends back a message that is too large without TC? That is a protocol violation, unless I still misunderstand

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for the reply - @miekg. Sure sure I understand. Yes, this is a scenario seen due to non-complying DNS server.

@tmthrgd mentioned the fix is actually much simpler than what I proposed here #1493 (comment). I am kindly requesting Tom's help here.

@rdrozhdzh has very nicely summarized the issue I am trying to explain - #1493 (comment).

Quote - Sporadic "legitimization" of malformed DNS responses by dns library under some condition, i.e. the library doesn't return error, but instead it silently returns (possibly truncated or "fixed") DNS message.

The type of malformed message being considered here is when the number of resource records declared in the message header does not correspond to the actual number of resource records existing in DNS response message. As a special case, this may happen when dns library allocates smaller UDP buffer than the size of DNS response coming from (non-complying) DNS server.

The condition when the error is not reported by dns library is if the last octet of DNS message (i.e. the last byte of the buffer) corresponds to last octet of one of resource records.

Copy link
Author

@SriHarsha001 SriHarsha001 Mar 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tmthrgd - Earlier you had mentioned about a simpler fix for this issue, would you be able to help here please ?

// The header counts might have been wrong so we need to update it
dh.Ancount = uint16(len(dns.Answer))
if err == nil {
dns.Ns, off, err = unpackRRslice(int(dh.Nscount), msg, off)
}
if err == nil && int(dh.Nscount) > int(len(dns.Ns)) {
SriHarsha001 marked this conversation as resolved.
Show resolved Hide resolved
return ErrBuf
}
// The header counts might have been wrong so we need to update it
dh.Nscount = uint16(len(dns.Ns))
if err == nil {
dns.Extra, _, err = unpackRRslice(int(dh.Arcount), msg, off)
}
if err == nil && int(dh.Arcount) > int(len(dns.Extra)) {
SriHarsha001 marked this conversation as resolved.
Show resolved Hide resolved
return ErrBuf
}
// The header counts might have been wrong so we need to update it
dh.Arcount = uint16(len(dns.Extra))

Expand Down
86 changes: 86 additions & 0 deletions msg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,89 @@ func TestLenDynamicA(t *testing.T) {
}
}
}

func TestUnPackForErrBuf(t *testing.T) {
SriHarsha001 marked this conversation as resolved.
Show resolved Hide resolved
var testCases = []struct {
domainName string
noOfARecords int
udpSize uint16
expectErrBuf bool
}{
// Testing with 15 characters in the domain name.
// ----------------------------------------------------------------------------------------------------------------------
// Length of response for 15 A records will be 482 bytes and UDPSize is 512. Response is not truncated.
{domainName: "example123.org.", noOfARecords: 15, udpSize: 512, expectErrBuf: false},

// Length of response for 16 A records will be 512 bytes and UDPSize is 512. Response is not truncated.
{domainName: "example123.org.", noOfARecords: 16, udpSize: 512, expectErrBuf: false},

// Length of response for 17 A records will be 542 bytes and UDPSize is 512. Response is truncated, so expect ErrBuf.
{domainName: "example123.org.", noOfARecords: 17, udpSize: 512, expectErrBuf: true},

// Length of response for 17 A records will be 542 bytes and UDPSize is 542. Response is not truncated.
{domainName: "example123.org.", noOfARecords: 17, udpSize: 542, expectErrBuf: false},

// Testing with 19 characters in the domain name.
// ----------------------------------------------------------------------------------------------------------------------
// Length of response for 13 A records will be 478 bytes and UDPSize is 512. Response is not truncated.
{domainName: "testingexample.org.", noOfARecords: 13, udpSize: 512, expectErrBuf: false},

// Length of response for 14 A records will be 512 bytes and UDPSize is 512. Response is not truncated.
{domainName: "testingexample.org.", noOfARecords: 14, udpSize: 512, expectErrBuf: false},

// Length of response for 15 A records will be 546 bytes and UDPSize is 512. Response is truncated, so expect ErrBuf.
{domainName: "testingexample.org.", noOfARecords: 15, udpSize: 512, expectErrBuf: true},

// Length of response for 15 A records will be 546 bytes and UDPSize is 546. Response is not truncated.
{domainName: "testingexample.org.", noOfARecords: 15, udpSize: 546, expectErrBuf: false},
}

for _, tc := range testCases {
testName := tc.domainName + " with " + strconv.Itoa(tc.noOfARecords) + " A-records"
t.Run(testName, func(t *testing.T) {
testLongResponseDNSServer(t, tc.domainName, tc.noOfARecords, tc.udpSize, tc.expectErrBuf)
})
}
}

func testLongResponseDNSServer(t *testing.T, domainName string, noOfARecords int, UDPSize uint16, expectErrBuf bool) {
HandleFunc(domainName, HelloServerLargeResponse)
defer HandleRemove(domainName)

srv, address, _, err := RunLocalUDPServer(":0")
if err != nil {
t.Fatalf("unable to run test server: %v", err)
}
defer srv.Shutdown()

reqm := new(Msg)
reqm.SetQuestion(domainName, TypeA)

c := new(Client)
c.Net = "udp"
c.UDPSize = UDPSize

M.Lock()
M.max = noOfARecords
M.Unlock()

response := new(Msg)
response, _, err = c.Exchange(reqm, address)

if expectErrBuf {
if err != ErrBuf {
t.Error("Expected ErrBuf due to truncation")
}
} else {

if err != nil {
t.Errorf("failed to exchange: %v", err)
}
if len(response.Answer) != M.max {
t.Errorf("Expected no of A records %v, but got %v", len(response.Answer), M.max)
}
if response.Rcode != RcodeSuccess {
t.Errorf("Expected No error, but got %v", response.Rcode)
}
}
}
Loading