-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8365203: defineClass with direct buffer can cause use-after-free #26724
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
Conversation
👋 Welcome back pminborg! A progress list of the required criteria for merging this PR into |
@minborg 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 34 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. ➡️ To integrate this PR with the above commit message to the |
final class GuardByteBuffer { | ||
|
||
@Test | ||
void guardCrash() throws InterruptedException { |
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 was not able to reproduce the crash using this test on a Mac. The original reproducer worked on a Windows machine.
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 Per, I too couldn't reproduce the crash from the original reproducer (and this test) on macos and I found that a bit odd. I read up a bit about macos memory debugging tools and it turns out macos has a "Guard Malloc" implementation https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html which can be optionally enabled to debug issues like these. man libgmalloc
has additional details about it.
So I ran the original reproducer again, this time with:
export DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib
java ... <that-reproducer>
and that consistently reproduces the crash on macos. I will build this PR locally and give it a try soon to make sure the crash no longer reproduces.
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 will build this PR locally and give it a try soon to make sure the crash no longer reproduces.
I tested this change locally with the reproducer and with libgmalloc
. The crash no longer happens.
postDefineClass(c, protectionDomain); | ||
return c; | ||
|
||
SharedSecrets.getJavaNioAccess().acquireSession(b); |
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.
Now that the fields in SharedSecrets
are @Stable
, we do not have to make a local copy in a static final
field.
Webrevs
|
SharedSecrets.getJavaNioAccess().acquireSession(b); | ||
try { | ||
Class<?> c = defineClass2(this, name, b, b.position(), len, protectionDomain, source); | ||
postDefineClass(c, protectionDomain); |
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.
Should we leave postDefineClass out of this acquire-release scope? I don't see any reason including this.
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 don't think it matters here because something looking to close the arena around the time that it wants to defineClass with memory allocated from that arena is broken.
test/jdk/java/lang/ClassLoader/defineClass/GuardByteBuffer.java
Outdated
Show resolved
Hide resolved
The change looks okay. One thing to check if whether we have tests for JNI GetDirectBufferAddress when the byte buffer is a view of a memory segment. |
I think for the new test introduced, I don't think we should test for a crash. Instead maybe we can simplify it as follows to assert that a closed /*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8365203
* @summary Tests guarding of ByteBuffers in ClassLoader::defineClass
* @run junit GuardByteBuffer
*/
import java.lang.foreign.Arena;
import java.util.HexFormat;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
final class GuardByteBuffer {
@Test
void guardCrash() {
final byte[] classBytes = getClassBytes(); // get bytes of a valid class
final var cl = new ClassLoader() {
void tryCrash() {
var arena = Arena.ofConfined();
var byteBuffer = arena.allocate(classBytes.length).asByteBuffer();
// Close the arena underneath
arena.close();
// expected to always fail because the arena
// from which the ByteBuffer was constructed
// has been closed
assertThrows(IllegalStateException.class,
() -> defineClass(null, byteBuffer, null));
}
};
for (int i = 0; i < 10000; i++) {
cl.tryCrash();
}
}
private static byte[] getClassBytes() {
// unused. this is here just for reference
final String source = """
public class NoOp {}
""";
// (externally) compiled content of the above "source", represented as hex
final String classBytesHex = """
cafebabe00000044000d0a000200030700040c000500060100106a6176612f
6c616e672f4f626a6563740100063c696e69743e0100032829560700080100
044e6f4f70010004436f646501000f4c696e654e756d6265725461626c6501
000a536f7572636546696c650100094e6f4f702e6a61766100210007000200
0000000001000100050006000100090000001d00010001000000052ab70001
b100000001000a000000060001000000010001000b00000002000c
""";
return HexFormat.of().parseHex(classBytesHex.replaceAll("\n", ""));
}
} |
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 Per for the update. This looks good to me.
It would be good to run this change and the test in our CI once to be sure nothing unexpected fails.
Thanks for the heads up on CI. It passes tier1-3. Thanks for the reviews! |
/integrate |
Going to push as commit 19f0755.
Your commit was automatically rebased without conflicts. |
Description
This PR proposes to update the
ClassLoader
implementation to properly guard access to the providedByteBuffer
when defining a class usingdefineClass(String, ByteBuffer, ...)
. Specifically, calls toSharedSecrets.getJavaNioAccess().acquireSession(ByteBuffer)
andreleaseSession(ByteBuffer)
have been introduced to ensure safe and consistent buffer access throughout the native class definition process, even in the case of aByteBuffer
is backed by aMemorySegment
.Impact
This modification is internal to the
ClassLoader
implementation and does not affect the public API.Improves the robustness and security of class loading from buffers.
Testing
Tier 1, 2, and 3 JDK tests pass on multiple platforms.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/26724/head:pull/26724
$ git checkout pull/26724
Update a local copy of the PR:
$ git checkout pull/26724
$ git pull https://git.openjdk.org/jdk.git pull/26724/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 26724
View PR using the GUI difftool:
$ git pr show -t 26724
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/26724.diff
Using Webrev
Link to Webrev Comment