# HG changeset patch # User Florian Weimer # Date 1392651473 -3600 # Mon Feb 17 16:37:53 2014 +0100 # Node ID ca1e190d841c2a2ba394e7e8564fdc8337a1d782 # Parent c20c6a1eb13ca71379bea94c63c42a62dd313633 8035105: Detect compression loops in the JNDI DNS client diff --git a/src/share/classes/com/sun/jndi/dns/DnsClient.java b/src/share/classes/com/sun/jndi/dns/DnsClient.java --- a/src/share/classes/com/sun/jndi/dns/DnsClient.java +++ b/src/share/classes/com/sun/jndi/dns/DnsClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -411,8 +411,7 @@ udpSocket.receive(ipkt); long end = System.currentTimeMillis(); - byte[] data = new byte[ipkt.getLength()]; - data = ipkt.getData(); + byte[] data = ipkt.getData(); if (isMatchResponse(data, xid)) { return data; } diff --git a/src/share/classes/com/sun/jndi/dns/ResourceRecord.java b/src/share/classes/com/sun/jndi/dns/ResourceRecord.java --- a/src/share/classes/com/sun/jndi/dns/ResourceRecord.java +++ b/src/share/classes/com/sun/jndi/dns/ResourceRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2002, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,8 +25,13 @@ package com.sun.jndi.dns; +import javax.naming.CommunicationException; import javax.naming.InvalidNameException; +import java.io.IOException; + +import java.nio.charset.StandardCharsets; + /** * The ResourceRecord class represents a DNS resource record. @@ -84,6 +89,11 @@ null, "IN", null, null, "HS" }; + /* + * Maximum number of compression references in labels. + * Used to detect compression loops. + */ + private static final int MAXIMUM_COMPRESSION_REFERENCES = 16; byte[] msg; // DNS message int msgLen; // msg size (in octets) @@ -110,12 +120,12 @@ * false, the rdata is not decoded (and getRdata() will return null) * unless this is an SOA record. * - * @throws InvalidNameException if a decoded domain name isn't valid. + * @throws CommunicationException if a decoded domain name isn't valid. * @throws ArrayIndexOutOfBoundsException given certain other corrupt data. */ ResourceRecord(byte[] msg, int msgLen, int offset, boolean qSection, boolean decodeRdata) - throws InvalidNameException { + throws CommunicationException { this.msg = msg; this.msgLen = msgLen; @@ -235,7 +245,7 @@ * Decodes the binary format of the RR. * May throw ArrayIndexOutOfBoundsException given corrupt data. */ - private void decode(boolean decodeRdata) throws InvalidNameException { + private void decode(boolean decodeRdata) throws CommunicationException { int pos = offset; // index of next unread octet name = new DnsName(); // NAME @@ -316,7 +326,7 @@ /* * Returns the name encoded at msg[pos], including the root label. */ - private DnsName decodeName(int pos) throws InvalidNameException { + private DnsName decodeName(int pos) throws CommunicationException { DnsName n = new DnsName(); decodeName(pos, n); return n; @@ -326,22 +336,39 @@ * Prepends to "n" the domain name encoded at msg[pos], including the root * label. Returns the index into "msg" following the name. */ - private int decodeName(int pos, DnsName n) throws InvalidNameException { - if (msg[pos] == 0) { // end of name - n.add(0, ""); - return (pos + 1); - } else if ((msg[pos] & 0xC0) != 0) { // name compression - decodeName(getUShort(pos) & 0x3FFF, n); - return (pos + 2); - } else { // append a label - int len = msg[pos++]; - try { - n.add(0, new String(msg, pos, len, "ISO-8859-1")); - } catch (java.io.UnsupportedEncodingException e) { - // assert false : "ISO-Latin-1 charset unavailable"; + private int decodeName(int pos, DnsName n) throws CommunicationException { + int endPos = -1; + int level = 0; + try { + while (true) { + if (level > MAXIMUM_COMPRESSION_REFERENCES) + throw new IOException("Too many compression references"); + int type = msg[pos] & 0xFF; + if (type == 0) { // end of name + ++pos; + n.add(0, ""); + break; + } else if (type <= 63) { // regular label + ++pos; + n.add(0, new String(msg, pos, type, + StandardCharsets.ISO_8859_1)); + pos += type; + } else if ((msg[pos] & 0xC0) == 0xC0) { // name compression + ++level; + endPos = pos + 2; + pos = getUShort(pos) & 0x3FFF; + } else + throw new IOException("Invalid label type: " + type); } - return decodeName(pos + len, n); + } catch (IOException | InvalidNameException e) { + CommunicationException ce =new CommunicationException( + "DNS error: malformed packet"); + ce.initCause(e); + throw ce; } + if (endPos == -1) + endPos = pos; + return endPos; } /* @@ -352,7 +379,7 @@ * The rdata of records with unknown type/class combinations is * returned in a newly-allocated byte array. */ - private Object decodeRdata(int pos) throws InvalidNameException { + private Object decodeRdata(int pos) throws CommunicationException { if (rrclass == CLASS_INTERNET) { switch (rrtype) { case TYPE_A: @@ -386,7 +413,7 @@ /* * Returns the rdata of an MX record that is encoded at msg[pos]. */ - private String decodeMx(int pos) throws InvalidNameException { + private String decodeMx(int pos) throws CommunicationException { int preference = getUShort(pos); pos += 2; DnsName name = decodeName(pos); @@ -396,7 +423,7 @@ /* * Returns the rdata of an SOA record that is encoded at msg[pos]. */ - private String decodeSoa(int pos) throws InvalidNameException { + private String decodeSoa(int pos) throws CommunicationException { DnsName mname = new DnsName(); pos = decodeName(pos, mname); DnsName rname = new DnsName(); @@ -421,7 +448,7 @@ * Returns the rdata of an SRV record that is encoded at msg[pos]. * See RFC 2782. */ - private String decodeSrv(int pos) throws InvalidNameException { + private String decodeSrv(int pos) throws CommunicationException { int priority = getUShort(pos); pos += 2; int weight = getUShort(pos); @@ -436,7 +463,7 @@ * Returns the rdata of an NAPTR record that is encoded at msg[pos]. * See RFC 2915. */ - private String decodeNaptr(int pos) throws InvalidNameException { + private String decodeNaptr(int pos) throws CommunicationException { int order = getUShort(pos); pos += 2; int preference = getUShort(pos); diff --git a/test/com/sun/jndi/dns/Parser.java b/test/com/sun/jndi/dns/Parser.java new file mode 100644 --- /dev/null +++ b/test/com/sun/jndi/dns/Parser.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2014, Red Hat, Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8035105 + * @summary DNS resource record parsing + */ + +import com.sun.jndi.dns.ResourceRecord; +import javax.naming.CommunicationException; +import javax.naming.InvalidNameException;; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import java.io.IOException; + +import static java.nio.charset.StandardCharsets.ISO_8859_1; + +public class Parser { + static Constructor rrConstructor; + static { + try { + rrConstructor = ResourceRecord.class.getDeclaredConstructor( + byte[].class, int.class, int.class, boolean.class, + boolean.class); + rrConstructor.setAccessible(true); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + static ResourceRecord parse(String data, int offset, boolean qSection) + throws Throwable { + byte[] bytes = data.getBytes(ISO_8859_1); + try { + return rrConstructor.newInstance( + bytes, bytes.length, offset, qSection, !qSection); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + public static void main(String[] args) throws Throwable { + ResourceRecord rr; + + rr = parse("\003www\007example\003com\000\000\002\000\001", + 0, true); + if (!rr.getName().toString().equals("www.example.com.")) + throw new AssertionError(rr.getName().toString()); + if (rr.getRrclass() != 1) + throw new AssertionError("RCLASS: " + rr.getRrclass()); + if (rr.getType() != 2) + throw new AssertionError("RTYPE: " + rr.getType()); + + String longLabel = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; + + rr = parse("\077" + longLabel + "\077" + longLabel + + "\077" + longLabel + "\061" + longLabel.substring(0, 49) + + "\007example\003com\000\000\002\000\001", + 0, true); + if (!rr.getName().toString().equals(longLabel + + '.' + longLabel + '.' + longLabel + + '.' + longLabel.substring(0, 49) + ".example.com.")) + throw new AssertionError(rr.getName().toString()); + if (rr.getRrclass() != 1) + throw new AssertionError("RCLASS: " + rr.getRrclass()); + if (rr.getType() != 2) + throw new AssertionError("RTYPE: " + rr.getType()); + + rr = parse("1-2-3-4-5-6-" + + "\003www\007example\003com\000\000\002\000\001" + + "\300\014\000\002\000\001\000\001\121\200" + + "\000\005\002ns\300\020", + 33, false); + if (!rr.getName().toString().equals("www.example.com.")) + throw new AssertionError(rr.getName().toString()); + if (rr.getRrclass() != 1) + throw new AssertionError("RCLASS: " + rr.getRrclass()); + if (rr.getType() != 2) + throw new AssertionError("RTYPE: " + rr.getType()); + if (!rr.getRdata().toString().equals("ns.example.com.")) + throw new AssertionError("RDATA: " + rr.getRdata()); + + try { + parse("1-2-3-4-5-6-" + + "\003www\007example\003com\000\000\002\000\001" + + "\300\014\000\002\000\001\300\051\300\047" + + "\000\005\002ns\300\051", + 33, false); + throw new AssertionError(); + } catch (CommunicationException e) { + if (!e.getMessage().equals("DNS error: malformed packet") + || e.getCause().getClass() != IOException.class + || !e.getCause().getMessage().equals( + "Too many compression references")) + throw e; + } + + try { + String longLabel62 = "\076" + longLabel.substring(1); + parse(longLabel62 + longLabel62 + longLabel62 + longLabel62 + + "\002XX\000\000\002\000\001", 0, true); + throw new AssertionError(); + } catch (CommunicationException e) { + if (!e.getMessage().equals("DNS error: malformed packet") + || e.getCause().getClass() != InvalidNameException.class + || !e.getCause().getMessage().equals("Name too long")) + throw e; + } + try { + parse("\100Y" + longLabel + "\000\000\002\000\001", 0, true); + throw new AssertionError(); + } catch (CommunicationException e) { + if (!e.getMessage().equals("DNS error: malformed packet") + || e.getCause().getClass() != IOException.class + || !e.getCause().getMessage().equals("Invalid label type: 64")) + throw e; + } + } +}