netty / netty

Netty project - an event-driven asynchronous network application framework

Home Page:http://netty.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

"io.netty.handler.codec.CorruptedFrameException: name has an out-of-range pointer" on some TXT lookup with long SPF values

yo000 opened this issue · comments

Hello, We discovered this bug through Graylog DNS Lookups. I isolated problematic code, but my java skills are too weak to precisely locate the problem

Expected behavior

TXT lookup returns values

$ drill -t TXT outlook.com
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 22722
;; flags: qr rd ra ; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; outlook.com. IN   TXT

;; ANSWER SECTION:
outlook.com.    300  IN TXT     "v=spf1 include:spf-a.outlook.com include:spf-b.outlook.com ip4:157.55.9.128/25 include:spf.protection.outlook.com include:spf-a.hotmail.com include:_spf-ssg-b.microsoft.com include:_spf-ssg-c.microsoft.com ~all"
outlook.com.    300  IN TXT     "google-site-verification=0iLWhIMhXEkeWwWfFU4ursTn-_OvoOjaA0Lr7Pg1sEM"
outlook.com.    300  IN TXT     "google-site-verification=DC2uC-T8kD33lINhNzfo0bNBrw-vrCXs5BPF5BXY56g"

Actual behavior

TXT Lookup return exception "io.netty.handler.codec.CorruptedFrameException: name has an out-of-range pointer" on seemingly valid TXT.
For example on outlook.com :

$ drill -t TXT outlook.com
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 22722
;; flags: qr rd ra ; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; outlook.com. IN   TXT

;; ANSWER SECTION:
outlook.com.    300  IN TXT     "v=spf1 include:spf-a.outlook.com include:spf-b.outlook.com ip4:157.55.9.128/25 include:spf.protection.outlook.com include:spf-a.hotmail.com include:_spf-ssg-b.microsoft.com include:_spf-ssg-c.microsoft.com ~all"
outlook.com.    300  IN TXT     "google-site-verification=0iLWhIMhXEkeWwWfFU4ursTn-_OvoOjaA0Lr7Pg1sEM"
outlook.com.    300  IN TXT     "google-site-verification=DC2uC-T8kD33lINhNzfo0bNBrw-vrCXs5BPF5BXY56g"

We have this exception

TXT record [0] retrieved with content [DefaultDnsRawRecord(outlook.com. 300 IN TXT 69B)].
The decoded TXT record is [google-site-verification=0iLWhIMhXEkeWwWfFU4ursTn-_OvoOjaA0Lr7Pg1sEM.]
TXT record [1] retrieved with content [DefaultDnsRawRecord(outlook.com. 300 IN TXT 69B)].
The decoded TXT record is [google-site-verification=DC2uC-T8kD33lINhNzfo0bNBrw-vrCXs5BPF5BXY56g.]
TXT record [2] retrieved with content [DefaultDnsRawRecord(outlook.com. 300 IN TXT 211B)].
Exception : io.netty.handler.codec.CorruptedFrameException: name has an out-of-range pointer

Steps to reproduce

    	NioEventLoopGroup nettyEventLoop = new NioEventLoopGroup(8);
    	DnsNameResolver resolver = new DnsNameResolverBuilder(nettyEventLoop.next())
                                                                .channelType(NioDatagramChannel.class)
                                                                .queryTimeoutMillis(2000)
                                                                .recursionDesired(true)
                                                                .build();
    	String hostName = new String("outlook.com");

        DnsResponse content = null;
        try {
            content = resolver.query(new DefaultDnsQuestion(hostName, DnsRecordType.TXT)).get(10000, TimeUnit.MILLISECONDS).content();
            int count = content.count(DnsSection.ANSWER);
            
            for (int i = 0; i < count; i++) {

                final DnsRecord dnsRecord = content.recordAt(DnsSection.ANSWER, i);
                System.out.printf("TXT record [%d] retrieved with content [%s].\n", i, dnsRecord);

                if (dnsRecord instanceof DefaultDnsRawRecord) {
                    final DefaultDnsRawRecord txtRecord = (DefaultDnsRawRecord) dnsRecord;
                    final String decodeTxtRecord = DefaultDnsRecordDecoder.decodeName(txtRecord.content());
                    System.out.printf("The decoded TXT record is [%s]\n", decodeTxtRecord);
                }
            }
        } catch (Exception e) {
            System.out.printf("Exception : %s", e);
        } finally {
            if (content != null) {
                // Must manually release references on content object since the DnsResponse class extends ReferenceCounted
                content.release();
            }
        }

Netty version

4.1.106.Final

JVM version (e.g. java -version)

Reproduced on OpenJDK 64-Bit Server VM (build 17.0.10+7-1, mixed mode, sharing)@FreeBSD13.2, Java(TM) SE Runtime Environment (build 20.0.1+9-29)@Windows 11

OS version (e.g. uname -a)

Reproduced on FreeBSD 13.2, Debian 12.4 and Windows 11

Will have a look

@yo000 can you share the decodeTxtRecord method ?

Sure, it's just a call to DefaultDnsRecordDecoder.decodeName.
I updated the code above to supress decodeTxtRecord method

@yo000 decodeName is not the right method for decoding TXT records as TXT records don't follow the same format as domain names. I think you want to use something like:

    private static List<String> decodeTxt(DnsRecord record) {
        if (!(record instanceof DnsRawRecord)) {
            return Collections.emptyList();
        }
        List<String> list = new ArrayList<String>();
        ByteBuf data = ((DnsRawRecord) record).content();
        int idx = data.readerIndex();
        int wIdx = data.writerIndex();
        while (idx < wIdx) {
            int len = data.getUnsignedByte(idx++);
            list.add(data.toString(idx, len, CharsetUtil.UTF_8));
            idx += len;
        }
        return list;
    }

From the RFC:

3.3.14. TXT RDATA format

    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    /                   TXT-DATA                    /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

where:

TXT-DATA        One or more <character-string>s.
<character-string> is a single
length octet followed by that number of characters.  <character-string>
is treated as binary information, and can be up to 256 characters in
length (including the length octet).