From 96859227463700b4181b65bc7a69280dfe87374a Mon Sep 17 00:00:00 2001 From: enp7s0d <75983347+Pushpenderrathore@users.noreply.github.com> Date: Fri, 14 Aug 2026 23:56:24 +0530 Subject: [PATCH] Fix two crashes in the default DNS forward/cache path Rex::Proto::DNS::Server#default_dispatch_request crashed with a NoMethodError the moment it had to finalize an empty response, which happens on any query that misses the cache and comes back from the resolver with no answers - an out-of-scope query being forwarded, for instance, or any query with a genuinely empty result. The exception was uncaught, so it killed the listener thread and stopped the whole server. Two mistakes, both on code Packet.encode_drb documents as returning a Dnsruby::Message: - req.header.rCode= does not exist on Dnsruby::Header; the real setter is rcode= (lowercase). The class does define an rCode-cased method elsewhere as a getter alias, which is presumably what led to the wrong casing here. - req.data does not exist on Dnsruby::Message either; the real serializer is #encode. Packet.encode_raw, a few lines above in the same file, already handles this correctly by checking respond_to?(:data) for a legacy Net::DNS::Packet and falling back to #encode otherwise - default_dispatch_request just used the wrong branch of that same distinction. No existing spec covered this path, so both bugs shipped and stayed live. Reproduced against a real client. Fixed and added coverage for the empty answer case that crashed. --- lib/rex/proto/dns/server.rb | 4 +-- spec/lib/rex/proto/dns/server_spec.rb | 40 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 spec/lib/rex/proto/dns/server_spec.rb diff --git a/lib/rex/proto/dns/server.rb b/lib/rex/proto/dns/server.rb index a8a4dd57d2e84..36de1e135acc7 100644 --- a/lib/rex/proto/dns/server.rb +++ b/lib/rex/proto/dns/server.rb @@ -185,10 +185,10 @@ def default_dispatch_request(cli,data) # Finalize answers in response # Check for empty response prior to sending if req.answer.size < 1 - req.header.rCode = Dnsruby::RCode::NOERROR + req.header.rcode = Dnsruby::RCode::NOERROR end req.header.qr = true # Set response bit - send_response(cli, req.data) + send_response(cli, req.encode) end # diff --git a/spec/lib/rex/proto/dns/server_spec.rb b/spec/lib/rex/proto/dns/server_spec.rb new file mode 100644 index 0000000000000..95b5da04ce940 --- /dev/null +++ b/spec/lib/rex/proto/dns/server_spec.rb @@ -0,0 +1,40 @@ +require 'rex/proto/dns' + +RSpec.describe Rex::Proto::DNS::Server do + subject(:server) { described_class.new } + + let(:cli) { double('client', write: nil) } + + # a real, encodable query for a name that is in neither the cache nor + # anything the stubbed resolver knows about + def query_for(name) + query = Dnsruby::Message.new + query.add_question(Dnsruby::Name.create("#{name}."), Dnsruby::Types::A) + query.encode + end + + describe '#default_dispatch_request' do + context 'when the forwarded response carries no answers' do + before do + # the resolver forwards the query and comes back empty, which is the + # path that finalizes an empty response - the one that crashed + empty_response = Dnsruby::Message.new + server.fwd_res = double('resolver', send: empty_response) + end + + it 'does not raise' do + expect { server.default_dispatch_request(cli, query_for('empty.example.com')) }.not_to raise_error + end + + it 'sends a well formed, encoded response back to the client' do + expect(cli).to receive(:write) do |data| + expect(data).to be_a(String) + reply = Dnsruby::Message.decode(data) + expect(reply.header.get_header_rcode).to eq(Dnsruby::RCode::NOERROR) + expect(reply.header.qr).to eq(true) + end + server.default_dispatch_request(cli, query_for('empty.example.com')) + end + end + end +end