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

8242882: opening jar file with large manifest might throw NegativeArraySizeException #323

Closed
wants to merge 3 commits into from

Conversation

jaikiran
Copy link
Member

@jaikiran jaikiran commented Sep 23, 2020

Can I please get a review and a sponsor for a fix for https://bugs.openjdk.java.net/browse/JDK-8242882?

As noted in that JBS issue, if the size of the Manifest entry in the jar happens to be very large (such that it exceeds the Integer.MAX_VALUE), then the current code in JarFile#getBytes can lead to a NegativeArraySizeException. This is due to the:

if (len != -1 && len <= 65535) 

block which evaluates to true when the size of the manifest entry is larger than Integer.MAX_VALUE. As a result, this then ends up calling the code which can lead to the NegativeArraySizeException.

The commit in this PR fixes that issue by changing those if/else blocks to prevent this issue and instead use a code path that leads to the InputStream#readAllBytes() which internally has the necessary checks to throw the expected OutOfMemoryError.

This commit also includes a jtreg test case which reproduces the issue and verifies the fix.


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed

Issue

  • JDK-8242882: opening jar file with large manifest might throw NegativeArraySizeException

Reviewers

Download

$ git fetch https://git.openjdk.java.net/jdk pull/323/head:pull/323
$ git checkout pull/323

@bridgekeeper
Copy link

bridgekeeper bot commented Sep 23, 2020

👋 Welcome back jpai! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

