From c8679713402186b24608fa4c91397b6a4fd5ebf3 Mon Sep 17 00:00:00 2001 From: Afshin Zafari Date: Mon, 20 Oct 2025 11:32:48 +0000 Subject: [PATCH] 8369527: NMT: print malloc-site when a malloc'd memory detected as corrupted Reviewed-by: dholmes, jsjolen --- src/hotspot/share/nmt/mallocHeader.cpp | 15 ++- src/hotspot/share/nmt/mallocHeader.hpp | 2 +- src/hotspot/share/nmt/mallocHeader.inline.hpp | 2 +- src/hotspot/share/nmt/mallocSiteTable.cpp | 8 +- .../test_nmt_buffer_overflow_detection.cpp | 10 ++ .../NMTPrintMallocSiteOfCorruptedMemory.java | 115 ++++++++++++++++++ .../runtime/NMT/libMallocHeaderModifier.c | 52 ++++++++ 7 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/NMT/NMTPrintMallocSiteOfCorruptedMemory.java create mode 100644 test/hotspot/jtreg/runtime/NMT/libMallocHeaderModifier.c diff --git a/src/hotspot/share/nmt/mallocHeader.cpp b/src/hotspot/share/nmt/mallocHeader.cpp index 2b59a2b6648..d88b5c790fb 100644 --- a/src/hotspot/share/nmt/mallocHeader.cpp +++ b/src/hotspot/share/nmt/mallocHeader.cpp @@ -26,6 +26,7 @@ #include "nmt/mallocHeader.inline.hpp" #include "nmt/mallocSiteTable.hpp" #include "nmt/memTag.hpp" +#include "nmt/memTracker.hpp" #include "runtime/os.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" @@ -36,7 +37,7 @@ // fitting into eight bits. STATIC_ASSERT(sizeof(MemTag) == sizeof(uint8_t)); -void MallocHeader::print_block_on_error(outputStream* st, address bad_address) const { +void MallocHeader::print_block_on_error(outputStream* st, address bad_address, address block_address) const { assert(bad_address >= (address)this, "sanity"); // This function prints block information, including hex dump, in case of a detected @@ -48,6 +49,18 @@ void MallocHeader::print_block_on_error(outputStream* st, address bad_address) c st->print_cr("NMT Block at " PTR_FORMAT ", corruption at: " PTR_FORMAT ": ", p2i(this), p2i(bad_address)); + if (MemTracker::tracking_level() == NMT_TrackingLevel::NMT_detail) { + MallocHeader* mh = (MallocHeader*)block_address; + NativeCallStack stack; + if (MallocSiteTable::access_stack(stack, *mh)) { + st->print_cr("allocated from:"); + stack.print_on(st); + } else { + st->print_cr("allocation-site cannot be shown since the marker is also corrupted."); + } + st->print_cr(""); + } + static const size_t min_dump_length = 256; address from1 = align_down((address)this, sizeof(void*)) - (min_dump_length / 2); address to1 = from1 + min_dump_length; diff --git a/src/hotspot/share/nmt/mallocHeader.hpp b/src/hotspot/share/nmt/mallocHeader.hpp index 8472b5f8ce8..acfc7401268 100644 --- a/src/hotspot/share/nmt/mallocHeader.hpp +++ b/src/hotspot/share/nmt/mallocHeader.hpp @@ -106,7 +106,7 @@ class MallocHeader { // We discount sizes larger than these static const size_t max_reasonable_malloc_size = LP64_ONLY(256 * G) NOT_LP64(3500 * M); - void print_block_on_error(outputStream* st, address bad_address) const; + void print_block_on_error(outputStream* st, address bad_address, address block_address) const; static uint16_t build_footer(uint8_t b1, uint8_t b2) { return (uint16_t)(((uint16_t)b1 << 8) | (uint16_t)b2); } diff --git a/src/hotspot/share/nmt/mallocHeader.inline.hpp b/src/hotspot/share/nmt/mallocHeader.inline.hpp index 8b1862332fc..7bc8a25028c 100644 --- a/src/hotspot/share/nmt/mallocHeader.inline.hpp +++ b/src/hotspot/share/nmt/mallocHeader.inline.hpp @@ -103,7 +103,7 @@ inline OutTypeParam MallocHeader::resolve_checked_impl(InTypeParam memblock) { } OutTypeParam header_pointer = (OutTypeParam)memblock - 1; if (!header_pointer->check_block_integrity(msg, sizeof(msg), &corruption)) { - header_pointer->print_block_on_error(tty, corruption != nullptr ? corruption : (address)header_pointer); + header_pointer->print_block_on_error(tty, corruption != nullptr ? corruption : (address)header_pointer, (address)header_pointer); fatal("NMT has detected a memory corruption bug. Block at " PTR_FORMAT ": %s", p2i(memblock), msg); } return header_pointer; diff --git a/src/hotspot/share/nmt/mallocSiteTable.cpp b/src/hotspot/share/nmt/mallocSiteTable.cpp index c9ddffce5ec..0150a25cae3 100644 --- a/src/hotspot/share/nmt/mallocSiteTable.cpp +++ b/src/hotspot/share/nmt/mallocSiteTable.cpp @@ -163,13 +163,17 @@ MallocSite* MallocSiteTable::lookup_or_add(const NativeCallStack& key, uint32_t* // Access malloc site MallocSite* MallocSiteTable::malloc_site(uint32_t marker) { uint16_t bucket_idx = bucket_idx_from_marker(marker); - assert(bucket_idx < table_size, "Invalid bucket index"); + if (bucket_idx >= table_size) { + return nullptr; + } const uint16_t pos_idx = pos_idx_from_marker(marker); MallocSiteHashtableEntry* head = _table[bucket_idx]; for (size_t index = 0; index < pos_idx && head != nullptr; index++, head = (MallocSiteHashtableEntry*)head->next()) {} - assert(head != nullptr, "Invalid position index"); + if (head == nullptr) { + return nullptr; + } return head->data(); } diff --git a/test/hotspot/gtest/nmt/test_nmt_buffer_overflow_detection.cpp b/test/hotspot/gtest/nmt/test_nmt_buffer_overflow_detection.cpp index ae3f4e74516..82ddd28d56c 100644 --- a/test/hotspot/gtest/nmt/test_nmt_buffer_overflow_detection.cpp +++ b/test/hotspot/gtest/nmt/test_nmt_buffer_overflow_detection.cpp @@ -164,4 +164,14 @@ TEST_VM(NMT, test_realloc) { } } +TEST_VM_FATAL_ERROR_MSG(NMT, memory_corruption_call_stack, ".*header canary.*") { + if (MemTracker::tracking_level() != NMT_detail) { + guarantee(false, "fake message ignore this - header canary"); + } + const size_t SIZE = 1024; + char* p = (char*)os::malloc(SIZE, mtTest); + *(p - 1) = 0; + os::free(p); +} + #endif // !INCLUDE_ASAN diff --git a/test/hotspot/jtreg/runtime/NMT/NMTPrintMallocSiteOfCorruptedMemory.java b/test/hotspot/jtreg/runtime/NMT/NMTPrintMallocSiteOfCorruptedMemory.java new file mode 100644 index 00000000000..f9f94107f37 --- /dev/null +++ b/test/hotspot/jtreg/runtime/NMT/NMTPrintMallocSiteOfCorruptedMemory.java @@ -0,0 +1,115 @@ +/* + * 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 021MALLOC_SIZE-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 + * @summary Check the allocation-site stack trace of a corrupted memory at free() time + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail NMTPrintMallocSiteOfCorruptedMemory + */ + +import jdk.test.lib.Utils; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.whitebox.WhiteBox; + +public class NMTPrintMallocSiteOfCorruptedMemory { + private static final String HEADER_ARG = "header"; + private static final String FOOTER_ARG = "footer"; + private static final String HEADER_AND_SITE_ARG = "header-and-site"; + private static final String FOOTER_AND_SITE_ARG = "footer-and-site"; + private static final int MALLOC_SIZE = 10; + private static WhiteBox wb = WhiteBox.getWhiteBox(); + + static { + System.loadLibrary("MallocHeaderModifier"); + } + + public static native byte modifyHeaderCanary(long malloc_memory); + public static native byte modifyFooterCanary(long malloc_memory, long size); + public static native byte modifyHeaderCanaryAndSiteMarker(long malloc_memory); + public static native byte modifyFooterCanaryAndSiteMarker(long malloc_memory, long size); + + private static void runThisTestWith(String arg) throws Exception { + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(new String[] {"-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-XX:NativeMemoryTracking=detail", + "-Djava.library.path=" + Utils.TEST_NATIVE_PATH, + "NMTPrintMallocSiteOfCorruptedMemory", + arg}); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldMatch("NMT Block at .*, corruption at: "); + switch(arg) { + case HEADER_AND_SITE_ARG, FOOTER_AND_SITE_ARG -> output.shouldContain("allocation-site cannot be shown since the marker is also corrupted."); + case HEADER_ARG, FOOTER_ARG -> { + output.shouldContain("allocated from:"); + output.shouldMatch("\\[.*\\]WB_NMTMalloc\\+0x.*"); + } + } + } + + private static void testModifyHeaderCanary() { + long addr = wb.NMTMalloc(MALLOC_SIZE); + modifyHeaderCanary(addr); + wb.NMTFree(addr); + } + + private static void testModifyFooterCanary() { + long addr = wb.NMTMalloc(MALLOC_SIZE); + modifyFooterCanary(addr, MALLOC_SIZE); + wb.NMTFree(addr); + } + + private static void testModifyHeaderCanaryAndSiteMarker() { + long addr = wb.NMTMalloc(MALLOC_SIZE); + modifyHeaderCanaryAndSiteMarker(addr); + wb.NMTFree(addr); + } + + private static void testModifyFooterCanaryAndSiteMarker() { + long addr = wb.NMTMalloc(MALLOC_SIZE); + modifyFooterCanaryAndSiteMarker(addr, MALLOC_SIZE); + wb.NMTFree(addr); + } + + public static void main(String args[]) throws Exception { + if (args != null && args.length == 1) { + switch (args[0]) { + case HEADER_ARG -> testModifyHeaderCanary(); + case FOOTER_ARG -> testModifyFooterCanary(); + case HEADER_AND_SITE_ARG -> testModifyHeaderCanaryAndSiteMarker(); + case FOOTER_AND_SITE_ARG -> testModifyFooterCanaryAndSiteMarker(); + default -> throw new RuntimeException("Invalid argument for NMTPrintMallocSiteOfCorruptedMemory (" + args[0] + ")"); + } + } else { + runThisTestWith(HEADER_ARG); + runThisTestWith(FOOTER_ARG); + runThisTestWith(HEADER_AND_SITE_ARG); + runThisTestWith(FOOTER_AND_SITE_ARG); + } + } +} diff --git a/test/hotspot/jtreg/runtime/NMT/libMallocHeaderModifier.c b/test/hotspot/jtreg/runtime/NMT/libMallocHeaderModifier.c new file mode 100644 index 00000000000..ca51b7212dd --- /dev/null +++ b/test/hotspot/jtreg/runtime/NMT/libMallocHeaderModifier.c @@ -0,0 +1,52 @@ +/* + * 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 "jni.h" +#include +#include + +JNIEXPORT jint JNICALL +Java_NMTPrintMallocSiteOfCorruptedMemory_modifyHeaderCanary(JNIEnv *env, jclass cls, jlong addr) { + *((jint*)(uintptr_t)addr - 1) = 0; + return 0; +} + +JNIEXPORT jint JNICALL +Java_NMTPrintMallocSiteOfCorruptedMemory_modifyFooterCanary(JNIEnv *env, jclass cls, jlong addr, jint size) { + *((jbyte*)(uintptr_t)addr + size + 1) = 0; + return 0; +} +JNIEXPORT jint JNICALL +Java_NMTPrintMallocSiteOfCorruptedMemory_modifyHeaderCanaryAndSiteMarker(JNIEnv *env, jclass cls, jlong addr) { + jbyte* p = (jbyte*)(uintptr_t)addr - 16; + memset(p, 0xFF , 16); + return 0; +} + +JNIEXPORT jint JNICALL +Java_NMTPrintMallocSiteOfCorruptedMemory_modifyFooterCanaryAndSiteMarker(JNIEnv *env, jclass cls, jlong addr, jint size) { + jbyte* p = (jbyte*)(uintptr_t)addr - 16; + memset(p, 0xFF , 16); + *((jbyte*)(uintptr_t)addr + size + 1) = 0; + return 0; +}