Skip to content

Commit 712014c

Browse files
author
David Holmes
committedJan 8, 2021
8258077: Using -Xcheck:jni can lead to a double-free after JDK-8193234
Reviewed-by: dcubed, hseigel
1 parent 56a354e commit 712014c

File tree

5 files changed

+316
-19
lines changed

5 files changed

+316
-19
lines changed
 

‎src/hotspot/share/prims/jniCheck.cpp

+24-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -43,6 +43,7 @@
4343
#include "runtime/jfieldIDWorkaround.hpp"
4444
#include "runtime/jniHandles.inline.hpp"
4545
#include "runtime/thread.inline.hpp"
46+
#include "utilities/formatBuffer.hpp"
4647
#include "utilities/utf8.hpp"
4748

4849
// Complain every extra number of unplanned local refs
@@ -399,19 +400,16 @@ static void* check_wrapped_array(JavaThread* thr, const char* fn_name,
399400
GuardedMemory guarded(carray);
400401
void* orig_result = guarded.get_tag();
401402
if (!guarded.verify_guards()) {
402-
tty->print_cr("ReleasePrimitiveArrayCritical: release array failed bounds "
403-
"check, incorrect pointer returned ? array: " PTR_FORMAT " carray: "
404-
PTR_FORMAT, p2i(obj), p2i(carray));
405-
guarded.print_on(tty);
406-
NativeReportJNIFatalError(thr, "ReleasePrimitiveArrayCritical: "
407-
"failed bounds check");
403+
tty->print_cr("%s: release array failed bounds check, incorrect pointer returned ? array: "
404+
PTR_FORMAT " carray: " PTR_FORMAT, fn_name, p2i(obj), p2i(carray));
405+
DEBUG_ONLY(guarded.print_on(tty);) // This may crash.
406+
NativeReportJNIFatalError(thr, err_msg("%s: failed bounds check", fn_name));
408407
}
409408
if (orig_result == NULL) {
410-
tty->print_cr("ReleasePrimitiveArrayCritical: unrecognized elements. array: "
411-
PTR_FORMAT " carray: " PTR_FORMAT, p2i(obj), p2i(carray));
412-
guarded.print_on(tty);
413-
NativeReportJNIFatalError(thr, "ReleasePrimitiveArrayCritical: "
414-
"unrecognized elements");
409+
tty->print_cr("%s: unrecognized elements. array: " PTR_FORMAT " carray: " PTR_FORMAT,
410+
fn_name, p2i(obj), p2i(carray));
411+
DEBUG_ONLY(guarded.print_on(tty);) // This may crash.
412+
NativeReportJNIFatalError(thr, err_msg("%s: unrecognized elements", fn_name));
415413
}
416414
if (rsz != NULL) {
417415
*rsz = guarded.get_user_size();
@@ -420,24 +418,30 @@ static void* check_wrapped_array(JavaThread* thr, const char* fn_name,
420418
}
421419

422420
static void* check_wrapped_array_release(JavaThread* thr, const char* fn_name,
423-
void* obj, void* carray, jint mode) {
421+
void* obj, void* carray, jint mode, jboolean is_critical) {
424422
size_t sz;
425423
void* orig_result = check_wrapped_array(thr, fn_name, obj, carray, &sz);
426424
switch (mode) {
427-
// As we never make copies, mode 0 and JNI_COMMIT are the same.
428425
case 0:
426+
memcpy(orig_result, carray, sz);
427+
GuardedMemory::free_copy(carray);
428+
break;
429429
case JNI_COMMIT:
430430
memcpy(orig_result, carray, sz);
431+
if (is_critical) {
432+
// For ReleasePrimitiveArrayCritical we must free the internal buffer
433+
// allocated through GuardedMemory.
434+
GuardedMemory::free_copy(carray);
435+
}
431436
break;
432437
case JNI_ABORT:
438+
GuardedMemory::free_copy(carray);
433439
break;
434440
default:
435441
tty->print_cr("%s: Unrecognized mode %i releasing array "
436-
PTR_FORMAT " elements " PTR_FORMAT, fn_name, mode, p2i(obj), p2i(carray));
442+
PTR_FORMAT " elements " PTR_FORMAT, fn_name, mode, p2i(obj), p2i(carray));
437443
NativeReportJNIFatalError(thr, "Unrecognized array release mode");
438444
}
439-
// We always need to release the copy we made with GuardedMemory
440-
GuardedMemory::free_copy(carray);
441445
return orig_result;
442446
}
443447

@@ -1715,7 +1719,7 @@ JNI_ENTRY_CHECKED(void, \
17151719
typeArrayOop a = typeArrayOop(JNIHandles::resolve_non_null(array)); \
17161720
) \
17171721
ElementType* orig_result = (ElementType *) check_wrapped_array_release( \
1718-
thr, "checked_jni_Release"#Result"ArrayElements", array, elems, mode); \
1722+
thr, "checked_jni_Release"#Result"ArrayElements", array, elems, mode, JNI_FALSE); \
17191723
UNCHECKED()->Release##Result##ArrayElements(env, array, orig_result, mode); \
17201724
functionExit(thr); \
17211725
JNI_END
@@ -1884,7 +1888,8 @@ JNI_ENTRY_CHECKED(void,
18841888
check_is_primitive_array(thr, array);
18851889
)
18861890
// Check the element array...
1887-
void* orig_result = check_wrapped_array_release(thr, "ReleasePrimitiveArrayCritical", array, carray, mode);
1891+
void* orig_result = check_wrapped_array_release(thr, "ReleasePrimitiveArrayCritical",
1892+
array, carray, mode, JNI_TRUE);
18881893
UNCHECKED()->ReleasePrimitiveArrayCritical(env, array, orig_result, mode);
18891894
functionExit(thr);
18901895
JNI_END
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright (c) 2021, 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+
/* @test
25+
* @bug 8258077
26+
* @summary verify multiple release calls on a copied array work when checked
27+
* @library /test/lib
28+
* @run main/native TestCheckedReleaseArrayElements launch
29+
*/
30+
31+
import java.util.Arrays;
32+
import jdk.test.lib.process.ProcessTools;
33+
import jdk.test.lib.process.OutputAnalyzer;
34+
import jdk.test.lib.Utils;
35+
import jtreg.SkippedException;
36+
37+
public class TestCheckedReleaseArrayElements {
38+
39+
static {
40+
System.loadLibrary("TestCheckedReleaseArrayElements");
41+
}
42+
43+
public static void main(String[] args) throws Throwable {
44+
if (args == null || args.length == 0) {
45+
test();
46+
} else {
47+
OutputAnalyzer output =
48+
ProcessTools.executeTestJvm("-Xcheck:jni",
49+
"-Djava.library.path=" + Utils.TEST_NATIVE_PATH,
50+
"TestCheckedReleaseArrayElements");
51+
output.shouldHaveExitValue(0);
52+
output.stderrShouldBeEmpty();
53+
output.stdoutShouldNotBeEmpty();
54+
}
55+
}
56+
57+
/*
58+
* If GetIntArrayElements returns a copy, we update the array in slices
59+
* calling ReleaseIntArrayElements with JNI_COMMIT to write-back the
60+
* updates, which are then checked. Finally we use JNI_ABORT to free
61+
* the copy.
62+
*/
63+
public static void test() {
64+
final int slices = 3;
65+
final int sliceLength = 3;
66+
int[] arr = new int[slices * sliceLength];
67+
68+
if (!init(arr)) {
69+
throw new SkippedException("Test skipped as GetIntArrayElements did not make a copy");
70+
}
71+
72+
System.out.println("Array before: " + Arrays.toString(arr));
73+
74+
// We fill the array in slices so that arr[i] = i
75+
for (int i = 0; i < slices; i++) {
76+
int start = i * sliceLength;
77+
fill(arr, start, sliceLength);
78+
System.out.println("Array during: " + Arrays.toString(arr));
79+
check(arr, (i + 1) * sliceLength);
80+
}
81+
System.out.println("Array after: " + Arrays.toString(arr));
82+
cleanup(arr);
83+
}
84+
85+
/*
86+
* Calls GetIntArrayElements and stashes the native pointer for
87+
* use by fill() if a copy was made.
88+
* Returns true if a copy was made else false.
89+
*/
90+
static native boolean init(int[] arr);
91+
92+
/*
93+
* Fills in target[start] to target[start+count-1], so that
94+
* target[i] == i. The update is done natively using the raw
95+
* pointer into the array.
96+
*/
97+
static native void fill(int[] target, int start, int count);
98+
99+
/*
100+
* Properly release the copied array
101+
*/
102+
static native void cleanup(int[] target);
103+
104+
105+
static void check(int[] source, int count) {
106+
for (int i = 0; i < count; i++) {
107+
if (source[i] != i) {
108+
System.out.println("Failing source array: " + Arrays.toString(source));
109+
throw new RuntimeException("Expected source[" + i + "] == " +
110+
i + " but got " + source[i]);
111+
}
112+
}
113+
for (int i = count; i < source.length; i++) {
114+
if (source[i] != 0) {
115+
System.out.println("Failing source array: " + Arrays.toString(source));
116+
throw new RuntimeException("Expected source[" + i +
117+
"] == 0 but got " + source[i]);
118+
}
119+
}
120+
121+
}
122+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2021, 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+
/* @test
25+
* @bug 8193234 8258077
26+
* @summary Check ReleasePrimitiveArrayCritical doesn't leak memory with
27+
* Xcheck:jni and JNI_COMMIT mode
28+
* @comment This is a manual test as you need to verify memory usage externally.
29+
* @library /test/lib
30+
* @run main/othervm/native/manual -Xms4m -Xmx4m -Xcheck:jni TestCheckedReleaseCriticalArray
31+
*/
32+
import jdk.test.lib.process.ProcessTools;
33+
import jdk.test.lib.process.OutputAnalyzer;
34+
import jtreg.SkippedException;
35+
36+
public class TestCheckedReleaseCriticalArray {
37+
38+
static {
39+
System.loadLibrary("TestCheckedReleaseCriticalArray");
40+
}
41+
42+
/*
43+
* We repeatedly modify an array via the JNI critical functions, using
44+
* JNI_COMMIT mode. No memory leak should be observed on a VM that
45+
* provides direct array access.
46+
*/
47+
public static void main(String[] args) {
48+
int[] array = new int[] { 1, 2, 3 };
49+
if (!modifyArray(array)) {
50+
// If the VM makes copies then we will leak them if we only ever use
51+
// JNI_COMMIT mode.
52+
throw new SkippedException("Test skipped as GetPrimitiveArrayCritical made a copy");
53+
}
54+
while (true) {
55+
modifyArray(array);
56+
}
57+
}
58+
59+
private static native boolean modifyArray(int[] array);
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2021, 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+
#include <jni.h>
25+
26+
static jint* arr;
27+
28+
JNIEXPORT jboolean JNICALL
29+
Java_TestCheckedReleaseArrayElements_init(JNIEnv *env,
30+
jclass* clazz,
31+
jintArray target) {
32+
jboolean isCopy;
33+
arr = (*env)->GetIntArrayElements(env, target, &isCopy);
34+
if (arr == NULL) {
35+
(*env)->FatalError(env, "Unexpected NULL return from GetIntArrayElements");
36+
}
37+
if (isCopy == JNI_FALSE) {
38+
(*env)->ReleaseIntArrayElements(env, target, arr, JNI_ABORT);
39+
}
40+
return isCopy;
41+
}
42+
43+
JNIEXPORT void JNICALL
44+
Java_TestCheckedReleaseArrayElements_cleanup(JNIEnv *env,
45+
jclass* clazz,
46+
jintArray target) {
47+
(*env)->ReleaseIntArrayElements(env, target, arr, JNI_ABORT);
48+
}
49+
50+
51+
JNIEXPORT void JNICALL
52+
Java_TestCheckedReleaseArrayElements_fill(JNIEnv *env,
53+
jclass* clazz,
54+
jintArray target,
55+
jint start,
56+
jint count) {
57+
// Update a slice of the raw array
58+
int i;
59+
for (i = start; count > 0; i++, count--) {
60+
arr[i] = i;
61+
}
62+
// Write the results back to target, leaving it usable for future updates
63+
(*env)->ReleaseIntArrayElements(env, target, arr, JNI_COMMIT);
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2021, 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+
#include <jni.h>
25+
26+
JNIEXPORT jboolean JNICALL
27+
Java_TestCheckedReleaseCriticalArray_modifyArray(JNIEnv *env,
28+
jclass clazz,
29+
jintArray iarr) {
30+
jboolean isCopy;
31+
jint* arr = (jint *)(*env)->GetPrimitiveArrayCritical(env, iarr, &isCopy);
32+
if (arr == NULL) {
33+
(*env)->FatalError(env, "Unexpected NULL return from GetPrimitiveArrayCritical");
34+
}
35+
if (isCopy == JNI_FALSE) {
36+
jint len = (*env)->GetArrayLength(env, iarr);
37+
// make arbitrary changes to the array
38+
for (int i = 0; i < len; i++) {
39+
arr[i] *= 2;
40+
}
41+
// write-back using JNI_COMMIT to test for memory leak
42+
(*env)->ReleasePrimitiveArrayCritical(env, iarr, arr, JNI_COMMIT);
43+
}
44+
// we skip the test if the VM makes a copy - as it will definitely leak
45+
return !isCopy;
46+
}

0 commit comments

Comments
 (0)
Please sign in to comment.