Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the P11Cipher AES/ECB/PKCS5Padding array index out of range issue #576

Draft
wants to merge 1 commit into
base: openj9
Choose a base branch
from

Conversation

taoliult
Copy link
Contributor

@taoliult taoliult commented Sep 14, 2022

Fix the issue https://github.ibm.com/runtimes/backlog/issues/853, when running CipherOutputStream tests in FIPS mode.

This issue happens when using Cipher Algorithm AES as parameter to initialize the P11Cipher, it will by default, initialize the P11Cipher with "AES/ECB/PKCS5Padding”, which failed due to ArrayIndexOutOfBoundsException.

In P11Cipher method “implUpdate()”, when there is no native padding support, like for AES-ECB, it will then go into its own padding implementation. And because the method is called for each byte from testing code ByteArrayInputStream for AES-ECB, so the “inLen = 1”, “padBuffer.length = 16” and “padBufferLen” start from 0 and increase by 1 when each implUpdate() invoke due to the “inLen = 1”. So, “bufCapacity” will from 16-1=15 to 16-16=0, for 16 times loop. And in the problem code, there is one more time when the “padBufferLen == 0”, so totally, there will be 16+1=17 times, which is wrong and cause the issue. For block size 16, there should be multiple of 16 times calls in the below part of codes.

        if (padBufferLen != 0) {
          if (padBufferLen != padBuffer.length) {
            int bufCapacity = padBuffer.length - padBufferLen;
            if (inLen > bufCapacity) {
              bufferInputBytes(in, inOfs, bufCapacity);
              inOfs += bufCapacity;
              inLen -= bufCapacity;
            } else {
              bufferInputBytes(in, inOfs, inLen);
              return 0;
            }
          }
          if (encrypt) {
            k = token.p11.C_EncryptUpdate(session.id(),
                0, padBuffer, 0, padBufferLen,
                0, out, outOfs, outLen);
          } else {
            k = token.p11.C_DecryptUpdate(session.id(),
                0, padBuffer, 0, padBufferLen,
                0, out, outOfs, outLen);
          }
          padBufferLen = 0;
        }

So, I change the “ if (inLen > bufCapacity)” to “ if (inLen >= bufCapacity)”, the in loop times change to 15, then the totally calls change to 16 for each block size 16 for AES.

It works for the failed test cases. And I also tried using the byte[] directly instead of the ByteArrayInputStream, it also works in the test case. Following are the Grinder tests results after the code fix.

https://hyc-runtimes-jenkins.swg-devops.com/view/Test_grinder/job/Grinder/27352/
https://hyc-runtimes-jenkins.swg-devops.com/view/Test_grinder/job/Grinder/27353/
https://hyc-runtimes-jenkins.swg-devops.com/view/Test_grinder/job/Grinder/27455/
https://hyc-runtimes-jenkins.swg-devops.com/view/Test_grinder/job/Grinder/27456/
https://hyc-runtimes-jenkins.swg-devops.com/view/Test_grinder/job/Grinder/27457/

And for why in non-FIPS testing, we did not found this issue, that is because in the non-FIPS testing, SunJCE provider will be used instead of the SunPKCS11.

Signed-off-by: Tao Liu [email protected]

@pshipton
Copy link
Member

@alon-sh pls review

@pshipton
Copy link
Member

Pls add an IBM copyright to the modified file.

@keithc-ca
Copy link
Member

@alon-sh Can you please share your opinions on this?

@alon-sh
Copy link
Contributor

alon-sh commented Sep 24, 2022

@taoliult i dont understand this fix - if you say the padding buffer is written to 17 times and u changed it so its 16 times. how come was it not failing when trying to buffer 17 times due to the padding buffer being only 16 bytes long? why did it only fail in the final() call?
also, the error in the original issue is
Java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 32
how is that error related to the issue you were pointing out?
please explain how does your change fixes the original issue

@alon-sh
Copy link
Contributor

alon-sh commented Sep 28, 2022

@taoliult any update?

@taoliult
Copy link
Contributor Author

@alon-sh

The test case will read each byte from the ByteArrayInputStream, and the ByteArrayInputStream come from a byte array, which size is 3 times of the blockSize, that is 3*16=48 for AES. And then, it calls the CipherOutputStream’s write and flush method. And when running the CipherOutputStream.write(), it will finally call the method "implUpdate(byte[] in, int inOfs, int inLen, byte[] out, int outOfs, int outLen)" in P11Cipher.

