-
Bug
-
Resolution: Fixed
-
P2
-
8, 10, 11
-
b11
-
x86_64
-
linux
-
Not verified
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8214386 | 11.0.3-oracle | Vyom Tewari | P2 | Resolved | Fixed | master |
JDK-8217142 | 11.0.3 | Vyom Tewari | P2 | Resolved | Fixed | master |
JDK-8214734 | 11.0.2 | Vyom Tewari | P2 | Resolved | Fixed | b06 |
JDK-8218535 | openjdk8u212 | Robert Mckenna | P2 | Resolved | Fixed | b01 |
JDK-8214667 | 8u211 | Robert Mckenna | P2 | Resolved | Fixed | b01 |
JDK-8211789 | 8u202 | Robert Mckenna | P2 | Closed | Fixed | b01 |
JDK-8214872 | 8u201 | Robert Mckenna | P2 | Closed | Fixed | b07 |
JDK-8221013 | emb-8u211 | Robert Mckenna | P2 | Resolved | Fixed | master |
JDK-8216918 | emb-8u201 | Robert Mckenna | P2 | Resolved | Fixed | b07 |
JDK-8219293 | openjdk7u | Robert Mckenna | P2 | Resolved | Fixed | master |
Reproduced on two separate systems:
Ubuntu 16.04.4 LTS -:
* Java 1.8.0_171-8u171
* Java 18.3 (build 10.0.1+10)
Mac OS X 10.13.5
* Java 1.8.0_171-b1
A DESCRIPTION OF THE PROBLEM :
If a connection has already been established and then the LDAP directory server sends an (unsolicited) "Notice of Disconnection", the client's processing of this LDAP message can race with an application thread calling new InitialDirContext() to authenticate user credentials (i.e.bind). There is an unlucky timing which leads to a NullPointerException, which is in contravention of InitialDirContext API.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the program provided below.
The program (Main):
1) starts an embedded ldap server which additionally produces "Notice of Disconnection" LDAP messages after each successful bind.
2) starts a client using the java.naming API which authenticates (binds) in a loop
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Program should run indefinetly without failure.
ACTUAL -
Program fails with the following NullPointerException:
java.lang.NullPointerException
at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:300)
at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2791)
at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:319)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:192)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:210)
at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:153)
at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:83)
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313)
at javax.naming.InitialContext.init(InitialContext.java:244)
at javax.naming.InitialContext.<init>(InitialContext.java:216)
at javax.naming.directory.InitialDirContext.<init>(InitialDirContext.java:101)
at LdapConnector.bind(LdapConnector.java:48)
at Main.main(Main.java:42)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:282)
at java.lang.Thread.run(Thread.java:748)
---------- BEGIN SOURCE ----------
The program is available here: https://github.com/k-wall/ldapinitialdircontextnpe
The program comprises three classes:
Main:
====
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main
{
private static final Logger LOG = LoggerFactory.getLogger(Main.class);
public static void main(String[] argv) throws Exception
{
int successfulAuthCount = 0;
try(EmbeddedLdapServer directory = new EmbeddedLdapServer("o=sevenSeas"))
{
directory.start();
directory.applyLdif("test.ldif");
LdapConnector ldapConnector = new LdapConnector("localhost", directory.getBoundPort());
String principal = "cn=Horatio Hornblower,ou=people,o=sevenSeas";
String secret = "secret";
while(true)
{
ldapConnector.bind(principal, secret);
successfulAuthCount++;
if (successfulAuthCount % 100 == 0)
{
LOG.info("Successfully LDAP binds {}", successfulAuthCount);
}
}
}
finally
{
LOG.info("Number of successful LDAP binds {}", successfulAuthCount);
}
}
}
LdapConnector:
=============
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.InitialDirContext;
public class LdapConnector {
private final String _host;
private final int _port;
LdapConnector(final String host, final int port) {
_host = host;
_port = port;
}
public void bind(final String principal, final String password) throws NamingException
{
Hashtable<String, Object> env = new Hashtable<>();
env.put("com.sun.jndi.ldap.connect.pool", "true");
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, String.format("ldap://%s:%d/", _host, _port));
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, principal);
env.put(Context.SECURITY_CREDENTIALS, password);
//env.put("com.sun.jndi.ldap.trace.ber", System.err);
InitialDirContext initialDirContext = new InitialDirContext(env);
initialDirContext.close();
}
}
import java.io.File;
import java.net.InetSocketAddress;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import org.apache.directory.api.ldap.model.message.BindResponse;
import org.apache.directory.api.ldap.model.message.extended.NoticeOfDisconnect;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.api.InstanceLayout;
import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory;
import org.apache.directory.server.core.partition.impl.avl.AvlPartition;
import org.apache.directory.server.ldap.LdapServer;
import org.apache.directory.server.ldap.LdapSession;
import org.apache.directory.server.ldap.LdapSessionManager;
import org.apache.directory.server.ldap.handlers.request.BindRequestHandler;
import org.apache.directory.server.ldap.handlers.response.BindResponseHandler;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
import org.apache.mina.core.session.IoSession;
public class EmbeddedLdapServer implements AutoCloseable
{
private static final String INSTANCE_NAME = "sevenSeas";
private DirectoryService _directoryService;
private LdapServer _ldapService;
private int _boundPort;
public EmbeddedLdapServer(final String baseDn) throws Exception
{
init(baseDn);
}
private void init(final String baseDn) throws Exception
{
DefaultDirectoryServiceFactory factory = new DefaultDirectoryServiceFactory();
factory.init(INSTANCE_NAME);
_directoryService = factory.getDirectoryService();
_directoryService.getChangeLog().setEnabled(false);
File dir = Files.createTempDirectory(INSTANCE_NAME).toFile();
InstanceLayout il = new InstanceLayout(dir);
_directoryService.setInstanceLayout(il);
AvlPartition partition = new AvlPartition(_directoryService.getSchemaManager());
partition.setId(INSTANCE_NAME);
partition.setSuffixDn(new Dn(_directoryService.getSchemaManager(), baseDn));
_directoryService.addPartition(partition);
_ldapService = new LdapServer();
_ldapService.setTransports(new TcpTransport(0));
_ldapService.setDirectoryService(_directoryService);
_ldapService.setBindHandlers(new BindRequestHandler(), new BindResponseHandler()
{
@Override
public void handle(final LdapSession ldapSession, final BindResponse bindResponse)
{
// Produce a 1.3.6.1.4.1.1466.20036 (notice of disconnect).
final IoSession ioSession = ldapSession.getIoSession();
final LdapSessionManager ldapSessionManager = ldapSession.getLdapServer().getLdapSessionManager();
ioSession.write(NoticeOfDisconnect.UNAVAILABLE);
ldapSessionManager.removeLdapSession(ioSession);
}
});
}
public int getBoundPort()
{
return _boundPort;
}
public void start() throws Exception
{
if (_ldapService.isStarted())
{
throw new IllegalStateException("Service already running");
}
_directoryService.startup();
_ldapService.start();
_boundPort = ((InetSocketAddress) _ldapService.getTransports()[0].getAcceptor().getLocalAddress()).getPort();
}
public void applyLdif(final String ldif) throws Exception
{
File tmp = File.createTempFile("test", ".ldif");
Files.copy(Thread.currentThread().getContextClassLoader().getResourceAsStream(ldif), tmp.toPath(), StandardCopyOption.REPLACE_EXISTING);
LdifFileLoader ldifFileLoader = new LdifFileLoader(_directoryService.getAdminSession(), tmp.getAbsolutePath());
int rv = ldifFileLoader.execute();
if (rv == 0)
{
throw new IllegalStateException(String.format("Load no entries from LDIF resource : '%s'", ldif));
}
}
@Override
public void close() throws Exception
{
if (!_ldapService.isStarted())
{
throw new IllegalStateException("Service is not running");
}
try
{
_ldapService.stop();
}
finally
{
_directoryService.shutdown();
}
}
}
test.ldif:
======
version: 1
dn: o=sevenSeas
objectClass: organization
objectClass: top
o: sevenSeas
dn: ou=people,o=sevenSeas
objectClass: organizationalUnit
objectClass: top
description: Contains entries which describe persons (seamen)
ou: people
dn: cn=Horatio Hornblower,ou=people,o=sevenSeas
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: top
cn: Horatio Hornblower
description: Capt. Horatio Hornblower, R.N
givenName: Horatio
sn: Hornblower
uid: hhornblo
mail: hhornblo@royalnavy.mod.uk
userPassword: secret
Maven dependencies:
<dependencies>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-all</artifactId>
<version>${apache.ds.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
None found. Restart of the application appears to be the only recourse.
FREQUENCY : often
- backported by
-
JDK-8214386 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Resolved
-
JDK-8214667 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Resolved
-
JDK-8214734 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Resolved
-
JDK-8216918 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Resolved
-
JDK-8217142 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Resolved
-
JDK-8218535 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Resolved
-
JDK-8219293 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Resolved
-
JDK-8221013 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Resolved
-
JDK-8211789 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Closed
-
JDK-8214872 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Closed
- relates to
-
JDK-8210695 Create test to cover JDK-8205330 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
- Closed