-
Notifications
You must be signed in to change notification settings - Fork 5.7k
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
Conversation
…aySizeException Reviewed-By:
👋 Welcome back jpai! A progress list of the required criteria for merging this PR into |
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())) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
I had created a copy of this branch in my personal fork and included the [1] https://mail.openjdk.java.net/pipermail/jdk-dev/2020-September/004736.html |
@jaikiran The following labels will be automatically applied to this pull request: 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 |
Webrevs
|
Hi, Jaikiran. I can sponsor this change. |
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 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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
…ile.getBytes() method itself
There was a problem hiding this 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.
/** | ||
* 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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
int len = (int)ze.getSize(); | ||
long len = ze.getSize(); | ||
if (len > MAX_BUFFER_SIZE) { | ||
throw new OutOfMemoryError("Required array size too large"); | ||
} |
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
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.
|
||
|
There was a problem hiding this comment.
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())) { |
There was a problem hiding this comment.
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(); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra line
bw.write("OOM-Test: "); | ||
for (long i = 0; i < 2147483648L; i++) { | ||
bw.write("a"); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Hello Lance, does the latest state of this PR look fine to you? If so, shall I trigger a integrate? |
@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:
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
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 |
There was a problem hiding this 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,
/integrate |
/sponsor |
@LanceAndersen @jaikiran Since your change was applied there have been 204 commits pushed to the
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. |
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 inJarFile#getBytes
can lead to aNegativeArraySizeException
. This is due to the:block which evaluates to
true
when the size of the manifest entry is larger thanInteger.MAX_VALUE
. As a result, this then ends up calling the code which can lead to theNegativeArraySizeException
.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 theInputStream#readAllBytes()
which internally has the necessary checks to throw the expectedOutOfMemoryError
.This commit also includes a jtreg test case which reproduces the issue and verifies the fix.
Progress
Issue
Reviewers
Download
$ git fetch https://git.openjdk.java.net/jdk pull/323/head:pull/323
$ git checkout pull/323