8361447: [REDO] Checked version of JNI Release<type>ArrayElements needs to filter out known wrapped arrays

8361754: New test runtime/jni/checked/TestCharArrayReleasing.java can cause disk full errors

Reviewed-by: coleenp
Backport-of: f67e4354316dcec185eac66adec2395e20b62579
This commit is contained in:
David Holmes 2025-07-11 00:21:36 +00:00
parent 4d5211ccb0
commit 9adc480ec3
6 changed files with 344 additions and 26 deletions

View File

@ -25,11 +25,12 @@
#include "nmt/memTag.hpp"
#include "runtime/os.hpp"
void* GuardedMemory::wrap_copy(const void* ptr, const size_t len, const void* tag) {
void* GuardedMemory::wrap_copy(const void* ptr, const size_t len,
const void* tag, const void* tag2) {
size_t total_sz = GuardedMemory::get_total_size(len);
void* outerp = os::malloc(total_sz, mtInternal);
if (outerp != nullptr) {
GuardedMemory guarded(outerp, len, tag);
GuardedMemory guarded(outerp, len, tag, tag2);
void* innerp = guarded.get_user_ptr();
if (ptr != nullptr) {
memcpy(innerp, ptr, len);
@ -58,8 +59,8 @@ void GuardedMemory::print_on(outputStream* st) const {
return;
}
st->print_cr("GuardedMemory(" PTR_FORMAT ") base_addr=" PTR_FORMAT
" tag=" PTR_FORMAT " user_size=%zu user_data=" PTR_FORMAT,
p2i(this), p2i(_base_addr), p2i(get_tag()), get_user_size(), p2i(get_user_ptr()));
" tag=" PTR_FORMAT " tag2=" PTR_FORMAT " user_size=%zu user_data=" PTR_FORMAT,
p2i(this), p2i(_base_addr), p2i(get_tag()), p2i(get_tag2()), get_user_size(), p2i(get_user_ptr()));
Guard* guard = get_head_guard();
st->print_cr(" Header guard @" PTR_FORMAT " is %s", p2i(guard), (guard->verify() ? "OK" : "BROKEN"));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 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
@ -26,6 +26,7 @@
#define SHARE_MEMORY_GUARDEDMEMORY_HPP
#include "memory/allocation.hpp"
#include "runtime/os.hpp"
#include "utilities/globalDefinitions.hpp"
/**
@ -43,13 +44,14 @@
* |base_addr | 0xABABABABABABABAB | Head guard |
* |+16 | <size_t:user_size> | User data size |
* |+sizeof(uintptr_t) | <tag> | Tag word |
* |+sizeof(uintptr_t) | <tag2> | Tag word |
* |+sizeof(void*) | 0xF1 <user_data> ( | User data |
* |+user_size | 0xABABABABABABABAB | Tail guard |
* -------------------------------------------------------------
*
* Where:
* - guard padding uses "badResourceValue" (0xAB)
* - tag word is general purpose
* - tag word and tag2 word are general purpose
* - user data
* -- initially padded with "uninitBlockPad" (0xF1),
* -- to "freeBlockPad" (0xBA), when freed
@ -111,6 +113,10 @@ protected:
}
bool verify() const {
// We may not be able to dereference directly.
if (!os::is_readable_range((const void*) _guard, (const void*) (_guard + GUARD_SIZE))) {
return false;
}
u_char* c = (u_char*) _guard;
u_char* end = c + GUARD_SIZE;
while (c < end) {
@ -137,6 +143,7 @@ protected:
size_t _user_size;
};
void* _tag;
void* _tag2;
public:
void set_user_size(const size_t usz) { _user_size = usz; }
size_t get_user_size() const { return _user_size; }
@ -144,6 +151,8 @@ protected:
void set_tag(const void* tag) { _tag = (void*) tag; }
void* get_tag() const { return _tag; }
void set_tag2(const void* tag2) { _tag2 = (void*) tag2; }
void* get_tag2() const { return _tag2; }
}; // GuardedMemory::GuardHeader
// Guarded Memory...
@ -162,9 +171,11 @@ protected:
* @param base_ptr allocation wishing to be wrapped, must be at least "GuardedMemory::get_total_size()" bytes.
* @param user_size the size of the user data to be wrapped.
* @param tag optional general purpose tag.
* @param tag2 optional second general purpose tag.
*/
GuardedMemory(void* base_ptr, const size_t user_size, const void* tag = nullptr) {
wrap_with_guards(base_ptr, user_size, tag);
GuardedMemory(void* base_ptr, const size_t user_size,
const void* tag = nullptr, const void* tag2 = nullptr) {
wrap_with_guards(base_ptr, user_size, tag, tag2);
}
/**
@ -189,16 +200,19 @@ protected:
* @param base_ptr allocation wishing to be wrapped, must be at least "GuardedMemory::get_total_size()" bytes.
* @param user_size the size of the user data to be wrapped.
* @param tag optional general purpose tag.
* @param tag2 optional second general purpose tag.
*
* @return user data pointer (inner pointer to supplied "base_ptr").
*/
void* wrap_with_guards(void* base_ptr, size_t user_size, const void* tag = nullptr) {
void* wrap_with_guards(void* base_ptr, size_t user_size,
const void* tag = nullptr, const void* tag2 = nullptr) {
assert(base_ptr != nullptr, "Attempt to wrap null with memory guard");
_base_addr = (u_char*)base_ptr;
get_head_guard()->build();
get_head_guard()->set_user_size(user_size);
get_tail_guard()->build();
set_tag(tag);
set_tag2(tag2);
set_user_bytes(uninitBlockPad);
assert(verify_guards(), "Expected valid memory guards");
return get_user_ptr();
@ -230,6 +244,20 @@ protected:
*/
void* get_tag() const { return get_head_guard()->get_tag(); }
/**
* Set the second general purpose tag.
*
* @param tag general purpose tag.
*/
void set_tag2(const void* tag) { get_head_guard()->set_tag2(tag); }
/**
* Return the second general purpose tag.
*
* @return the second general purpose tag, defaults to null.
*/
void* get_tag2() const { return get_head_guard()->get_tag2(); }
/**
* Return the size of the user data.
*
@ -302,10 +330,12 @@ protected:
* @param ptr the memory to be copied
* @param len the length of the copy
* @param tag optional general purpose tag (see GuardedMemory::get_tag())
* @param tag2 optional general purpose tag (see GuardedMemory::get_tag2())
*
* @return guarded wrapped memory pointer to the user area, or null if OOM.
*/
static void* wrap_copy(const void* p, const size_t len, const void* tag = nullptr);
static void* wrap_copy(const void* p, const size_t len,
const void* tag = nullptr, const void* tag2 = nullptr);
/**
* Free wrapped copy.

View File

@ -350,24 +350,33 @@ check_is_obj_array(JavaThread* thr, jarray jArray) {
}
}
// Arbitrary (but well-known) tag for GetStringChars
const void* STRING_TAG = (void*)0x47114711;
// Arbitrary (but well-known) tag for GetStringUTFChars
const void* STRING_UTF_TAG = (void*) 0x48124812;
// Arbitrary (but well-known) tag for GetPrimitiveArrayCritical
const void* CRITICAL_TAG = (void*)0x49134913;
/*
* Copy and wrap array elements for bounds checking.
* Remember the original elements (GuardedMemory::get_tag())
*/
static void* check_jni_wrap_copy_array(JavaThread* thr, jarray array,
void* orig_elements) {
void* orig_elements, jboolean is_critical = JNI_FALSE) {
void* result;
IN_VM(
oop a = JNIHandles::resolve_non_null(array);
size_t len = arrayOop(a)->length() <<
TypeArrayKlass::cast(a->klass())->log2_element_size();
result = GuardedMemory::wrap_copy(orig_elements, len, orig_elements);
result = GuardedMemory::wrap_copy(orig_elements, len, orig_elements, is_critical ? CRITICAL_TAG : nullptr);
)
return result;
}
static void* check_wrapped_array(JavaThread* thr, const char* fn_name,
void* obj, void* carray, size_t* rsz) {
void* obj, void* carray, size_t* rsz, jboolean is_critical) {
if (carray == nullptr) {
tty->print_cr("%s: elements vector null" PTR_FORMAT, fn_name, p2i(obj));
NativeReportJNIFatalError(thr, "Elements vector null");
@ -386,6 +395,29 @@ static void* check_wrapped_array(JavaThread* thr, const char* fn_name,
DEBUG_ONLY(guarded.print_on(tty);) // This may crash.
NativeReportJNIFatalError(thr, err_msg("%s: unrecognized elements", fn_name));
}
if (orig_result == STRING_TAG || orig_result == STRING_UTF_TAG) {
bool was_utf = orig_result == STRING_UTF_TAG;
tty->print_cr("%s: called on something allocated by %s",
fn_name, was_utf ? "GetStringUTFChars" : "GetStringChars");
DEBUG_ONLY(guarded.print_on(tty);) // This may crash.
NativeReportJNIFatalError(thr, err_msg("%s called on something allocated by %s",
fn_name, was_utf ? "GetStringUTFChars" : "GetStringChars"));
}
if (is_critical && (guarded.get_tag2() != CRITICAL_TAG)) {
tty->print_cr("%s: called on something not allocated by GetPrimitiveArrayCritical", fn_name);
DEBUG_ONLY(guarded.print_on(tty);) // This may crash.
NativeReportJNIFatalError(thr, err_msg("%s called on something not allocated by GetPrimitiveArrayCritical",
fn_name));
}
if (!is_critical && (guarded.get_tag2() == CRITICAL_TAG)) {
tty->print_cr("%s: called on something allocated by GetPrimitiveArrayCritical", fn_name);
DEBUG_ONLY(guarded.print_on(tty);) // This may crash.
NativeReportJNIFatalError(thr, err_msg("%s called on something allocated by GetPrimitiveArrayCritical",
fn_name));
}
if (rsz != nullptr) {
*rsz = guarded.get_user_size();
}
@ -395,7 +427,7 @@ static void* check_wrapped_array(JavaThread* thr, const char* fn_name,
static void* check_wrapped_array_release(JavaThread* thr, const char* fn_name,
void* obj, void* carray, jint mode, jboolean is_critical) {
size_t sz;
void* orig_result = check_wrapped_array(thr, fn_name, obj, carray, &sz);
void* orig_result = check_wrapped_array(thr, fn_name, obj, carray, &sz, is_critical);
switch (mode) {
case 0:
memcpy(orig_result, carray, sz);
@ -1430,9 +1462,6 @@ JNI_ENTRY_CHECKED(jsize,
return result;
JNI_END
// Arbitrary (but well-known) tag
const void* STRING_TAG = (void*)0x47114711;
JNI_ENTRY_CHECKED(const jchar *,
checked_jni_GetStringChars(JNIEnv *env,
jstring str,
@ -1535,9 +1564,6 @@ JNI_ENTRY_CHECKED(jlong,
return result;
JNI_END
// Arbitrary (but well-known) tag - different than GetStringChars
const void* STRING_UTF_TAG = (void*) 0x48124812;
JNI_ENTRY_CHECKED(const char *,
checked_jni_GetStringUTFChars(JNIEnv *env,
jstring str,
@ -1859,7 +1885,7 @@ JNI_ENTRY_CHECKED(void *,
)
void *result = UNCHECKED()->GetPrimitiveArrayCritical(env, array, isCopy);
if (result != nullptr) {
result = check_jni_wrap_copy_array(thr, array, result);
result = check_jni_wrap_copy_array(thr, array, result, JNI_TRUE);
}
functionExit(thr);
return result;

View File

@ -57,7 +57,7 @@ TEST(GuardedMemory, size) {
}
// Test the basic characteristics
TEST(GuardedMemory, basic) {
TEST_VM(GuardedMemory, basic) {
u_char* basep =
(u_char*) os::malloc(GuardedMemory::get_total_size(1), mtInternal);
GuardedMemory guarded(basep, 1, GEN_PURPOSE_TAG);
@ -78,7 +78,7 @@ TEST(GuardedMemory, basic) {
}
// Test a number of odd sizes
TEST(GuardedMemory, odd_sizes) {
TEST_VM(GuardedMemory, odd_sizes) {
u_char* basep =
(u_char*) os::malloc(GuardedMemory::get_total_size(1), mtInternal);
GuardedMemory guarded(basep, 1, GEN_PURPOSE_TAG);
@ -99,7 +99,7 @@ TEST(GuardedMemory, odd_sizes) {
}
// Test buffer overrun into head...
TEST(GuardedMemory, buffer_overrun_head) {
TEST_VM(GuardedMemory, buffer_overrun_head) {
u_char* basep =
(u_char*) os::malloc(GuardedMemory::get_total_size(1), mtInternal);
GuardedMemory guarded(basep, 1, GEN_PURPOSE_TAG);
@ -111,7 +111,7 @@ TEST(GuardedMemory, buffer_overrun_head) {
}
// Test buffer overrun into tail with a number of odd sizes
TEST(GuardedMemory, buffer_overrun_tail) {
TEST_VM(GuardedMemory, buffer_overrun_tail) {
u_char* basep =
(u_char*) os::malloc(GuardedMemory::get_total_size(1), mtInternal);
GuardedMemory guarded(basep, 1, GEN_PURPOSE_TAG);
@ -128,7 +128,7 @@ TEST(GuardedMemory, buffer_overrun_tail) {
}
// Test wrap_copy/wrap_free
TEST(GuardedMemory, wrap) {
TEST_VM(GuardedMemory, wrap) {
EXPECT_TRUE(GuardedMemory::free_copy(nullptr)) << "Expected free nullptr to be OK";
const char* str = "Check my bounds out";
@ -146,3 +146,10 @@ TEST(GuardedMemory, wrap) {
EXPECT_TRUE(GuardedMemory::free_copy(no_data_copy))
<< "Expected valid guards even for no data copy";
}
// Test passing back a bogus GuardedMemory region
TEST_VM(GuardedMemory, unmapped) {
char* unmapped_base = (char*) (GuardedMemoryTest::get_guard_header_size() + 0x1000 + 1); // Avoids assert in constructor
GuardedMemory guarded(unmapped_base);
EXPECT_FALSE(guarded.verify_guards()) << "Guard was not broken as expected";
}

View File

@ -0,0 +1,129 @@
/*
* 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 8357601
* @requires vm.flagless
* @library /test/lib
* @run main/othervm/native TestCharArrayReleasing 0 0
* @run main/othervm/native TestCharArrayReleasing 1 0
* @run main/othervm/native TestCharArrayReleasing 2 0
* @run main/othervm/native TestCharArrayReleasing 3 0
* @run main/othervm/native TestCharArrayReleasing 4 0
* @run main/othervm/native TestCharArrayReleasing 0 1
* @run main/othervm/native TestCharArrayReleasing 1 1
* @run main/othervm/native TestCharArrayReleasing 2 1
* @run main/othervm/native TestCharArrayReleasing 3 1
* @run main/othervm/native TestCharArrayReleasing 4 1
* @run main/othervm/native TestCharArrayReleasing 0 2
* @run main/othervm/native TestCharArrayReleasing 1 2
* @run main/othervm/native TestCharArrayReleasing 2 2
* @run main/othervm/native TestCharArrayReleasing 3 2
* @run main/othervm/native TestCharArrayReleasing 4 2
* @run main/othervm/native TestCharArrayReleasing 0 3
* @run main/othervm/native TestCharArrayReleasing 1 3
* @run main/othervm/native TestCharArrayReleasing 2 3
* @run main/othervm/native TestCharArrayReleasing 3 3
* @run main/othervm/native TestCharArrayReleasing 4 3
*/
import jdk.test.lib.Platform;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
// Test the behaviour of the JNI "char" releasing functions, under Xcheck:jni,
// when they are passed "char" arrays obtained from different sources:
// - source_mode indicates which array to use
// - 0: use a raw malloc'd array
// - 1: use an array from GetCharArrayElements
// - 2: use an array from GetStringChars
// - 3: use an array from GetStringUTFChars
// - 4: use an array from GetPrimitiveArrayCritical
// - release_mode indicates which releasing function to use
// - 0: ReleaseCharArrayElements
// - 1: ReleaseStringChars
// - 2: ReleaseStringUTFChars
// - 3: ReleasePrimitiveArrayCritical
public class TestCharArrayReleasing {
static native void testIt(int srcMode, int releaseMode);
static class Driver {
static {
System.loadLibrary("CharArrayReleasing");
}
public static void main(String[] args) {
int srcMode = Integer.parseInt(args[0]);
int relMode = Integer.parseInt(args[1]);
testIt(srcMode, relMode);
}
}
public static void main(String[] args) throws Throwable {
int ABRT = 1;
int[][] errorCodes = new int[][] {
{ ABRT, 0, ABRT, ABRT, ABRT },
{ ABRT, ABRT, 0, ABRT, ABRT },
{ ABRT, ABRT, ABRT, 0, ABRT },
{ ABRT, ABRT, ABRT, ABRT, 0 },
};
String rcae = "ReleaseCharArrayElements called on something allocated by GetStringChars";
String rcaeUTF = "ReleaseCharArrayElements called on something allocated by GetStringUTFChars";
String rcaeCrit = "ReleaseCharArrayElements called on something allocated by GetPrimitiveArrayCritical";
String rcaeBounds = "ReleaseCharArrayElements: release array failed bounds check";
String rsc = "ReleaseStringChars called on something not allocated by GetStringChars";
String rscBounds = "ReleaseStringChars: release chars failed bounds check";
String rsuc = "ReleaseStringUTFChars called on something not allocated by GetStringUTFChars";
String rsucBounds = "ReleaseStringUTFChars: release chars failed bounds check";
String rpac = "ReleasePrimitiveArrayCritical called on something not allocated by GetPrimitiveArrayCritical";
String rpacBounds = "ReleasePrimitiveArrayCritical: release array failed bounds check";
String rpacStr = "ReleasePrimitiveArrayCritical called on something allocated by GetStringChars";
String rpacStrUTF = "ReleasePrimitiveArrayCritical called on something allocated by GetStringUTFChars";
String[][] errorMsgs = new String[][] {
{ rcaeBounds, "", rcae, rcaeUTF, rcaeCrit },
{ rscBounds, rsc, "", rsc, rsc },
{ rsucBounds, rsuc, rsuc, "", rsuc },
{ rpacBounds, rpac, rpacStr, rpacStrUTF, "" },
};
int srcMode = Integer.parseInt(args[0]);
int relMode = Integer.parseInt(args[1]);
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-Djava.library.path=" + System.getProperty("test.nativepath"),
"--enable-native-access=ALL-UNNAMED",
"-XX:-CreateCoredumpOnCrash",
"-Xcheck:jni",
"TestCharArrayReleasing$Driver",
args[0], args[1]);
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldHaveExitValue(errorCodes[relMode][srcMode]);
output.shouldContain(errorMsgs[relMode][srcMode]);
}
}

View File

@ -0,0 +1,125 @@
/*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "jni.h"
#include "jni_util.h"
// Test the behaviour of the JNI "char" releasing functions, under Xcheck:jni,
// when they are passed "char" arrays obtained from different sources:
// - source_mode indicates which array to use
// - 0: use a raw malloc'd array
// - 1: use an array from GetCharArrayElements
// - 2: use an array from GetStringChars
// - 3: use an array from GetStringUTFChars
// - 4: use an array from GetPrimitiveArrayCritical
// - release_mode indicates which releasing function to use
// - 0: ReleaseCharArrayElements
// - 1: ReleaseStringChars
// - 2: ReleaseStringUTFChars
// - 3: ReleasePrimitiveArrayCritical
//
static char* source[] = {
"malloc",
"GetCharArrayElements",
"GetStringChars",
"GetStringUTFChars",
"GetPrimitiveArrayCritical"
};
static char* release_func[] = {
"ReleaseCharArrayElements",
"ReleaseStringChars",
"ReleaseStringUTFChars",
"ReleasePrimitiveArrayCritical"
};
JNIEXPORT void JNICALL
Java_TestCharArrayReleasing_testIt(JNIEnv *env, jclass cls, jint source_mode,
jint release_mode) {
// First create some Java objects to be used as the sources for jchar[]
// extraction.
const int len = 10;
jcharArray ca = (*env)->NewCharArray(env, len);
jstring str = (*env)->NewStringUTF(env, "A_String");
jthrowable exc = (*env)->ExceptionOccurred(env);
if (exc != NULL) {
fprintf(stderr, "ERROR: Unexpected exception during test set up:\n");
(*env)->ExceptionDescribe(env);
exit(2);
}
fprintf(stdout, "Testing release function %s with array from %s\n",
release_func[release_mode], source[source_mode]);
fflush(stdout);
jboolean is_copy = JNI_FALSE;
jchar* to_release;
switch(source_mode) {
case 0: {
to_release = malloc(10 * sizeof(jchar));
break;
}
case 1: {
to_release = (*env)->GetCharArrayElements(env, ca, &is_copy);
break;
}
case 2: {
to_release = (jchar*) (*env)->GetStringChars(env, str, &is_copy);
break;
}
case 3: {
to_release = (jchar*) (*env)->GetStringUTFChars(env, str, &is_copy);
break;
}
case 4: {
to_release = (jchar*) (*env)->GetPrimitiveArrayCritical(env, ca, &is_copy);
break;
}
default: fprintf(stderr, "Unexpected source_mode %d\n", source_mode);
exit(1);
}
switch (release_mode) {
case 0:
(*env)->ReleaseCharArrayElements(env, ca, to_release, 0);
break;
case 1:
(*env)->ReleaseStringChars(env, str, to_release);
break;
case 2:
(*env)->ReleaseStringUTFChars(env, str, (const char*)to_release);
break;
case 3:
(*env)->ReleasePrimitiveArrayCritical(env, ca, to_release, 0);
break;
default: fprintf(stderr, "Unexpected release_mode %d\n", source_mode);
exit(1);
}
}