Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8067755

AES/GCM NullPointerException from com.sun.crypto.provider.GCTR.reset

XMLWordPrintable

      FULL PRODUCT VERSION :
      java version "1.8.0_05"
      Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
      Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)

      and

      java version "1.8.0_25"
      Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
      Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

      and

      openjdk version "1.8.0-internal"
      OpenJDK Runtime Environment (build 1.8.0-internal-cellis_2014_01_30_19_42-b00)
      OpenJDK 64-Bit Server VM (build 25.0-b69, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Tested on multiple OpenSUSE 13.1 installations, bug is not OS related, rather a pure Java bug. OS is Linux x86_64 kernel 3.11.10

      EXTRA RELEVANT SYSTEM CONFIGURATION :
      Tested against Java 8 update 5, Java 8 update 25 and custom OpenJDK build

      A DESCRIPTION OF THE PROBLEM :
      When connecting to a HTTPS server using TLS 1.2 and the AES/GCM cipher I am seeing NullPointerExceptions being thrown from the internals of the AES GCM cipher.

          Caused by: java.lang.NullPointerException
              at java.lang.System.arraycopy(Native Method)
              at com.sun.crypto.provider.GCTR.reset(GCTR.java:125)
              at com.sun.crypto.provider.GCTR.doFinal(GCTR.java:116)
              at com.sun.crypto.provider.GaloisCounterMode.doLastBlock(GaloisCounterMode.java:343)
              at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:511)
              at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1023)
              at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:960)
              at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:479)
              at javax.crypto.CipherSpi.bufferCrypt(CipherSpi.java:830)
              at javax.crypto.CipherSpi.engineDoFinal(CipherSpi.java:730)
              at javax.crypto.Cipher.doFinal(Cipher.java:2416)
              at sun.security.ssl.CipherBox.decrypt(CipherBox.java:535)
              at sun.security.ssl.EngineInputRecord.decrypt(EngineInputRecord.java:216)
              at sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:968)
              at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:901)
              at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:775)
              at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
              at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:883)
              at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:828)
              at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:803)
              at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:226)

      I can reliable reproduce this error with JDK 8u25, JDK8u5 and OpenJDK 8 when connecting to one
      particular host.

      Debugging the issue, it seems that the restore() method o GCTR is called after a call to reset(). This enables the counter member variable to become null and cause the above error.

      It appears that the method doFinal in CipherCore calls save() and restore() to handle when the output buffer is smaller than the plain text. However in certain circumstances the GCTR.doFinal method is called from CipherCore.doFinal (via finalNoPadding). This leads to the sequence of reset() and restore() being called on GCTR resulting in a NullPointerException.

      My tracing shows the following sequence:

      CipherCore::DoFinal entered input=[B@3f10ad62, inputOffset=0, inputLen=29, output=[B@1c867062, outputOffset=0
      CipherCore::DoFinal outputCapacity=4096, estOutSize=4109
      CipherCore::DoFinal estimating output buffer size 4096 < 4109 => true, inputLen=29
      CipherCore::DoFinal saving
      GCTR::Save, counter=[B@6ace9e77, counterSave=null
      CipherCore::FinalNoPadding entered in=[B@7608b818, inOfs=0, out=[B@4cc96006, outOfs=0, len=45
      GCTR::Update: counter=[B@6ace9e77
      ... snip
      GCTR::Update: counter=[B@6ace9e77
      GCTR::DoFinal calling GCTR::Reset
      GCTR::Reset, counter=[B@6ace9e77, icb=[B@63e30c9d, counterSave=[B@7d401bb0
      GCTR::Update: counter=[B@4e3bb478
      GCTR::DoFinal calling GCTR::Reset
      GCTR::Reset, counter=[B@4e3bb478, icb=[B@49f524e9, counterSave=null
      CipherCore::FinalNoPadding exiting return=4109
      CipherCore::DoFinal checking output buffer size outputCapacity=4096, outLen=4109 ==> true
      CipherCore::DoFinal restoring to permit bigger buffer use: outputCapacity=4096, outLen=4109 ==> true
      GCTR::Restore, counter=[B@32094eae, counterSave=null
      GCTR::Restore restore called with null saved counter, reset will have been called before calling restore, not restoring counter!

      Patching the reset() and restore() methods of com.sun.crypto.provider.GCTR, to the following:

      /**
          * Resets the content of this object to when it's first constructed.
          */
      void reset() {
          this.counter = this.icb.clone();
          counterSave = null;
      }

      /**
          * Restores the content of this object to the previous saved one.
          */
      void restore() {
          /*
              * If there is no saved state, then don't restore
              *
              * There are times when restore() maybe called on a reset() object
              * without save() having been called, this is treated as a no-op.
              */
          if (this.counterSave != null)
              this.counter = this.counterSave;
      }

      Resolves any issues I see, to me I can't see that this small patch alters these methods beyond the implicit contract they offer.

      I've written up my finding on my blog: https://intrbiz.com/post/blog/development/java_8_aes_gcm_nullpointerexception





      REGRESSION. Last worked in version 7u72

      ADDITIONAL REGRESSION INFORMATION:
      AES/GCM support was added to Java 8. The bug seems present in all versions of Java 8. There is a regression in the sense that when connecting over TLS in prior releases, AES/GCM support did not exist and as such the error was not present.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      I can reliably reproduce the bug using the Netty non-blocking IO library when making a TLS connection.

      I have a test case for my Open Source application, which can be found here:

      https://github.com/intrbiz/bergamot/blob/master/bergamot-check-http/src/test/java/com/intrbiz/bergamot/check/http/GCMTest.java

      I've not been able to reproduce via the blocking SSLSocket API, only via Netty and the SSLEngine API. I've not had chance to write a test case using SSLEngine. Or been able to extract the state to create a dedicated test case for the AES/GCM cipher.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      A TLS 1.2 connection using AES/GCM should connect without receiving a NullPointerException whilst decrypting the content.
      ACTUAL -
      A TLS 1.2 connection using AES/GCM will intermittently fail with a NullPointerException whilst decrypting the content. I can reliably reproduce the fault when connecting to one particular host.

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      The following stack trace is raised by the Java SSL stack:

          Caused by: java.lang.NullPointerException
              at java.lang.System.arraycopy(Native Method)
              at com.sun.crypto.provider.GCTR.reset(GCTR.java:125)
              at com.sun.crypto.provider.GCTR.doFinal(GCTR.java:116)
              at com.sun.crypto.provider.GaloisCounterMode.doLastBlock(GaloisCounterMode.java:343)
              at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:511)
              at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1023)
              at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:960)
              at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:479)
              at javax.crypto.CipherSpi.bufferCrypt(CipherSpi.java:830)
              at javax.crypto.CipherSpi.engineDoFinal(CipherSpi.java:730)
              at javax.crypto.Cipher.doFinal(Cipher.java:2416)
              at sun.security.ssl.CipherBox.decrypt(CipherBox.java:535)
              at sun.security.ssl.EngineInputRecord.decrypt(EngineInputRecord.java:216)
              at sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:968)
              at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:901)
              at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:775)
              at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
              at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:883)
              at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:828)
              at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:803)
              at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:226)

      REPRODUCIBILITY :
      This bug can be reproduced often.

      ---------- BEGIN SOURCE ----------
      https://github.com/intrbiz/bergamot/blob/master/bergamot-check-http/src/test/java/com/intrbiz/bergamot/check/http/GCMTest.java

      I also have patched GCTR and CipherCore classes with additional trace logging in which helped me debug this issue.
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      The only work around I've found is to disable the AES/GCM cipher suites when setting up the SSLEngine. However this is a rather suboptimal work around.

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: