# HG changeset patch # User xuelei # Date 1267075960 -28800 # Node ID 6bee79de5db6eaa9d9b2423a7b6b4dc52d2ad61c # Parent 81902a400bbf15af79b334dbecbd5f60b2d3285a 6916202: More cases of invalid ldap filters accepted and processed Reviewed-by: vinnie, weijun diff -r 81902a400bbf -r 6bee79de5db6 jdk/src/share/classes/com/sun/jndi/ldap/Filter.java --- a/jdk/src/share/classes/com/sun/jndi/ldap/Filter.java Wed Feb 24 10:48:18 2010 -0800 +++ b/jdk/src/share/classes/com/sun/jndi/ldap/Filter.java Thu Feb 25 13:32:40 2010 +0800 @@ -1,5 +1,5 @@ /* - * Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 1999-2010 Sun Microsystems, Inc. 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 @@ -33,6 +33,7 @@ /** * LDAP (RFC-1960) and LDAPv3 (RFC-2254) search filters. * + * @author Xuelei Fan * @author Vincent Ryan * @author Jagane Sundar * @author Rosanna Lee @@ -258,7 +259,7 @@ byte[] answer = new byte[j]; System.arraycopy(tbuf, 0, answer, 0, j); if (dbg) { - Ber.dumpBER(System.err, null, answer, 0, j); + Ber.dumpBER(System.err, "", answer, 0, j); } return answer; } @@ -330,7 +331,7 @@ } - valueStart = eq + 1; // value starts after equal sign + valueStart = eq + 1; // value starts after equal sign valueEnd = filtEnd; typeStart = filtStart; // beginning of string @@ -355,20 +356,199 @@ break; default: typeEnd = eq; + //initializing ftype to make the compiler happy + ftype = 0x00; + break; + } + + if (dbg) { + System.err.println("type: " + typeStart + ", " + typeEnd); + System.err.println("value: " + valueStart + ", " + valueEnd); + } + + // check validity of type + // + // RFC4512 defines the type as the following ABNF: + // attr = attributedescription + // attributedescription = attributetype options + // attributetype = oid + // oid = descr / numericoid + // descr = keystring + // keystring = leadkeychar *keychar + // leadkeychar = ALPHA + // keychar = ALPHA / DIGIT / HYPHEN + // numericoid = number 1*( DOT number ) + // number = DIGIT / ( LDIGIT 1*DIGIT ) + // options = *( SEMI option ) + // option = 1*keychar + // + // And RFC4515 defines the extensible type as the following ABNF: + // attr [dnattrs] [matchingrule] / [dnattrs] matchingrule + int optionsStart = -1; + int extensibleStart = -1; + if ((filter[typeStart] >= '0' && filter[typeStart] <= '9') || + (filter[typeStart] >= 'A' && filter[typeStart] <= 'Z') || + (filter[typeStart] >= 'a' && filter[typeStart] <= 'z')) { + + boolean isNumericOid = + filter[typeStart] >= '0' && filter[typeStart] <= '9'; + for (int i = typeStart + 1; i < typeEnd; i++) { + // ';' is an indicator of attribute options + if (filter[i] == ';') { + if (isNumericOid && filter[i - 1] == '.') { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + + // attribute options + optionsStart = i; + break; + } + + // ':' is an indicator of extensible rules + if (filter[i] == ':' && ftype == LDAP_FILTER_EXT) { + if (isNumericOid && filter[i - 1] == '.') { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + + // extensible matching + extensibleStart = i; + break; + } + + if (isNumericOid) { + // numeric object identifier + if ((filter[i] == '.' && filter[i - 1] == '.') || + (filter[i] != '.' && + !(filter[i] >= '0' && filter[i] <= '9'))) { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + } else { + // descriptor + if (filter[i] != '-' && + !(filter[i] >= '0' && filter[i] <= '9') && + !(filter[i] >= 'A' && filter[i] <= 'Z') && + !(filter[i] >= 'a' && filter[i] <= 'z')) { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + } + } + } else if (ftype == LDAP_FILTER_EXT && filter[typeStart] == ':') { + // extensible matching + extensibleStart = typeStart; + } else { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + + // check attribute options + if (optionsStart > 0) { + for (int i = optionsStart + 1; i < typeEnd; i++) { + if (filter[i] == ';') { + if (filter[i - 1] == ';') { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + continue; + } + + // ':' is an indicator of extensible rules + if (filter[i] == ':' && ftype == LDAP_FILTER_EXT) { + if (filter[i - 1] == ';') { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + + // extensible matching + extensibleStart = i; + break; + } + + if (filter[i] != '-' && + !(filter[i] >= '0' && filter[i] <= '9') && + !(filter[i] >= 'A' && filter[i] <= 'Z') && + !(filter[i] >= 'a' && filter[i] <= 'z')) { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + } + } + + // check extensible matching + if (extensibleStart > 0) { + boolean isMatchingRule = false; + for (int i = extensibleStart + 1; i < typeEnd; i++) { + if (filter[i] == ':') { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } else if ((filter[i] >= '0' && filter[i] <= '9') || + (filter[i] >= 'A' && filter[i] <= 'Z') || + (filter[i] >= 'a' && filter[i] <= 'z')) { + boolean isNumericOid = filter[i] >= '0' && filter[i] <= '9'; + i++; + for (int j = i; j < typeEnd; j++, i++) { + // allows no more than two extensible rules + if (filter[j] == ':') { + if (isMatchingRule) { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + if (isNumericOid && filter[j - 1] == '.') { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + + isMatchingRule = true; + break; + } + + if (isNumericOid) { + // numeric object identifier + if ((filter[j] == '.' && filter[j - 1] == '.') || + (filter[j] != '.' && + !(filter[j] >= '0' && filter[j] <= '9'))) { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + } else { + // descriptor + if (filter[j] != '-' && + !(filter[j] >= '0' && filter[j] <= '9') && + !(filter[j] >= 'A' && filter[j] <= 'Z') && + !(filter[j] >= 'a' && filter[j] <= 'z')) { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + } + } + } else { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + } + } + + // ensure the latest byte is not isolated + if (filter[typeEnd - 1] == '.' || filter[typeEnd - 1] == ';' || + filter[typeEnd - 1] == ':') { + throw new InvalidSearchFilterException( + "invalid attribute description"); + } + + if (typeEnd == eq) { // filter type is of "equal" if (findUnescaped(filter, '*', valueStart, valueEnd) == -1) { ftype = LDAP_FILTER_EQUALITY; - } else if (filter[valueStart] == '*' && valueStart == (valueEnd - 1)) { + } else if (filter[valueStart] == '*' && + valueStart == (valueEnd - 1)) { ftype = LDAP_FILTER_PRESENT; } else { encodeSubstringFilter(ber, filter, typeStart, typeEnd, valueStart, valueEnd); return; } - break; - } - if (dbg) { - System.err.println("type: " + typeStart + ", " + typeEnd); - System.err.println("value: " + valueStart + ", " + valueEnd); } if (ftype == LDAP_FILTER_PRESENT) { @@ -379,7 +559,7 @@ } else { ber.beginSeq(ftype); ber.encodeOctetString(filter, Ber.ASN_OCTET_STR, - typeStart, typeEnd-typeStart); + typeStart, typeEnd - typeStart); ber.encodeOctetString( unescapeFilterValue(filter, valueStart, valueEnd), Ber.ASN_OCTET_STR); @@ -623,7 +803,8 @@ // //////////////////////////////////////////////////////////////////////////// - private static final boolean dbg = false; + // private static final boolean dbg = false; + private static final boolean dbg = true; private static int dbgIndent = 0; private static void dprint(String msg) { diff -r 81902a400bbf -r 6bee79de5db6 jdk/test/com/sun/jndi/ldap/InvalidLdapFilters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/com/sun/jndi/ldap/InvalidLdapFilters.java Thu Feb 25 13:32:40 2010 +0800 @@ -0,0 +1,311 @@ +/* + * Copyright 2010 Sun Microsystems, Inc. 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 + * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/** + * @test + * @bug 6916202 + * @summary More cases of invalid ldap filters accepted and processed + * @run main/othervm InvalidLdapFilters valid (cn=Babs) + * @run main/othervm InvalidLdapFilters valid (&(cn=Bob)) + * @run main/othervm InvalidLdapFilters valid (&(objectClass=*)(uid=*)) + * @run main/othervm InvalidLdapFilters valid (|(cn=Bob)) + * @run main/othervm InvalidLdapFilters valid (|(objectClass=*)(uid=*)) + * @run main/othervm InvalidLdapFilters valid (!(cn=Tim)) + * @run main/othervm InvalidLdapFilters valid (!(!(cn=Tim))) + * @run main/othervm InvalidLdapFilters valid (!(&(objectClass=*)(uid=*))) + * @run main/othervm InvalidLdapFilters valid (!(|(objectClass=*)(uid=*))) + * @run main/othervm InvalidLdapFilters valid (o=univ*of*mich*) + * @run main/othervm InvalidLdapFilters valid (seeAlso=) + * @run main/othervm InvalidLdapFilters valid (cn:caseExactMatch:=Flintstone) + * @run main/othervm InvalidLdapFilters valid (cn:=Betty) + * @run main/othervm InvalidLdapFilters valid (sn:dn:2.4.6.8.10:=Barney) + * @run main/othervm InvalidLdapFilters valid (o:dn:=Ace) + * @run main/othervm InvalidLdapFilters valid (:1.2.3:=Wilma) + * @run main/othervm InvalidLdapFilters valid (:DN:2.4.6.8.10:=Dino) + * @run main/othervm InvalidLdapFilters valid (1.2.3=abc) + * @run main/othervm InvalidLdapFilters valid (cn;lang-de;lang-en=abc) + * @run main/othervm InvalidLdapFilters valid (owner=abc) + * @run main/othervm InvalidLdapFilters valid (sn;lang-en:dn:2.4.6.8.10:=Barney) + * @run main/othervm InvalidLdapFilters valid + (&(objectClass=Person)(|(sn=Jensen)(cn=Bab*))) + * @run main/othervm InvalidLdapFilters invalid "(&(cn=Robert Dean)))" + * @run main/othervm InvalidLdapFilters invalid (&|(cn=Bob)) + * @run main/othervm InvalidLdapFilters invalid (&&(cn=Bob)) + * @run main/othervm InvalidLdapFilters invalid (|&(cn=Bob)) + * @run main/othervm InvalidLdapFilters invalid (||(cn=Bob)) + * @run main/othervm InvalidLdapFilters invalid (:1.2.:=Wilma) + * @run main/othervm InvalidLdapFilters invalid (::DN:2.4.6.8.10:=Dino) + * @run main/othervm InvalidLdapFilters invalid (:DN::2.4.6.8.10:=Dino) + * @run main/othervm InvalidLdapFilters invalid (:DN:2.4.6.8.10::=Dino) + * @run main/othervm InvalidLdapFilters invalid (:DN:2.4.6..8.10:=Dino) + * @run main/othervm InvalidLdapFilters invalid (:DN:2.4.6.8.:=Dino) + * @run main/othervm InvalidLdapFilters invalid (1.2.;::=abc) + * @run main/othervm InvalidLdapFilters invalid (1.2.3;::=abc) + * @run main/othervm InvalidLdapFilters invalid (1.2.3;x;=abc) + * @run main/othervm InvalidLdapFilters invalid (1.2.3:x::=abc) + * @run main/othervm InvalidLdapFilters invalid (1.2.3:x=abc) + * @run main/othervm InvalidLdapFilters invalid (sn;:dn:2.4.6.8.10:=Barney) + * @run main/othervm InvalidLdapFilters invalid "\"((objectClass=*)&(uid=*))\"" + * @run main/othervm InvalidLdapFilters invalid "&(objectClass=*)(uid=*)" + * @run main/othervm InvalidLdapFilters invalid "(:DN:2.4.6.8.10:cn:=Dino)" + * @run main/othervm InvalidLdapFilters invalid "(:DN:2.4.6.8.10:cn=Dino)" + * @run main/othervm InvalidLdapFilters invalid + "((objectCategory=person)(cn=u)(!(cn=u2*)))" + * @run main/othervm InvalidLdapFilters invalid + "((&(objectClass=user)(cn=andy*)(cn=steve*)(cn=bob*)))" + * + * @author Xuelei Fan + */ + +import java.io.*; +import javax.naming.*; +import javax.naming.directory.*; +import java.util.Properties; +import java.util.Hashtable; + +import java.net.Socket; +import java.net.ServerSocket; + +public class InvalidLdapFilters { + // Should we run the client or server in a separate thread? + // + // Both sides can throw exceptions, but do you have a preference + // as to which side should be the main thread. + static boolean separateServerThread = true; + + // use any free port by default + volatile int serverPort = 0; + + // Is the server ready to serve? + volatile static boolean serverReady = false; + + // Define the server side of the test. + // + // If the server prematurely exits, serverReady will be set to true + // to avoid infinite hangs. + void doServerSide() throws Exception { + ServerSocket serverSock = new ServerSocket(serverPort); + + // signal client, it's ready to accecpt connection + serverPort = serverSock.getLocalPort(); + serverReady = true; + + // accept a connection + Socket socket = serverSock.accept(); + System.out.println("Server: Connection accepted"); + + InputStream is = socket.getInputStream(); + OutputStream os = socket.getOutputStream(); + + // read the bindRequest + while (is.read() != -1) { + // ignore + is.skip(is.available()); + break; + } + + byte[] bindResponse = {0x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A, + 0x01, 0x00, 0x04, 0x00, 0x04, 0x00}; + // write bindResponse + os.write(bindResponse); + os.flush(); + + // ignore any more request. + while (is.read() != -1) { + // ignore + is.skip(is.available()); + } + + is.close(); + os.close(); + socket.close(); + serverSock.close(); + } + + // Define the client side of the test. + // + // If the server prematurely exits, serverReady will be set to true + // to avoid infinite hangs. + void doClientSide() throws Exception { + // Wait for server to get started. + while (!serverReady) { + Thread.sleep(50); + } + + // set up the environment for creating the initial context + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldap://localhost:" + serverPort); + env.put("com.sun.jndi.ldap.read.timeout", "1000"); + + // env.put(Context.SECURITY_AUTHENTICATION, "simple"); + // env.put(Context.SECURITY_PRINCIPAL,"cn=root"); + // env.put(Context.SECURITY_CREDENTIALS,"root"); + + // create initial context + DirContext context = new InitialDirContext(env); + + // searching + SearchControls scs = new SearchControls(); + scs.setSearchScope(SearchControls.SUBTREE_SCOPE); + + try { + NamingEnumeration answer = + context.search("o=sun,c=us", searchFilter, scs); + } catch (InvalidSearchFilterException isfe) { + if (filterIsValid) { + // unexpected filter exception. + throw new Exception("Unexpected ISFE", isfe); + } else { + // ignore, it is the expected filter exception. + System.out.println("Expected exception: " + isfe.getMessage()); + } + } catch (NamingException ne) { + // maybe a read timeout exception, as the server does not response. + if (filterIsValid) { + System.out.println("Expected exception: " + ne.getMessage()); + } else { + throw new Exception("Not an InvalidSearchFilterException", ne); + } + } + + context.close(); + } + + private static boolean filterIsValid; + private static String searchFilter; + + private static void parseArguments(String[] args) { + System.out.println("arguments length: " + args.length); + if (args[0].equals("valid")) { + filterIsValid = true; + } + + searchFilter = args[1]; + } + + /* + * ============================================================ + * The remainder is just support stuff + */ + + // client and server thread + Thread clientThread = null; + Thread serverThread = null; + + // client and server exceptions + volatile Exception serverException = null; + volatile Exception clientException = null; + + void startServer(boolean newThread) throws Exception { + if (newThread) { + serverThread = new Thread() { + public void run() { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Release the client, if not active already... + */ + System.err.println("Server died..."); + System.err.println(e); + serverReady = true; + serverException = e; + } + } + }; + serverThread.start(); + } else { + doServerSide(); + } + } + + void startClient(boolean newThread) throws Exception { + if (newThread) { + clientThread = new Thread() { + public void run() { + try { + doClientSide(); + } catch (Exception e) { + /* + * Our client thread just died. + */ + System.err.println("Client died..."); + clientException = e; + } + } + }; + clientThread.start(); + } else { + doClientSide(); + } + } + + // Primary constructor, used to drive remainder of the test. + InvalidLdapFilters() throws Exception { + if (separateServerThread) { + startServer(true); + startClient(false); + } else { + startClient(true); + startServer(false); + } + + /* + * Wait for other side to close down. + */ + if (separateServerThread) { + serverThread.join(); + } else { + clientThread.join(); + } + + /* + * When we get here, the test is pretty much over. + * + * If the main thread excepted, that propagates back + * immediately. If the other thread threw an exception, we + * should report back. + */ + if (serverException != null) { + System.out.print("Server Exception:"); + throw serverException; + } + if (clientException != null) { + System.out.print("Client Exception:"); + throw clientException; + } + } + + public static void main(String[] args) throws Exception { + // parse the customized arguments + parseArguments(args); + + // start the test + new InvalidLdapFilters(); + } + +}