From 1ff9622f74d77baed3dc477b43759472b950d630 Mon Sep 17 00:00:00 2001 Date: Sat, 9 Nov 2024 16:25:10 +0800 Subject: SA redact support password --- .../sun/jvm/hotspot/tools/HeapDumper.java | 18 ++- .../jvm/hotspot/utilities/HeapRedactor.java | 14 +- hotspot/src/share/vm/runtime/arguments.cpp | 15 +- hotspot/src/share/vm/runtime/arguments.hpp | 4 - hotspot/src/share/vm/runtime/globals.hpp | 4 +- .../src/share/vm/services/heapRedactor.cpp | 8 +- .../src/share/vm/services/heapRedactor.hpp | 1 + .../share/classes/sun/tools/jmap/JMap.java | 150 +++++++++++++----- 8 files changed, 150 insertions(+), 64 deletions(-) diff --git a/hotspot/agent/src/share/classes/sun/jvm/hotspot/tools/HeapDumper.java b/hotspot/agent/src/share/classes/sun/jvm/hotspot/tools/HeapDumper.java index be503fe06..32c91d300 100644 --- a/hotspot/agent/src/share/classes/sun/jvm/hotspot/tools/HeapDumper.java +++ b/hotspot/agent/src/share/classes/sun/jvm/hotspot/tools/HeapDumper.java @@ -40,6 +40,9 @@ public class HeapDumper extends Tool { private static String DEFAULT_DUMP_FILE = "heap.bin"; + // encrypt + private static int SALT_MIN_LENGTH = 8; + private String dumpFile; private HeapRedactor redactor; @@ -78,8 +81,19 @@ public class HeapDumper extends Tool { public void run() { System.out.println("Dumping heap to " + dumpFile + " ..."); try { + String redactAuth = getVMRedactParameter("RedactPassword"); + boolean redactAuthFlag = true; + if(redactAuth != null) { + String[] auths = redactAuth.split(","); + if(auths.length == 2) { + byte[] saltBytes = auths[1].getBytes("UTF-8"); + if(saltBytes.length >= SALT_MIN_LENGTH) { + redactAuthFlag = (this.redactor != null && auths[0].equals(this.redactor.getRedactPassword())); + } + } + } HeapHprofBinWriter writer = new HeapHprofBinWriter(); - if(this.redactor != null){ + if(this.redactor != null && redactAuthFlag) { writer.setHeapRedactor(this.redactor); if(writer.getHeapDumpRedactLevel() != HeapRedactor.HeapDumpRedactLevel.REDACT_UNKNOWN){ System.out.println("HeapDump Redact Level = " + this.redactor.getRedactLevelString()); @@ -133,7 +147,7 @@ public class HeapDumper extends Tool { } } - HeapDumper dumper = heapRedactor == null? new HeapDumper(file):new HeapDumper(file, heapRedactor); + HeapDumper dumper = heapRedactor == null? new HeapDumper(file) : new HeapDumper(file, heapRedactor); dumper.execute(args); } diff --git a/hotspot/agent/src/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java b/hotspot/agent/src/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java index 26782b879..c71340255 100644 --- a/hotspot/agent/src/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java +++ b/hotspot/agent/src/share/classes/sun/jvm/hotspot/utilities/HeapRedactor.java @@ -51,7 +51,8 @@ public class HeapRedactor { private HeapDumpRedactLevel redactLevel; private Map redactNameTable; private Map> redactClassTable; - private String redactClassFullName = null; + private String redactClassFullName = null; + private String redactPassword = null; private Map redactValueTable; private RedactVectorNode headerNode; private RedactVectorNode currentNode; @@ -62,6 +63,7 @@ public class HeapRedactor { public static final String REDACT_MAP_PREFIX = "RedactMap="; public static final String REDACT_MAP_FILE_PREFIX = "RedactMapFile="; public static final String REDACT_CLASS_PATH_PREFIX = "RedactClassPath="; + public static final String REDACT_PASSWORD_PREFIX = "RedactPassword="; public static final String REDACT_UNKNOWN_STR = "UNKNOWN"; public static final String REDACT_OFF_STR = "OFF"; @@ -170,6 +172,10 @@ public class HeapRedactor { return redactParams.getRedactClassPath(); } + public String getRedactPassword(){ + return redactPassword; + } + public Optional> getRedactRulesTable(String key) { return Optional.>ofNullable(redactClassTable == null ? null: redactClassTable.get(key)); } @@ -231,9 +237,11 @@ public class HeapRedactor { params.setRedactMap(option.substring(REDACT_MAP_PREFIX.length())); } else if (option.startsWith(REDACT_MAP_FILE_PREFIX)) { params.setRedactMapFile(option.substring(REDACT_MAP_FILE_PREFIX.length())); - } else if (option.startsWith(REDACT_CLASS_PATH_PREFIX)) { + } else if (option.startsWith(REDACT_CLASS_PATH_PREFIX)) { params.setRedactClassPath(option.substring(REDACT_CLASS_PATH_PREFIX.length())); - }else{ + } else if (option.startsWith(REDACT_PASSWORD_PREFIX)) { + redactPassword = option.substring(REDACT_PASSWORD_PREFIX.length()); + } else{ // None matches } } diff --git a/hotspot/src/share/vm/runtime/arguments.cpp b/hotspot/src/share/vm/runtime/arguments.cpp index 360a87159..a50aa1866 100644 --- a/hotspot/src/share/vm/runtime/arguments.cpp +++ b/hotspot/src/share/vm/runtime/arguments.cpp @@ -152,8 +152,6 @@ char* Arguments::_meta_index_dir = NULL; bool Arguments::_transletEnhance = false; -char* Arguments::_heap_dump_redact_auth = NULL; - // Check if head of 'option' matches 'name', and sets 'tail' remaining part of option string static bool match_option(const JavaVMOption *option, const char* name, @@ -4183,14 +4181,13 @@ jint Arguments::parse(const JavaVMInitArgs* args) { if(match_option(option, "-XX:RedactPassword=", &tail)) { if(tail == NULL || strlen(tail) == 0) { VerifyRedactPassword = false; - jio_fprintf(defaultStream::output_stream(), "redact password is null, disable verify heap dump authority.\n"); } else { - VerifyRedactPassword = true; - size_t redact_password_len = strlen(tail); - _heap_dump_redact_auth = NEW_C_HEAP_ARRAY(char, redact_password_len+1, mtInternal); - memcpy(_heap_dump_redact_auth, tail, redact_password_len); - _heap_dump_redact_auth[redact_password_len] = '\0'; - memset((void*)tail, '0', redact_password_len); + char* split_char = strstr(const_cast(tail), ","); + VerifyRedactPassword = !(split_char == NULL || strlen(split_char) < SALT_LEN); + } + + if(!VerifyRedactPassword) { + jio_fprintf(defaultStream::output_stream(), "redact auth is null or with incorrect format, disable verify heap dump authority.\n"); } } diff --git a/hotspot/src/share/vm/runtime/arguments.hpp b/hotspot/src/share/vm/runtime/arguments.hpp index 945f487e1..fdd1d14b0 100644 --- a/hotspot/src/share/vm/runtime/arguments.hpp +++ b/hotspot/src/share/vm/runtime/arguments.hpp @@ -449,8 +449,6 @@ class Arguments : AllStatic { static char* SharedDynamicArchivePath; - static char* _heap_dump_redact_auth; - public: // Parses the arguments, first phase static jint parse(const JavaVMInitArgs* args); @@ -564,8 +562,6 @@ class Arguments : AllStatic { static const char* GetSharedDynamicArchivePath() { return SharedDynamicArchivePath; } - static const char* get_heap_dump_redact_auth() { return _heap_dump_redact_auth; } - static bool init_shared_archive_paths(); static void extract_shared_archive_paths(const char* archive_path, diff --git a/hotspot/src/share/vm/runtime/globals.hpp b/hotspot/src/share/vm/runtime/globals.hpp index 28bdd336f..b3c2f5af6 100644 --- a/hotspot/src/share/vm/runtime/globals.hpp +++ b/hotspot/src/share/vm/runtime/globals.hpp @@ -1007,8 +1007,8 @@ class CommandLineFlags { product(bool, VerifyRedactPassword, false, \ "verify authority for operating heapDump redact feature") \ \ - product(ccstr, RedactPassword, "", \ - "authority for operating heapDump redact feature") \ + product(ccstr, RedactPassword, NULL, \ + "authority for operating heapDump redact feature, format {password,salt}, salt length >= 8") \ \ develop(uintx, SegmentedHeapDumpThreshold, 2*G, \ "Generate a segmented heap dump (JAVA PROFILE 1.0.2 format) " \ diff --git a/hotspot/src/share/vm/services/heapRedactor.cpp b/hotspot/src/share/vm/services/heapRedactor.cpp index 6120e9458..8d620431e 100644 --- a/hotspot/src/share/vm/services/heapRedactor.cpp +++ b/hotspot/src/share/vm/services/heapRedactor.cpp @@ -24,7 +24,6 @@ #include "../runtime/globals.hpp" #include "../runtime/os.hpp" -#include "../runtime/arguments.hpp" #include "../utilities/ostream.hpp" #include "../memory/allocation.hpp" #include "../memory/allocation.inline.hpp" @@ -182,12 +181,15 @@ void HeapRedactor::init(outputStream* out) { * if HeapDumpRedact is NULL , jmap operation can not open redact feature without password * if HeapDumpRedact is not NULL, jmap operation can not change redact level without password **/ - if(Arguments::get_heap_dump_redact_auth() == NULL) { + char* split_char = NULL; + if(RedactPassword == NULL || (split_char = strstr(const_cast(RedactPassword), ",")) == NULL || strlen(split_char) < SALT_LEN) { VerifyRedactPassword = false; } if(VerifyRedactPassword && !_use_sys_params) { + size_t auth_len = strlen(RedactPassword); + size_t suffix_len = strlen(split_char); if(_redact_params.redact_password == NULL || - strcmp(_redact_params.redact_password, Arguments::get_heap_dump_redact_auth()) ) { + strncmp(_redact_params.redact_password, RedactPassword, auth_len-suffix_len) ) { // no password or wrong password; _use_sys_params = true; if(out != NULL) { diff --git a/hotspot/src/share/vm/services/heapRedactor.hpp b/hotspot/src/share/vm/services/heapRedactor.hpp index 06ffcc830..ed53d7a03 100644 --- a/hotspot/src/share/vm/services/heapRedactor.hpp +++ b/hotspot/src/share/vm/services/heapRedactor.hpp @@ -32,6 +32,7 @@ #endif #define MAX_MAP_FILE_LENGTH 1024 +#define SALT_LEN 9 enum HeapDumpRedactLevel { REDACT_UNKNOWN, diff --git a/jdk/src/share/classes/sun/tools/jmap/JMap.java b/jdk/src/share/classes/sun/tools/jmap/JMap.java index b184beb74..319f577bd 100644 --- a/jdk/src/share/classes/sun/tools/jmap/JMap.java +++ b/jdk/src/share/classes/sun/tools/jmap/JMap.java @@ -31,14 +31,19 @@ import java.lang.reflect.Method; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; import java.util.Arrays; import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.AttachNotSupportedException; import sun.tools.attach.HotSpotVirtualMachine; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + /* * This class is the main class for the JMap utility. It parses its arguments * and decides if the command should be satisfied using the VM attach mechanism @@ -64,6 +69,11 @@ public class JMap { // Default option (if nothing provided) private static String DEFAULT_OPTION = "-pmap"; + // encrypt + private static int SALT_MIN_LENGTH = 8; + private static int HASH_BIT_SIZE = 256; + private static int HASH_ITERATIONS_COUNT = 10000; + public static void main(String[] args) throws Exception { if (args.length == 0) { usage(1); // no arguments @@ -169,7 +179,8 @@ public class JMap { if (option.startsWith(DUMP_OPTION_PREFIX)) { // first check that the option can be parsed RedactParams redactParams = new RedactParams(); - String fn = parseDumpOptions(option, redactParams); + String pid = args.length == 1 ? args[0] : null; + String fn = parseDumpOptions(option, redactParams, pid); if (fn == null) { usage(1); } @@ -258,16 +269,10 @@ public class JMap { private static void dump(String pid, String options) throws IOException { RedactParams redactParams = new RedactParams(); // parse the options to get the dump filename - String filename = parseDumpOptions(options,redactParams); + String filename = parseDumpOptions(options, redactParams, pid); if (filename == null) { usage(1); // invalid options or no filename } - - String redactPassword = ",RedactPassword="; - if (options.contains("RedactPassword,") || options.contains(",RedactPassword")) { - // heap dump may need a password - redactPassword = getRedactPassword(); - } // get the canonical path - important to avoid just passing // a "heap.bin" and having the dump created in the target VM // working directory rather than the directory where jmap @@ -282,12 +287,12 @@ public class JMap { InputStream in = ((HotSpotVirtualMachine)vm). dumpHeap((Object)filename, (live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION), - redactParams.isEnableRedact() ? redactParams.toDumpArgString() + redactPassword : ""); + redactParams.isEnableRedact() ? redactParams.toDumpArgString() : ""); drain(vm, in); } - private static String getRedactPassword() { - String redactPassword = ",RedactPassword="; + private static String getRedactPassword(String pid) { + String redactPassword = ""; Console console = System.console(); char[] passwords = null; if (console == null) { @@ -305,53 +310,97 @@ public class JMap { String password = new String(passwords); Arrays.fill(passwords, '0'); String passwordPattern = "^[0-9a-zA-Z!@#$]{1,9}$"; - if(!password.matches(passwordPattern)) { - return redactPassword; - } String digestStr = null; - byte[] passwordBytes = null; char[] passwordValue = null; try { Field valueField = password.getClass().getDeclaredField("value"); valueField.setAccessible(true); passwordValue = (char[])valueField.get(password); - passwordBytes= password.getBytes(StandardCharsets.UTF_8); - StringBuilder digestStrBuilder = new StringBuilder(); - MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); - byte[] digestBytes = messageDigest.digest(passwordBytes); - for(byte b : digestBytes) { - String hex = Integer.toHexString(0xff & b); - if(hex.length() == 1) { - digestStrBuilder.append('0'); - } - digestStrBuilder.append(hex); + if(!password.matches(passwordPattern)) { + return redactPassword; + } + + String salt = getSalt(pid); + if(salt == null) { + return redactPassword; + } + byte[] saltBytes = salt.getBytes("UTF-8"); + if(saltBytes.length < SALT_MIN_LENGTH) { + return redactPassword; } - digestStr = digestStrBuilder.toString(); + + digestStr = getEncryptValue(passwordValue, saltBytes); } catch (Exception e) { }finally { // clear all password - if(passwordBytes != null) { - Arrays.fill(passwordBytes, (byte) 0); - } if(passwordValue != null) { Arrays.fill(passwordValue, '0'); } } - redactPassword += (digestStr == null ? "" : digestStr); + redactPassword = (digestStr == null ? "" : digestStr); return redactPassword; } + private static String getSalt(String pid) throws Exception { + String salt = null; + StringBuilder redactAuth = new StringBuilder(); + + VirtualMachine vm = VirtualMachine.attach(pid); + HotSpotVirtualMachine hvm = (HotSpotVirtualMachine) vm; + String flag = "RedactPassword"; + try (InputStream in = hvm.printFlag(flag)) { + byte b[] = new byte[256]; + int n; + do { + n = in.read(b); + if (n > 0) { + redactAuth.append(new String(b, 0, n, "UTF-8")); + } + } while (n > 0); + } + vm.detach(); + + if(redactAuth.length() > 0) { + String[] auths = redactAuth.toString().split(","); + if(auths.length != 2) { + return salt; + } + return auths[1].trim(); + } + + return salt; + } + + private static String getEncryptValue(char[] passwordValue, byte[] saltBytes) throws InvalidKeySpecException, NoSuchAlgorithmException { + StringBuilder digestStrBuilder = new StringBuilder(); + + KeySpec spec = new PBEKeySpec(passwordValue, saltBytes, HASH_ITERATIONS_COUNT, HASH_BIT_SIZE); + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + SecretKey secretKey = secretKeyFactory.generateSecret(spec); + byte[] digestBytes = secretKey.getEncoded(); + for (byte b : digestBytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + digestStrBuilder.append('0'); + } + digestStrBuilder.append(hex); + } + String digestStr = digestStrBuilder.toString(); + + return digestStr; + } + // Parse the options to the -dump option. Valid options are format=b and // file=. Returns if provided. Returns null if not // provided, or invalid option. private static String parseDumpOptions(String arg){ - return parseDumpOptions(arg, null); + return parseDumpOptions(arg, null, null); } - private static String parseDumpOptions(String arg, RedactParams redactParams) { + private static String parseDumpOptions(String arg, RedactParams redactParams, String pid) { assert arg.startsWith(DUMP_OPTION_PREFIX); String filename = null; @@ -366,8 +415,6 @@ public class JMap { // ignore format (not needed at this time) } else if (option.equals("live")) { // a valid suboption - } else if (option.equals("RedactPassword")) { - // ignore this option, just suit the parse rule } else { // file= - check that is specified if (option.startsWith("file=")) { @@ -376,7 +423,7 @@ public class JMap { return null; } } else { - if (redactParams != null && initRedactParams(redactParams, option)) { + if (redactParams != null && initRedactParams(redactParams, option, pid)) { continue; } return null; // option not recognized @@ -397,7 +444,7 @@ public class JMap { return filename; } - private static boolean initRedactParams(RedactParams redactParams, String option) { + private static boolean initRedactParams(RedactParams redactParams, String option, String pid) { if (option.startsWith("HeapDumpRedact=")) { if (!redactParams.setAndCheckHeapDumpRedact(option.substring("HeapDumpRedact=".length()))) { usage(1); @@ -409,9 +456,14 @@ public class JMap { } else if (option.startsWith("RedactMapFile=")) { redactParams.setRedactMapFile(option.substring("RedactMapFile=".length())); return true; - } else if (option.startsWith("RedactClassPath")) { + } else if (option.startsWith("RedactClassPath=")) { redactParams.setRedactClassPath(option.substring("RedactClassPath=".length())); return true; + } else if (option.startsWith("RedactPassword")) { + // heap dump may need a password + String redactPassword = getRedactPassword(pid); + redactParams.setRedactPassword(redactPassword); + return true; } else { // None matches return false; @@ -544,11 +596,12 @@ public class JMap { private String redactMap; private String redactMapFile; private String redactClassPath; + private String redactPassword; public RedactParams() { } - public RedactParams(String heapDumpRedact, String redactMap, String redactMapFile, String redactClassPath) { + public RedactParams(String heapDumpRedact, String redactMap, String redactMapFile, String redactClassPath, String redactPassword) { if (heapDumpRedact != null && checkLauncherHeapdumpRedactSupport(heapDumpRedact)) { enableRedact = true; } @@ -556,6 +609,7 @@ public class JMap { this.redactMap = redactMap; this.redactMapFile = redactMapFile; this.redactClassPath = redactClassPath; + this.redactPassword = redactPassword; } @Override @@ -579,6 +633,11 @@ public class JMap { if (redactClassPath != null) { builder.append("RedactClassPath="); builder.append(redactClassPath); + builder.append(","); + } + if (redactPassword != null) { + builder.append("RedactPassword="); + builder.append(redactPassword); } return builder.toString(); } @@ -587,7 +646,8 @@ public class JMap { return "-HeapDumpRedact=" + (heapDumpRedact == null ? "off" : heapDumpRedact) + ",RedactMap=" + (redactMap == null ? "" : redactMap) + ",RedactMapFile=" + (redactMapFile == null ? "" : redactMapFile) + - ",RedactClassPath=" + (redactClassPath == null ? "" : redactClassPath); + ",RedactClassPath=" + (redactClassPath == null ? "" : redactClassPath) + + ",RedactPassword=" + (redactPassword == null ? "" : redactPassword); } public static boolean checkLauncherHeapdumpRedactSupport(String value) { @@ -644,5 +704,13 @@ public class JMap { public void setRedactClassPath(String redactClassPath) { this.redactClassPath = redactClassPath; } + + public String getRedactPassword() { + return redactPassword; + } + + public void setRedactPassword(String redactPassword) { + this.redactPassword = redactPassword; + } } } -- 2.22.0