robur-coop / udns

[deprecated, developmeht moved to https://github.com/mirage/ocaml-dns] µDNS - an opinionated Domain Name System (DNS) library
BSD 2-Clause "Simplified" License
55 stars 5 forks source link

Query with a huge question section can lead to crash #5

Closed Willy-Tan closed 6 years ago

Willy-Tan commented 6 years ago

After trying to fuzz the primary server from the example folder with big inputs, I found some packets that could crash main.native. In this example, the culprit is a query of size 3451 that consists of many questions (https://pastebin.com/tnV0JUbR for a hexadecimal and byte representation of that packet) :

2018-07-05 16:21:20 +01:00: INF [tcpip-stack-socket] Manager: connect
2018-07-05 16:21:20 +01:00: INF [tcpip-stack-socket] Manager: configuring
2018-07-05 16:21:20 +01:00: WRN [application] no secondaries keys found (err not found  TTL 300 soa SOA foo._key-management foo._key-management 0 16384 2048 1048576 300)
2018-07-05 16:21:20 +01:00: INF [application] loaded zone: mirage.  2560    SOA ns.mirage.hostmaster.mirage.    1   10  5   60  2560
mirage. 2560    NS  ns.mirage.
charrua.mirage. 2560    A   10.0.42.3
ns.mirage.  2560    A   10.0.42.2
resolver.mirage.    2560    A   10.0.42.5
router.mirage.  2560    A   10.0.42.1
secondary.mirage.   2560    A   10.0.42.4
www.mirage. 2560    CNAME   router.mirage.

2018-07-05 16:21:20 +01:00: INF [dns_mirage_server] DNS server listening on UDP port 53
2018-07-05 16:21:20 +01:00: INF [dns_mirage_server] DNS server listening on TCP port 53
2018-07-05 16:21:26 +01:00: INF [dns_mirage_server] udp frame from 127.0.0.1:33525
2018-07-05 16:21:26 +01:00: ERR [dns_server] 181 questions foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?, foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, foo.my.domain A?,
foo.my.domain A?, bailing
Fatal error: exception (Invalid_argument
  "invalid bounds in Cstruct.BE.set_uint16 [0,450](450) off=449 len=2")
Raised at file "format.ml" (inlined), line 242, characters 35-52
Called from file "format.ml", line 469, characters 8-33
Called from file "format.ml", line 484, characters 6-24

TL;DR : The packet is faulty according to the uDNS primary server handle function because the question section contains more than one question. The server tries to reply with an answer containing the same question section (I think it must be done for security purposes), but it creates a buffer with a shorter length, that's why that error is raised and crashes the application.

Now for a more detailed explanation (took me a really long time to figure out !). If I understood correctly :

Maybe a solution would be to rise max_udp_size to 4096 ?

hannesm commented 6 years ago

thanks for the report :)

I added a regression test (which decodes the frame and afterwards expects error to produce something sensible (and esp. not crash).

the fix I applied is if 450 byte are not sufficient to encode out only the first question. max_udp_size is my sanity guard to not act as an amplifier (which maybe should be tweaked to be a ratio between incoming and outgoing packet) -- given that I don't support any frames with multiple questions, I don't think I need to send the entire question back (please let me know if there's a requirement in some RFC to always send back the entire question section).

Willy-Tan commented 6 years ago

Sending the entire question back isn't a compulsory requirement but RFC5452, a Proposed Standard RFC, advises that the question section in the answer should match the question section from the query to help against spoofed packets. There may be some resolvers implementing this RFC, so that may be an issue, although I don't really know if there are many packets asking for multiples questions in a single query.

I will continue fuzzing in that direction, there may be other cases in which it could happen. The parsing function looks really solid though, I haven't found any bugs here 🤔