final JarFile jar = new JarFile(jarFilePath.toFile());
final OutOfMemoryError oome = Assert.expectThrows(OutOfMemoryError.class, () -> jar.getManifest());
// additionally verify that the OOM was for the right/expected reason
if (!"Required array size too large".equals(oome.getMessage())) {
Copy link
Member Author

Choose a reason for hiding this comment

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

I wasn't too sure if I should add this additional check on the message of the OutOfMemoryError, but decided to do it anyway, given that from what I remember there were some discussions in the core-libs-dev list a while back on the exact messages that such OOMs should throw. So I guessed that it might be OK to rely on those messages in the tests within this project. However, I am open to removing specific check if it's considered unnecessary.

Copy link
Member

Choose a reason for hiding this comment

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

I think it's fine either way.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you are going to validate the message, which I probably would not, it would be important to make sure it document if the message is changed in JarFile::getBytes, that the test needs updated. Otherwise it will be easy to miss.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hello Lance,
I decided to remove the assertion on the exception message. I have updated the PR accordingly.

@jaikiran
Copy link
Member Author

I had created a copy of this branch in my personal fork and included the submit.yml workflow as noted in this recent mail here[1] to try and run the tier1 testing for this change. But it looks like that has failed for unrelated reasons[2]. So I'll go ahead and trigger the tier1 tests locally instead.

[1] https://mail.openjdk.java.net/pipermail/jdk-dev/2020-September/004736.html
[2] https://github.com/jaikiran/jdk/actions/runs/268948812

@openjdk openjdk bot added the rfr Pull request is ready for review label Sep 23, 2020
@openjdk
Copy link

openjdk bot commented Sep 23, 2020

@jaikiran The following labels will be automatically applied to this pull request: core-libs security.

When this pull request is ready to be reviewed, an RFR email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label (add|remove) "label" command.

@openjdk openjdk bot added security security-dev@openjdk.org core-libs core-libs-dev@openjdk.org labels Sep 23, 2020
@mlbridge
Copy link

mlbridge bot commented Sep 23, 2020

Webrevs

@bchristi-git
Copy link
Member

Hi, Jaikiran. I can sponsor this change.

@mlbridge
Copy link

mlbridge bot commented Sep 24, 2020

Mailing list message from Jaikiran Pai on security-dev:

Hello Brent,

Thank you for sponsoring this change.

In the meantime, I triggered the pre-submit GitHub action job to run the
"tier1" tests for a duplicate branch of this PR. That completed
successfully https://github.com/jaikiran/jdk/actions/runs/269960940.

I'll wait for the reviews, before initiating any /integrate command.

-Jaikiran

On 23/09/20 10:21 pm, Brent Christian wrote:

@@ -791,8 +791,10 @@ private void initializeVerifier() {
int len = (int)ze.getSize();
Copy link
Member

Choose a reason for hiding this comment

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

I think the main issue is the casting of the 'long' value from ZipEntry.getSize() into an 'int'. I think checking if the size is > the maximum array size and throwing an OOME here might be a sufficient fix on its own.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hello Brent,

Thank you for the review and sorry about the delayed response - I was involved in a few other things so couldn't get to this sooner.

I have taken your input and updated this patch to address this part. Specifically, I have introduced a new MAX_BUFFER_SIZE within the JarFile. This MAX_BUFFER_SIZE is an actual copy of the field (and value) of java.io.InputStream#MAX_BUFFER_SIZE. I have done a minor change to the javadoc of this field as compared to what is in the javadoc of its InputStream counterpart. I did this so that the OOM exception message being thrown matches the comment in this javadoc (the InputStream has a mismatch in its javadoc and the actual message that gets thrown).

Additionally, in this patch, the if (len != -1 ...) has been changed to if (len >= 0 ...) to prevent the code from entering this block when Zip64 format is involved where (from what I can gather) an uncompressed entry size value can have 2^64 (unsigned) bytes.

Copy link
Member

@bchristi-git bchristi-git left a comment

Choose a reason for hiding this comment

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

If there is only modest improvement in test duration, we may want to add a jtreg timeout tag. Also, given the long duration but relative low priority of testing this, it perhaps should be moved out of Tier 1. I will look into those things after your next update.

Comment on lines 155 to 161
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Required array size too large
*/
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
Copy link
Member

@bchristi-git bchristi-git Sep 30, 2020

Choose a reason for hiding this comment

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

"/**" comments are generally only used for public documentation. For use here, probably a single line // comment would be sufficient to explain what this value is.

This constant is also named, "MAX_ARRAY_SIZE" in various places, which seems more applicable to this case.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hello Brent,

I've updated the PR with your suggested changes for this member variable name and the comment.

Comment on lines 791 to 801
int len = (int)ze.getSize();
long len = ze.getSize();
if (len > MAX_BUFFER_SIZE) {
throw new OutOfMemoryError("Required array size too large");
}
Copy link
Member

Choose a reason for hiding this comment

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

I would just add a new long zeSize to read and check ze.getSize(), and then (int) cast it into len, as before. Then I think no changes would be needed past L802, int bytesRead;

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. Changed it based on your input.

Comment on lines 44 to 45


Copy link
Member

Choose a reason for hiding this comment

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

Unneeded blank lines

final JarFile jar = new JarFile(jarFilePath.toFile());
final OutOfMemoryError oome = Assert.expectThrows(OutOfMemoryError.class, () -> jar.getManifest());
// additionally verify that the OOM was for the right/expected reason
if (!"Required array size too large".equals(oome.getMessage())) {
Copy link
Member

Choose a reason for hiding this comment

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

I think it's fine either way.

bw.newLine();
}
}

Copy link
Member

Choose a reason for hiding this comment

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

Extra line

Comment on lines 76 to 78
bw.write("OOM-Test: ");
for (long i = 0; i < 2147483648L; i++) {
bw.write("a");
Copy link
Member

Choose a reason for hiding this comment

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

As you probably noticed, this test takes a little while to run. One way to speed it up a little would be to write more characters at a time. While we're at it, we may as well make the Manifest well-formed by breaking it into 72-byte lines. See "Line length" under:
https://docs.oracle.com/en/java/javase/15/docs/specs/jar/jar.html#notes-on-manifest-and-signature-files

Just write enough lines to exceed Integer.MAX_VALUE bytes.

Copy link
Member Author

Choose a reason for hiding this comment

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

I decided to slightly change the way this large manifest file was being created. I borrowed the idea from Zip64SizeTest[1] to create the file and set its length to a large value. I hope that is OK. If not, let me know, I will change this part.

[1] https://github.com/openjdk/jdk/blob/master/test/jdk/java/util/zip/ZipFile/Zip64SizeTest.java#L121

Copy link
Member

Choose a reason for hiding this comment

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

I did some automated test runs, and the duration of this test is sufficiently improved, IMO.

While not representative of a real MANIFEST.MF file, I think it works well enough for this specific test.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you for the review Brent.

@jaikiran
Copy link
Member Author

jaikiran commented Oct 8, 2020

Hello Lance, does the latest state of this PR look fine to you? If so, shall I trigger a integrate?

@openjdk
Copy link

openjdk bot commented Oct 8, 2020

@jaikiran This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8242882: opening jar file with large manifest might throw NegativeArraySizeException

Reviewed-by: bchristi, lancea

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 204 new commits pushed to the master branch:

  • f860372: 8253566: clazz.isAssignableFrom will return false for interface implementors
  • 66f27b5: 8254015: copy_to_survivor_space should use in-hand klass for scanning
  • 76a5852: 8253756: C2 CompilerThread0 crash in Node::add_req(Node*)
  • 8f9e479: 8254144: Non-x86 Zero builds fail with return-type warning in os_linux_zero.cpp
  • 7952c06: 8254166: Zero: return-type warning in zeroInterpreter_zero.cpp
  • 894ec76: 8254027: gc/g1/TestHumongousConcurrentStartUndo.java failed with "'Concurrent Mark Cycle' missing from stdout/stderr"
  • bc23690: 8254173: Add Zero, Minimal hotspot targets to submit workflow
  • e1187c4: 8248262: Wrong link target in ModuleDescriptor#isAutomatic's API documentation
  • 9cdfd0f: 8254096: remove jdk.test.lib.Utils::getMandatoryProperty(String) method
  • d1e94ee: 8253909: Implement detailed map file for CDS
  • ... and 194 more: https://git.openjdk.java.net/jdk/compare/812b39f574326406fbbb891fc6bca7947465ff4e...master

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@bchristi-git, @LanceAndersen) but any other Committer may sponsor as well.

➡️ To flag this PR as ready for integration with the above commit message, type /integrate in a new comment. (Afterwards, your sponsor types /sponsor in a new comment to perform the integration).

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Oct 8, 2020
Copy link
Contributor

@LanceAndersen LanceAndersen left a comment

Choose a reason for hiding this comment

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

Hi Jaikiran,

Yes I think you are OK to move forward with the integration,

@jaikiran
Copy link
Member Author

jaikiran commented Oct 8, 2020

/integrate

@openjdk openjdk bot added the sponsor Pull request is ready to be sponsored label Oct 8, 2020
@openjdk
Copy link

openjdk bot commented Oct 8, 2020

@jaikiran
Your change (at version a011b0d) is now ready to be sponsored by a Committer.

@LanceAndersen
Copy link
Contributor

/sponsor

@openjdk openjdk bot closed this Oct 8, 2020
@openjdk openjdk bot added integrated Pull request has been integrated and removed sponsor Pull request is ready to be sponsored ready Pull request is ready to be integrated rfr Pull request is ready for review labels Oct 8, 2020
@openjdk
Copy link

openjdk bot commented Oct 8, 2020

@LanceAndersen @jaikiran Since your change was applied there have been 204 commits pushed to the master branch:

  • f860372: 8253566: clazz.isAssignableFrom will return false for interface implementors
  • 66f27b5: 8254015: copy_to_survivor_space should use in-hand klass for scanning
  • 76a5852: 8253756: C2 CompilerThread0 crash in Node::add_req(Node*)
  • 8f9e479: 8254144: Non-x86 Zero builds fail with return-type warning in os_linux_zero.cpp
  • 7952c06: 8254166: Zero: return-type warning in zeroInterpreter_zero.cpp
  • 894ec76: 8254027: gc/g1/TestHumongousConcurrentStartUndo.java failed with "'Concurrent Mark Cycle' missing from stdout/stderr"
  • bc23690: 8254173: Add Zero, Minimal hotspot targets to submit workflow
  • e1187c4: 8248262: Wrong link target in ModuleDescriptor#isAutomatic's API documentation
  • 9cdfd0f: 8254096: remove jdk.test.lib.Utils::getMandatoryProperty(String) method
  • d1e94ee: 8253909: Implement detailed map file for CDS
  • ... and 194 more: https://git.openjdk.java.net/jdk/compare/812b39f574326406fbbb891fc6bca7947465ff4e...master

Your commit was automatically rebased without conflicts.

Pushed as commit 782d45b.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@jaikiran jaikiran deleted the 8242882 branch August 11, 2021 07:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-libs core-libs-dev@openjdk.org integrated Pull request has been integrated security security-dev@openjdk.org
Development

Successfully merging this pull request may close these issues.

3 participants