Hi folks,
Hi Max,

please assess the following bug I have found in Java 11+, it does not exist
in Java 8. I have tried the following most versions on Azul Zulu/
OpenJDK: 8, 11, 17, 21, 24 on multiple platforms. Searched JBS as well,
nothing found.

Consider the following code:
public static void main(String[] args) throws NamingException {
        DirContextSource dirSource = new 
DirContextSource.Builder("ldaps://ad001.siemens.net").debug(true).version(3)
                        
.readTimeout(1000).referral("throw").derefAliases("never")
                        
.additionalProperty("net.sf.michaelo.activedirectory.readTimeout", "1000")
                        
.gssApiAuth("com.sun.security.jgss.initiate").qop("auth,auth-int").mutualAuth().connectTimeout(1000).build();

        DirContext dirContext = dirSource.getDirContext();
        SearchControls ctls = new SearchControls(SearchControls.OBJECT_SCOPE, 
0, 0, args, false, false);

        try {
                NamingEnumeration<SearchResult> search = dirContext.search("", 
"(objectClass=*)", ctls);
                while (search.hasMore()) {
                        SearchResult res = search.next();
                        Attributes attrs = res.getAttributes();
                        for (String arg : args) {
                                Attribute attr = attrs.get(arg);
                                if (attr != null) {
                                        System.out.println(arg + ":");
                                        NamingEnumeration<?> all = 
attr.getAll();

                                        while (all.hasMore()) {
                                                System.out.println("    " + 
all.next());
                                        }
                                }
                        }
                }
        } catch (LdapReferralException e) {
                ...
        }
        dirContext.close();
}
Please disregard DirContextSource, it is my thin OSS fluent wrapper
around InitialDirContext [1].

Create a TLS connection to Active Directory, bind with SASL GSSAPI and
require SASL integrity. Now, this is thrown by Java 8:
Exception in thread "main" javax.naming.NamingException: LDAP
connection has been closed; remaining name '' at
com.sun.jndi.ldap.LdapRequest.getReplyBer(LdapRequest.java:133) at
com.sun.jndi.ldap.Connection.readReply(Connection.java:469) at
com.sun.jndi.ldap.LdapClient.getSearchReply(LdapClient.java:638) at
com.sun.jndi.ldap.LdapClient.search(LdapClient.java:561) at
com.sun.jndi.ldap.LdapCtx.doSearch(LdapCtx.java:2014) at
com.sun.jndi.ldap.LdapCtx.searchAux(LdapCtx.java:1873) at
com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1798) at
com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(ComponentDirContext.java:392)
 at
com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:358)
 at
com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:341)
 at
javax.naming.directory.InitialDirContext.search(InitialDirContext.java:267)
 at DirTest.main(DirTest.java:24)

The reason for the closure I will describe later.

Now with Java 11+ I see:
Exception in thread "main" java.lang.NullPointerException: Cannot
invoke "org.ietf.jgss.GSSContext.wrap(byte[], int, int,
org.ietf.jgss.MessageProp)" because "this.secCtx" is null at
jdk.security.jgss/
com.sun.security.sasl.gsskerb.GssKrb5Base.wrap(GssKrb5Base.java:140) at java.naming/
com.sun.jndi.ldap.sasl.SaslOutputStream.write(SaslOutputStream.java:89)
 at java.naming/
com.sun.jndi.ldap.Connection.abandonRequest(Connection.java:582) at
java.naming/
com.sun.jndi.ldap.Connection.readReply(Connection.java:472) at
java.naming/
com.sun.jndi.ldap.LdapClient.getSearchReply(LdapClient.java:664) at
java.naming/com.sun.jndi.ldap.LdapClient.search(LdapClient.java:587) at java.naming/com.sun.jndi.ldap.LdapCtx.doSearch(LdapCtx.java:2015) at java.naming/
com.sun.jndi.ldap.LdapCtx.searchAux(LdapCtx.java:1874) at
java.naming/com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1799) at
java.naming/
com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(ComponentDirContext.java:392)
 at java.naming/
com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:358)
 at java.naming/
com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:341)
 at java.naming/
javax.naming.directory.InitialDirContext.search(InitialDirContext.java:346)
 at DirTest.main(DirTest.java:24)

So the bug seems to have surfaced somewhere between 8 and 11. Active
Directory does not support any SASL QOP (but auth) over a TLS connection [2].
Digging through RFCs [3], [4], [5] it seems to me that Active Directory
clearly violates those. What I see in the decrypted Wireshark traffic is
the following:
* SASL Bind completed
* GSS Wrap happens to negotiate the QOP, server says: 07 A0 00 00
* SASL client matches qop-int and wraps streams into SASL streams
* SearchRequest with SASL integrity is issued
* Server sends ExtendedResponse: 00000057: LdapErr: DSID-0C0C0095,
comment: Error decoding ldap message, data 0, v4563,
1.3.6.1.4.1.1466.20036 which is [6].
* Server completes with TCP RST, ACK. No TLS close_notify or similar

Of course, the client tries to decode the ExtendedResponse as a SASL
buffer and reads the first four bytes 30 84 00 00 as the SASL buffer
length and signals a swallowed exception in SaslInputStream.fill():
"java.io.IOException: 813957120exceeds the negotiated receive buffer
size limit:65536" (note the missing space) instead of the start of a
SEQUENCE caught by Connection. The forthcoming Unbind goes into oblivion.

This is how ldapsearch(1) handles it:
$ ldapsearch -O minssf=1 -d 10 -H ldaps://ad001.siemens.net -s base -Y GSSAPI namingContexts ...
sb_sasl_generic_pkt_length: received illegal packet length of 813957120 bytes
sasl_generic_read: want=16, got=16
  0000:  00 7e 02 01 00 78 84 00  00 00 5d 0a 01 02 04 00   .~...x....].....
sb_sasl_cyrus_decode: failed to decode packet: generic failure
sb_sasl_generic_read: failed to decode packet
ldap_read: want=8 error=Input/output error
ber_get_next failed, errno=5.

# numResponses: 0
ldap_result: Can't contact LDAP server (-1)
tls_write: want=31, written=31
  0000:  15 03 03 00 1a df 9c b5  96 48 55 9d 1e 65 dc eb   .........HU..e..
  0010:  a1 ca 00 a5 96 10 be 5c  23 32 b9 90 68 c4 04      .......\#2..h..

libldap does properly signal in the invalid buffer size and shows the connection closure.

I do understand that there are several issues here and AD is clearly misbehaving (it should be 01 A0 00 00) and Cyrus SASL does have code to drop internal security if outer security is good enough [7], but least Java SASL GSSAPI should properly dispose the GSS security context and release the SASL client before it says that the connection has been closed.

All trace output, decrypted PCAPs are available privately through email. If you need anything else, let me know.

Note: Just using "auth" does work, of course.

Best regards,

Michael

[1] https://github.com/michael-o/dirctxsrc
[2] https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-
adts/284923c1-6a5b-4510-b97a-631963c0c3bd.
[3] https://datatracker.ietf.org/doc/html/rfc4513#section-5.2.1.6
[4] https://datatracker.ietf.org/doc/html/rfc4752#section-3.3
[5] https://datatracker.ietf.org/doc/html/rfc4422#section-4 point b)
[6] https://datatracker.ietf.org/doc/html/rfc4511#section-4.4.1
[7] https://github.com/cyrusimap/cyrus-sasl/blob/e256dd0cc61d60cbb905b601241077f0a2ba0907/plugins/gssapi.c#L1545-L1556

Reply via email to