And in implUpdate() method, for AES/ECB/PKCS5Padding, it will go into the below codes, because there is no native padding, it will use the P11Cipher its own padding implementation.

            if (padBufferLen != 0) {                                                          // line 604
                if (padBufferLen != padBuffer.length) {
                    int bufCapacity = padBuffer.length - padBufferLen;
                    if (inLen > bufCapacity) {
                        bufferInputBytes(in, inOfs, bufCapacity);
                        inOfs += bufCapacity;
                        inLen -= bufCapacity;
                    } else {
                        bufferInputBytes(in, inOfs, inLen);
                        return 0;
                    }
                }
                if (encrypt) {
                    k = token.p11.C_EncryptUpdate(session.id(),
                            0, padBuffer, 0, padBufferLen,
                            0, out, outOfs, outLen);
                } else {
                    k = token.p11.C_DecryptUpdate(session.id(),
                            0, padBuffer, 0, padBufferLen,
                            0, out, outOfs, outLen);
                }
                padBufferLen = 0;
            }

When call the write method for the first byte, padBufferLen == 0, so it will not fall into the above "if" condition. And when call for the second byte, padBufferLen == 1, then it will fall into the above "if" condition.

"inLen" is always 1 because the test code call the write method for each byte.

"padBuffer.length" is 16 because the AES block size is 16.

"padBufferLen" will increase 1 each time when the test code call the write method for each byte. So, it will go from 1 to 16, the 16 times. And when padBufferLen is 16, bufCapacity will be 16-16=0, then the bytes in buffer will be processed and the padBufferLen will be set to 0 again.

"bufCapacity" will from 16-1=15 to 16-16=0, totally 16 times.

And why 16 times, because if (inLen > bufCapacity), and inLen is alway 1 in this test case, bufCapacity will from 15 to 0, 16 times. And then, 1 > 0 is true, so the byte will not be copy into the buffer array, instead, those 16 bytes will be processed.

And, before go into the "if" condition, there is one byte be processed when padBufferLen == 0. So, if there are 17 bytes, all of these 17 bytes will be processed. But if there are 16 bytes, then only the first byte will be processed, all the other 15 bytes will be copy into a buffer array without be processed. And at this time, because the padBufferLen will increase 1 after the above codes, so after the last byte copy into the buffer array, the padBufferLen will become 16.

And for the error “ArrayIndexOutOfBoundsException: Array index out of range: 32”, it happens when call the setPaddingBytes() method from line 808 to line 95, codes below.

            if (paddingObj != null) {
                int startOff = 0;
                if (reqBlockUpdates) {
                    startOff = padBufferLen;
                }
                int actualPadLen = paddingObj.setPaddingBytes(padBuffer,     // line 808
                        startOff, requiredOutLen - bytesBuffered);
                k = token.p11.C_EncryptUpdate(session.id(),
                        0, padBuffer, 0, startOff + actualPadLen,
                        0, out, outOfs, outLen);
            }


    public int setPaddingBytes(byte[] paddingBuffer, int startOff, int padLen) {
        Arrays.fill(paddingBuffer, startOff, startOff + padLen, (byte) (padLen & 0x007f));     // line 95
        return padLen;
    }

The padBuffer length is 16, startOff is 16, and requiredOutLen - bytesBuffered is 16. And there is an out of range check in the Arrays.fill() method, so if the startOff + requiredOutLen - bytesBuffered > padBuffer length, that mean 16 + 16 > 16 is true, then the out of range error happens.

And why startOff is 16, because “startOff = padBufferLen;” and padBufferLen is 16 when input byte array size is 16.

So, my understanding, when AES block size is 16, that mean, the 16 or the multiple of 16 can be processed. But in the current code, 17 bytes can be processed but not 16.

@alon-sh please advice.

@taoliult taoliult force-pushed the bugfix branch 2 times, most recently from fcdcb26 to ca0d683 Compare January 24, 2023 14:46
@@ -604,7 +611,7 @@ private int implUpdate(byte[] in, int inOfs, int inLen,
if (padBufferLen != 0) {
if (padBufferLen != padBuffer.length) {
int bufCapacity = padBuffer.length - padBufferLen;
if (inLen > bufCapacity) {
if (inLen >= bufCapacity) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand your explanation of the problem, we have the same issue on line 715 in implUpdate(ByteBuffer inBuffer, ByteBuffer outBuffer).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithc-ca Working on it, will update shortly.

@taoliult taoliult marked this pull request as draft February 7, 2023 19:24
@taoliult
Copy link
Contributor Author

taoliult commented Feb 7, 2023

Convert this PR back to draft, since need more time to investigate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants