Skip to content

Commit 4ca4485

Browse files
author
Xueming Shen
committed
8365588: defineClass that accepts a ByteBuffer does not work as expected
Reviewed-by: alanb
1 parent 1d6cafd commit 4ca4485

File tree

3 files changed

+432
-5
lines changed

3 files changed

+432
-5
lines changed

src/java.base/share/classes/java/lang/ClassLoader.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@
3030
import java.io.IOException;
3131
import java.io.UncheckedIOException;
3232
import java.io.File;
33+
import java.lang.foreign.Arena;
3334
import java.lang.reflect.Constructor;
3435
import java.lang.reflect.InvocationTargetException;
3536
import java.net.URL;
37+
import java.nio.ByteBuffer;
3638
import java.security.CodeSource;
3739
import java.security.ProtectionDomain;
3840
import java.security.cert.Certificate;
@@ -1037,7 +1039,7 @@ protected final Class<?> defineClass(String name, byte[] b, int off, int len,
10371039
*
10381040
* @since 1.5
10391041
*/
1040-
protected final Class<?> defineClass(String name, java.nio.ByteBuffer b,
1042+
protected final Class<?> defineClass(String name, ByteBuffer b,
10411043
ProtectionDomain protectionDomain)
10421044
throws ClassFormatError
10431045
{
@@ -1057,13 +1059,26 @@ protected final Class<?> defineClass(String name, java.nio.ByteBuffer b,
10571059
}
10581060
}
10591061

1060-
protectionDomain = preDefineClass(name, protectionDomain);
1061-
String source = defineClassSourceLocation(protectionDomain);
1062+
boolean trusted = this instanceof BuiltinClassLoader;
1063+
if (trusted) {
1064+
return defineClass(name, b, len, protectionDomain);
1065+
} else {
1066+
// make copy from input byte buffer
1067+
try (var arena = Arena.ofConfined()) {
1068+
ByteBuffer bb = arena.allocate(len).asByteBuffer();
1069+
bb.put(0, b, b.position(), len);
1070+
return defineClass(name, bb, len, protectionDomain);
1071+
}
1072+
}
1073+
}
10621074

1075+
private Class<?> defineClass(String name, ByteBuffer b, int len, ProtectionDomain pd) {
1076+
pd = preDefineClass(name, pd);
1077+
String source = defineClassSourceLocation(pd);
10631078
SharedSecrets.getJavaNioAccess().acquireSession(b);
10641079
try {
1065-
Class<?> c = defineClass2(this, name, b, b.position(), len, protectionDomain, source);
1066-
postDefineClass(c, protectionDomain);
1080+
Class<?> c = defineClass2(this, name, b, b.position(), len, pd, source);
1081+
postDefineClass(c, pd);
10671082
return c;
10681083
} finally {
10691084
SharedSecrets.getJavaNioAccess().releaseSession(b);
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @summary Test various cases of passing java.nio.ByteBuffers to defineClass().
27+
* @bug 8365588
28+
*
29+
* @build DefineClassDirectByteBuffer
30+
* @run junit/othervm --add-opens java.base/java.lang=ALL-UNNAMED -Dmode=Direct DefineClassDirectByteBuffer
31+
* @run junit/othervm --add-opens java.base/java.lang=ALL-UNNAMED -Dmode=Heap DefineClassDirectByteBuffer
32+
*/
33+
34+
import java.lang.foreign.Arena;
35+
import java.lang.reflect.Method;
36+
import java.nio.*;
37+
import java.nio.channels.*;
38+
import java.io.*;
39+
import java.nio.file.Files;
40+
import java.nio.file.Path;
41+
import java.nio.file.Paths;
42+
import java.nio.file.StandardOpenOption;
43+
import java.security.ProtectionDomain;
44+
import java.util.HexFormat;
45+
import java.util.stream.Stream;
46+
47+
import org.junit.jupiter.api.Test;
48+
import org.junit.jupiter.params.ParameterizedTest;
49+
import org.junit.jupiter.params.provider.MethodSource;
50+
51+
import static org.junit.Assert.assertEquals;
52+
import static org.junit.jupiter.api.Assertions.assertThrows;
53+
54+
public class DefineClassDirectByteBuffer {
55+
// this is for the trusted/biltin classloader
56+
private static final String mode = System.getProperty("mode", "Direct");
57+
private static final int CLASSBYTES_OFFSET = 16;
58+
59+
// -------- untrusted path (custom loader) --------
60+
static Stream<ByteBuffer> bytebuffers() throws Exception {
61+
byte[] classBytes = getTestClassBytes();
62+
byte[] classBytesAtOffset = new byte[classBytes.length + CLASSBYTES_OFFSET];
63+
System.arraycopy(classBytes, 0, classBytesAtOffset, CLASSBYTES_OFFSET, classBytes.length);
64+
65+
return Stream.of(
66+
// WRAPPED_BUFFER
67+
ByteBuffer.wrap(classBytes),
68+
ByteBuffer.wrap(classBytes)
69+
.asReadOnlyBuffer(),
70+
ByteBuffer.wrap(classBytesAtOffset)
71+
.position(CLASSBYTES_OFFSET),
72+
ByteBuffer.wrap(classBytesAtOffset)
73+
.position(CLASSBYTES_OFFSET)
74+
.asReadOnlyBuffer(),
75+
// ARRAY_BUFFER
76+
ByteBuffer.allocate(classBytes.length)
77+
.put(classBytes)
78+
.flip(),
79+
ByteBuffer.allocate(classBytes.length)
80+
.put(classBytes)
81+
.flip()
82+
.asReadOnlyBuffer(),
83+
ByteBuffer.allocate(classBytesAtOffset.length)
84+
.put(classBytesAtOffset)
85+
.flip()
86+
.position(CLASSBYTES_OFFSET),
87+
ByteBuffer.allocate(classBytesAtOffset.length)
88+
.put(classBytesAtOffset)
89+
.flip()
90+
.position(CLASSBYTES_OFFSET)
91+
.asReadOnlyBuffer(),
92+
// DIRECT_BUFFER
93+
ByteBuffer.allocateDirect(classBytes.length)
94+
.put(classBytes)
95+
.flip(),
96+
ByteBuffer.allocateDirect(classBytes.length)
97+
.put(classBytes)
98+
.flip()
99+
.asReadOnlyBuffer(),
100+
ByteBuffer.allocateDirect(classBytesAtOffset.length)
101+
.put(classBytesAtOffset)
102+
.flip()
103+
.position(CLASSBYTES_OFFSET),
104+
ByteBuffer.allocateDirect(classBytesAtOffset.length)
105+
.put(classBytesAtOffset)
106+
.flip()
107+
.position(CLASSBYTES_OFFSET)
108+
.asReadOnlyBuffer(),
109+
// FOREIGN_AUTO_BUFFER
110+
Arena.ofAuto()
111+
.allocate(classBytes.length)
112+
.asByteBuffer()
113+
.put(classBytes)
114+
.flip(),
115+
Arena.ofAuto()
116+
.allocate(classBytes.length)
117+
.asByteBuffer()
118+
.put(classBytes)
119+
.flip()
120+
.asReadOnlyBuffer(),
121+
Arena.ofAuto()
122+
.allocate(classBytesAtOffset.length)
123+
.asByteBuffer()
124+
.put(classBytesAtOffset)
125+
.flip()
126+
.position(CLASSBYTES_OFFSET),
127+
Arena.ofAuto()
128+
.allocate(classBytesAtOffset.length)
129+
.asByteBuffer()
130+
.put(classBytesAtOffset)
131+
.flip()
132+
.position(CLASSBYTES_OFFSET)
133+
.asReadOnlyBuffer(),
134+
// FOREIGN_CONFINED_BUFFER
135+
Arena.ofConfined()
136+
.allocate(classBytes.length)
137+
.asByteBuffer()
138+
.put(classBytes)
139+
.flip(),
140+
Arena.ofConfined()
141+
.allocate(classBytes.length)
142+
.asByteBuffer()
143+
.put(classBytes)
144+
.flip()
145+
.asReadOnlyBuffer(),
146+
Arena.ofConfined()
147+
.allocate(classBytesAtOffset.length)
148+
.asByteBuffer()
149+
.put(classBytesAtOffset)
150+
.flip()
151+
.position(CLASSBYTES_OFFSET),
152+
Arena.ofConfined()
153+
.allocate(classBytesAtOffset.length)
154+
.asByteBuffer()
155+
.put(classBytesAtOffset)
156+
.flip()
157+
.position(CLASSBYTES_OFFSET)
158+
.asReadOnlyBuffer(),
159+
// FOREIGN_GLOBAL_BUFFER
160+
Arena.global()
161+
.allocate(classBytes.length)
162+
.asByteBuffer()
163+
.put(classBytes)
164+
.flip(),
165+
Arena.global()
166+
.allocate(classBytes.length)
167+
.asByteBuffer()
168+
.put(classBytes)
169+
.flip()
170+
.asReadOnlyBuffer(),
171+
Arena.global()
172+
.allocate(classBytesAtOffset.length)
173+
.asByteBuffer()
174+
.put(classBytesAtOffset)
175+
.flip()
176+
.position(CLASSBYTES_OFFSET),
177+
Arena.global()
178+
.allocate(classBytesAtOffset.length)
179+
.asByteBuffer()
180+
.put(classBytesAtOffset)
181+
.flip()
182+
.position(CLASSBYTES_OFFSET)
183+
.asReadOnlyBuffer(),
184+
// FOREIGN_SHARED_BUFFER
185+
Arena.ofShared()
186+
.allocate(classBytes.length)
187+
.asByteBuffer()
188+
.put(classBytes)
189+
.flip(),
190+
Arena.ofShared()
191+
.allocate(classBytes.length)
192+
.asByteBuffer()
193+
.put(classBytes)
194+
.flip()
195+
.asReadOnlyBuffer(),
196+
Arena.ofShared()
197+
.allocate(classBytesAtOffset.length)
198+
.asByteBuffer()
199+
.put(classBytesAtOffset)
200+
.flip()
201+
.position(CLASSBYTES_OFFSET),
202+
Arena.ofShared()
203+
.allocate(classBytesAtOffset.length)
204+
.asByteBuffer()
205+
.put(classBytesAtOffset)
206+
.flip()
207+
.position(CLASSBYTES_OFFSET)
208+
.asReadOnlyBuffer(),
209+
// MAPPED_BUFFER: MapMode.READ_ONLY from READ fc, the bb is readonly
210+
getMappedByteBuffer(classBytes, 0),
211+
getMappedByteBuffer(classBytesAtOffset, CLASSBYTES_OFFSET)
212+
);
213+
}
214+
215+
static ByteBuffer getMappedByteBuffer(byte[] classBytes, int offset) throws Exception {
216+
Path tempDir = Paths.get(System.getProperty("test.classes", "."));
217+
Files.createDirectories(tempDir);
218+
Path tempClassFile = tempDir.resolve(
219+
String.format("DefineClassDirectByteBuffer_Greeting_%d.class", offset));
220+
Files.write(tempClassFile, classBytes);
221+
try (FileChannel fc = FileChannel.open(tempClassFile, StandardOpenOption.READ)) {
222+
return fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).position(offset);
223+
}
224+
}
225+
226+
@ParameterizedTest()
227+
@MethodSource("bytebuffers")
228+
void testDefineClassWithCustomLoaderByteBuffer(ByteBuffer bb) throws Exception {
229+
int originalPos = bb.position();
230+
int originalLimit = bb.limit();
231+
CustomClassLoader loader = new CustomClassLoader();
232+
Class<?> clazz = loader.defineClassFromByteBuffer(bb);
233+
assertInvocating(clazz);
234+
// long-standing (and undocumented) behavior, see JDK-8352583
235+
if (bb.isDirect() || bb.hasArray()) {
236+
assertEquals(originalPos, bb.position());
237+
} else {
238+
assertEquals(originalLimit, bb.position());
239+
}
240+
}
241+
242+
// -------- trusted path (BuiltinClassLoader) --------
243+
@Test
244+
void testDefineClassWithBuiltinLoaderByteBuffer() throws Exception {
245+
var classBytes = getTestClassBytes();
246+
var builtin = ClassLoader.getPlatformClassLoader();
247+
var bb = mode.equals("Direct")
248+
? ByteBuffer.allocateDirect(classBytes.length).put(classBytes).flip()
249+
: ByteBuffer.allocate(classBytes.length).put(classBytes).flip();
250+
var originalPos = bb.position();
251+
// reflectively call protected defineClass(String, ByteBuffer, ProtectionDomain)
252+
Method m = ClassLoader.class.getDeclaredMethod(
253+
"defineClass", String.class, ByteBuffer.class, ProtectionDomain.class
254+
);
255+
m.setAccessible(true);
256+
Class<?> clazz = (Class<?>) m.invoke(builtin, null, bb, null);
257+
assertInvocating(clazz);
258+
assertEquals(originalPos, bb.position());
259+
}
260+
261+
// -------- shared helpers --------
262+
private static void assertInvocating(Class<?> clazz) throws Exception {
263+
var instance = clazz.getDeclaredConstructor().newInstance();
264+
var m = clazz.getMethod("hello");
265+
assertEquals("Hello", m.invoke(instance));
266+
}
267+
268+
private static class CustomClassLoader extends ClassLoader {
269+
Class<?> defineClassFromByteBuffer(ByteBuffer bb) throws Exception {
270+
return defineClass(null, bb, null);
271+
}
272+
}
273+
274+
private static byte[] getTestClassBytes() throws Exception {
275+
final String source = """
276+
public class Greeting {
277+
public String hello() {
278+
return "Hello";
279+
}
280+
}
281+
""";
282+
// (externally) compiled content of the above source, represented as hex
283+
final String classBytesHex = """
284+
cafebabe0000004600110a000200030700040c000500060100106a617661
285+
2f6c616e672f4f626a6563740100063c696e69743e010003282956080008
286+
01000548656c6c6f07000a0100084772656574696e67010004436f646501
287+
000f4c696e654e756d6265725461626c6501000568656c6c6f0100142829
288+
4c6a6176612f6c616e672f537472696e673b01000a536f7572636546696c
289+
6501000d4772656574696e672e6a61766100210009000200000000000200
290+
01000500060001000b0000001d00010001000000052ab70001b100000001
291+
000c000000060001000000010001000d000e0001000b0000001b00010001
292+
000000031207b000000001000c000000060001000000030001000f000000
293+
020010
294+
""";
295+
return HexFormat.of().parseHex(classBytesHex.replaceAll("\n", ""));
296+
}
297+
}

0 commit comments

Comments
 (0)