diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp index 549d879de99..a41515edfbb 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -27,6 +27,7 @@ #include "jfr/dcmd/jfrDcmds.hpp" #include "jfr/jfr.hpp" #include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/periodic/jfrRedactedEvents.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" #include "jfr/support/jfrThreadLocal.hpp" @@ -401,11 +402,25 @@ JfrConfigureFlightRecorderDCmd::JfrConfigureFlightRecorderDCmd(outputStream* out }; void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const { - outputStream* out = output(); + print_help(output(), false); +} + +static void print_filters(outputStream* out, JfrRedactedEvents::StringArray* filters) { + for (int i = 0; i < filters->length(); i++) { + out->print_cr(" %s", filters->at(i)->text()); + } +} + +void JfrConfigureFlightRecorderDCmd::print_help(outputStream* out, bool startup) { // 0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890 + if (startup) { + out->print_cr("Syntax : -XX:FlightRecorderOptions:[options]"); + out->print_cr(""); + } out->print_cr("Options:"); out->print_cr(""); - out->print_cr(" globalbuffercount (Optional) Number of global buffers. This option is a legacy"); + out->print_cr( " %-19s (Optional) Number of global buffers. This option is a legacy", + startup ? "numglobalbuffers": "globalbuffercount"); out->print_cr(" option: change the memorysize parameter to alter the number of"); out->print_cr(" global buffers. This value cannot be changed once JFR has been"); out->print_cr(" initialized. (STRING, default determined by the value for"); @@ -427,7 +442,8 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const { out->print_cr(" gigabytes. This value cannot be changed once JFR has been"); out->print_cr(" initialized. (STRING, 10M)"); out->print_cr(""); - out->print_cr(" repositorypath (Optional) Path to the location where recordings are stored until"); + out->print_cr( " %-19s (Optional) Path to the location where recordings are stored until", + startup ? "repository" : "repositorypath"); out->print_cr(" they are written to a permanent file. (STRING, The default"); out->print_cr(" location is the temporary directory for the operating system. On"); out->print_cr(" Linux operating systems, the temporary directory is /tmp. On"); @@ -443,7 +459,8 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const { out->print_cr(" degradation. This value cannot be changed once JFR has been"); out->print_cr(" initialized. (LONG, 64)"); out->print_cr(""); - out->print_cr(" thread_buffer_size (Optional) Local buffer size for each thread in bytes if one of"); + out->print_cr( " %-19s (Optional) Local buffer size for each thread in bytes if one of", + startup ? "threadbuffersize" : "thread_buffer_size"); out->print_cr(" the following suffixes is not used: 'k' or 'K' for kilobytes or"); out->print_cr(" 'm' or 'M' for megabytes. Overriding this parameter could reduce"); out->print_cr(" performance and is not recommended. This value cannot be changed"); @@ -452,14 +469,77 @@ void JfrConfigureFlightRecorderDCmd::print_help(const char* name) const { out->print_cr(" preserve-repository (Optional) Preserve files stored in the disk repository after the"); out->print_cr(" Java Virtual Machine has exited. (BOOLEAN, false)"); out->print_cr(""); - out->print_cr("Options must be specified using the or = syntax."); - out->print_cr(""); - out->print_cr("Example usage:"); - out->print_cr(""); - out->print_cr(" $ jcmd JFR.configure"); - out->print_cr(" $ jcmd JFR.configure repositorypath=/temporary"); - out->print_cr(" $ jcmd JFR.configure stackdepth=256"); - out->print_cr(" $ jcmd JFR.configure memorysize=100M"); + if (startup) { + out->print_cr(" old-object-queue-size (Optional) Maximum number of old objects to track. By default,"); + out->print_cr(" the number of objects is set to 256. (LONG, 256)"); + out->print_cr(""); + out->print_cr(" redact-argument (Optional) Replace command-line arguments that match a"); + out->print_cr(" semicolon-separated list of glob patterns, for example,"); + out->print_cr(" *secret*;password*. Matching is case-insensitive, and the"); + out->print_cr(" supported wildcards are '*' and '?'. To redact multiple arguments,"); + out->print_cr(" use a literal space (' ') as a separator. For example, to match"); + out->print_cr(" the two arguments --auth username:token, use the filter"); + out->print_cr(" --auth *:*. Filters containing spaces must be quoted as a single"); + out->print_cr(" command-line argument, for example,"); + out->print_cr(" -XX:FlightRecorderOptions:'redact-argument=--auth *:*'."); + out->print_cr(" Arguments containing spaces might not be matched as expected."); + out->print_cr(" The option redact-argument is best-effort and applies only to"); + out->print_cr(" command-line arguments in the jdk.JVMInformation event and to"); + out->print_cr(" the java.command system property in the jdk.InitialSystemProperty"); + out->print_cr(" event. Other events, such as jdk.ProcessStart (child processes),"); + out->print_cr(" are not redacted."); + out->print_cr(""); + out->print_cr(" If the redact-argument option is not specified, the following"); + out->print_cr(" filters are used by default:"); + out->print_cr(""); + print_filters(out, JfrRedactedEvents::argument_filters()); + out->print_cr(""); + out->print_cr(" To load patterns from a file (one per line), use @."); + out->print_cr(" To add to the default patterns instead of replacing them, prefix"); + out->print_cr(" the whole list with '+', for example, +*foo*;@redact.txt."); + out->print_cr(" Use 'none' (lowercase) to disable all redaction filters for"); + out->print_cr(" command-line arguments. Redacted arguments will be replaced"); + out->print_cr(" with '[REDACTED]'. (STRING, default filters)"); + out->print_cr(""); + out->print_cr(" redact-key (Optional) Replace the value of environment variables and system"); + out->print_cr(" properties whose key matches a semicolon-separated list of glob"); + out->print_cr(" patterns, for example, *password*;*token*. Matching is"); + out->print_cr(" case-insensitive, and the supported wildcards are '*' and '?'."); + out->print_cr(" The option redact-key is best-effort and applies only to the"); + out->print_cr(" jdk.InitialSystemProperty, jdk.InitialEnvironmentVariable and"); + out->print_cr(" jdk.JVMInformation (-Dkey...) events. Other events, such as"); + out->print_cr(" jdk.InitialSecurityProperty, are not redacted."); + out->print_cr(""); + out->print_cr(" If the redact-key option is not specified, the"); + out->print_cr(" following filters are used by default:"); + out->print_cr(""); + print_filters(out, JfrRedactedEvents::key_filters()); + out->print_cr(""); + out->print_cr(" To load patterns from a file (one per line), use @."); + out->print_cr(" To add to the default patterns instead of replacing them, prefix"); + out->print_cr(" the whole list with '+', for example, +*cred*;@keys.txt."); + out->print_cr(" Use 'none' (lowercase) to disable all redaction filters for key"); + out->print_cr(" matching. Redacted values will be replaced with '[REDACTED]'."); + out->print_cr(" (STRING, default filters)"); + out->print_cr(""); + out->print_cr("Options must be specified using the = syntax. Multiple options are separated"); + out->print_cr("with a comma."); + out->print_cr(""); + out->print_cr("Example usage:"); + out->print_cr(""); + out->print_cr(" -XX:FlightRecorderOptions:repository=/temporary,stackdepth=256"); + out->print_cr(" -XX:FlightRecorderOptions:'redact-key=+*confidential*;*private*,redact-argument=+https://*:*@*'"); + out->print_cr(" -XX:FlightRecorderOptions:'redact-argument=+-*private *'"); + } else { + out->print_cr("Options must be specified using the or = syntax."); + out->print_cr(""); + out->print_cr("Example usage:"); + out->print_cr(""); + out->print_cr(" $ jcmd JFR.configure"); + out->print_cr(" $ jcmd JFR.configure repositorypath=/temporary"); + out->print_cr(" $ jcmd JFR.configure stackdepth=256"); + out->print_cr(" $ jcmd JFR.configure memorysize=100M"); + } out->print_cr(""); } diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp index 8e7dad5a20c..aecd187a2e0 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -202,6 +202,7 @@ class JfrConfigureFlightRecorderDCmd : public DCmdWithParser { return "Low"; } static int num_arguments() { return 10; } + static void print_help(outputStream* out, bool startup); virtual void execute(DCmdSource source, TRAPS); virtual void print_help(const char* name) const; }; diff --git a/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp b/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp index 18b2d7c5785..71bcf7e02bf 100644 --- a/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp +++ b/src/hotspot/share/jfr/periodic/jfrOSInterface.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -268,37 +268,6 @@ const char* JfrOSInterface::virtualization_name() { return "No virtualization detected"; } -int JfrOSInterface::generate_initial_environment_variable_events() { - if (os::get_environ() == nullptr) { - return OS_ERR; - } - - if (EventInitialEnvironmentVariable::is_enabled()) { - // One time stamp for all events, so they can be grouped together - JfrTicks time_stamp = JfrTicks::now(); - for (char** p = os::get_environ(); *p != nullptr; p++) { - char* variable = *p; - char* equal_sign = strchr(variable, '='); - if (equal_sign != nullptr) { - // Extract key/value - ResourceMark rm; - ptrdiff_t key_length = equal_sign - variable; - char* key = NEW_RESOURCE_ARRAY(char, key_length + 1); - char* value = equal_sign + 1; - strncpy(key, variable, key_length); - key[key_length] = '\0'; - EventInitialEnvironmentVariable event(UNTIMED); - event.set_starttime(time_stamp); - event.set_endtime(time_stamp); - event.set_key(key); - event.set_value(value); - event.commit(); - } - } - } - return OS_OK; -} - int JfrOSInterface::system_processes(SystemProcess** sys_processes, int* no_of_sys_processes) { return instance()._impl->system_processes(sys_processes, no_of_sys_processes); } diff --git a/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp b/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp index 141e7505efb..9e8d4837a52 100644 --- a/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp +++ b/src/hotspot/share/jfr/periodic/jfrOSInterface.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -53,7 +53,6 @@ class JfrOSInterface: public JfrCHeapObj { static int cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad); static int os_version(char** os_version); static const char* virtualization_name(); - static int generate_initial_environment_variable_events(); static int system_processes(SystemProcess** system_processes, int* no_of_sys_processes); static int network_utilization(NetworkInterface** network_interfaces); }; diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp index 92f406bd095..83d58c704fc 100644 --- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp @@ -41,6 +41,7 @@ #include "jfr/periodic/jfrNativeMemoryEvent.hpp" #include "jfr/periodic/jfrNetworkUtilization.hpp" #include "jfr/periodic/jfrOSInterface.hpp" +#include "jfr/periodic/jfrRedactedEvents.hpp" #include "jfr/periodic/jfrThreadCPULoadEvent.hpp" #include "jfr/periodic/jfrThreadDumpEvent.hpp" #include "jfr/recorder/jfrRecorder.hpp" @@ -100,17 +101,8 @@ TRACE_REQUEST_FUNC(ResidentSetSize) { } TRACE_REQUEST_FUNC(JVMInformation) { - ResourceMark rm; - EventJVMInformation event; - event.set_jvmName(VM_Version::vm_name()); - event.set_jvmVersion(VM_Version::internal_vm_info_string()); - event.set_javaArguments(Arguments::java_command()); - event.set_jvmArguments(Arguments::jvm_args()); - event.set_jvmFlags(Arguments::jvm_flags()); - event.set_jvmStartTime(Management::vm_init_done_time()); - event.set_pid(os::current_process_id()); - event.commit(); - } + JfrRedactedEvents::emit_jvm_information(); +} TRACE_REQUEST_FUNC(OSInformation) { ResourceMark rm; @@ -169,7 +161,9 @@ TRACE_REQUEST_FUNC(NativeLibrary) { } TRACE_REQUEST_FUNC(InitialEnvironmentVariable) { - JfrOSInterface::generate_initial_environment_variable_events(); + if (!JfrRedactedEvents::emit_initial_environment_variables()) { + log_debug(jfr, system)( "Unable to generate periodic event InitialEnvironmentVariable"); + } } TRACE_REQUEST_FUNC(CPUInformation) { @@ -393,7 +387,7 @@ TRACE_REQUEST_FUNC(BooleanFlag) { } TRACE_REQUEST_FUNC(StringFlag) { - SEND_FLAGS_OF_TYPE(StringFlag, ccstr); + JfrRedactedEvents::emit_string_flags(); } class VM_GC_SendObjectCountEvent : public VM_GC_HeapInspection { @@ -476,19 +470,7 @@ TRACE_REQUEST_FUNC(YoungGenerationConfiguration) { } TRACE_REQUEST_FUNC(InitialSystemProperty) { - SystemProperty* p = Arguments::system_properties(); - JfrTicks time_stamp = JfrTicks::now(); - while (p != nullptr) { - if (!p->internal()) { - EventInitialSystemProperty event(UNTIMED); - event.set_key(p->key()); - event.set_value(p->value()); - event.set_starttime(time_stamp); - event.set_endtime(time_stamp); - event.commit(); - } - p = p->next(); - } + JfrRedactedEvents::emit_initial_system_properties(); } TRACE_REQUEST_FUNC(ThreadAllocationStatistics) { diff --git a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp new file mode 100644 index 00000000000..5ace0d0fae4 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp @@ -0,0 +1,663 @@ +/* + * Copyright (c) 2026, 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 "jfr/jfrEvents.hpp" +#include "jfr/periodic/jfrRedactedEvents.hpp" +#include "jfr/utilities/jfrTime.hpp" +#include "logging/log.hpp" +#include "logging/logMessage.hpp" +#include "runtime/arguments.hpp" +#include "runtime/flags/jvmFlag.hpp" +#include "runtime/os.hpp" +#include "runtime/vm_version.hpp" +#include "services/diagnosticArgument.hpp" +#include "services/diagnosticFramework.hpp" +#include "services/management.hpp" +#include "utilities/ostream.hpp" + +#include +#include +#include + +using StringArray = JfrRedactedEvents::StringArray; +using String = JfrRedactedEvents::String; +using StringFlag = JfrRedactedEvents::StringFlag; +using StringKeyValueArray = GrowableArray*; + +static const char REDACTED[] = "[REDACTED]"; +static const char DELIMITER[] = " "; +static const char REDACT_ARGUMENT_EQUAL[] = "redact-argument="; + +static const size_t REDACTED_LENGTH = sizeof(REDACTED) -1; +static const size_t DELIMITER_LENGTH = sizeof(DELIMITER) -1; +static const size_t REDACT_ARGUMENT_EQUAL_LENGTH = sizeof(REDACT_ARGUMENT_EQUAL) -1; + +String* JfrRedactedEvents::_redacted_java_command_line = nullptr; +String* JfrRedactedEvents::_redacted_jvm_command_line = nullptr; +String* JfrRedactedEvents::_redacted_flags_command_line = nullptr; +String* JfrRedactedEvents::_redacted_flight_recorder_options = nullptr; + +StringKeyValueArray JfrRedactedEvents::_initial_environment_variables = nullptr; +StringKeyValueArray JfrRedactedEvents::_initial_system_properties = nullptr; +GrowableArray* JfrRedactedEvents::_string_flags = nullptr; + +StringArray* JfrRedactedEvents::_redacted_arguments = nullptr; +StringArray* JfrRedactedEvents::_argument_filters = nullptr; +StringArray* JfrRedactedEvents::_key_filters = nullptr; + +bool JfrRedactedEvents::_initialized = false; + +bool JfrRedactedEvents::set_argument_filter(const char* filters) { + assert (_argument_filters == nullptr, "invariant"); + assert (filters != nullptr, "invariant"); + _argument_filters = new StringArray(); + return append_filters(_argument_filters, true, filters); +} + +bool JfrRedactedEvents::set_key_filter(const char* filters) { + assert (_key_filters == nullptr, "invariant"); + assert (filters != nullptr, "invariant"); + _key_filters = new StringArray(); + return append_filters(_key_filters, false, filters); +} + +void JfrRedactedEvents::log_redaction() { + //At this point JFR is not enabled so events will not be emitted + ensure_initialized(); + emit_initial_environment_variables(true); + emit_initial_system_properties(true); + emit_jvm_information(true); + emit_string_flags(true); +} + +StringArray* JfrRedactedEvents::key_filters() { + ensure_initialized(); + return _key_filters; +} + +StringArray* JfrRedactedEvents::argument_filters() { + ensure_initialized(); + return _argument_filters; +} + +void JfrRedactedEvents::add_default_filters(StringArray* target, bool argument) { + if (argument) { + target->add("-*api*key *"); + target->add("-*client*secret *"); + target->add("-*credential *"); + target->add("-*jaas*config *"); + target->add("-*passphrase *"); + target->add("-*passwd *"); + target->add("-*password *"); + target->add("-*private*key *"); + target->add("-*pwd *"); + target->add("-*secret *"); + target->add("-*token *"); + } else { + target->add("*auth*"); + } + target->add("*api*key*"); + target->add("*client*secret*"); + target->add("*credential*"); + target->add("*jaas*config*"); + target->add("*passphrase*"); + target->add("*passwd*"); + target->add("*password*"); + target->add("*private*key*"); + target->add("*pwd*"); + target->add("*secret*"); + target->add("*token*"); +} + +bool JfrRedactedEvents::append_filters(StringArray* target, bool argument, const char* filters) { + const char* option_name = argument ? "redact-argument": "redact-key"; + + if (filters == nullptr) { + log_warning(jfr, redact)("Missing value for option -XX:FlightRecorderOptions:%s", option_name); + return false; + } + if (filters[0] == '\0') { + LogMessage(jfr, redact) msg; + msg.warning("Default redaction filters are replaced. Specify:"); + msg.warning("-XX:FlightRecorderOptions:%s=none to disable filters without a warning.", option_name); + return true; + } + if (strcmp(filters, "none") == 0) { + return true; + } + if (filters[0] == '+') { + filters++; + add_default_filters(target, argument); + } else { + if (strncmp(filters, "none;", 5) != 0) { + LogMessage(jfr, redact) msg; + msg.warning("Default redaction filters are replaced. Prepend with '+' to add filters to the"); + msg.warning("defaults, or specify -XX:FlightRecorderOptions:'%s=none;'", option_name); + msg.warning("to replace without a warning."); + } else { + filters += 5; + } + } + StringArray* filter_array = split(filters, ';'); + bool success = true; + for (int i = 0; i < filter_array->length(); i++) { + const String* filter = filter_array->at(i); + if (strcmp(filter->text(), "none") == 0) { + log_error(jfr, redact)("'none' can only be used as the first filter."); + success = false; + break; + } + if (filter->at(0) == '@') { + const char* filename = filter->text() + 1; + if (!read_file(target, filename)) { + success = false; + } + } else { + target->add_contents(filter); + } + } + delete filter_array; + return success; +} + +char* JfrRedactedEvents::new_redacted_text() { + char* result = NEW_C_HEAP_ARRAY(char, REDACTED_LENGTH + 1, mtTracing); + strncpy(result, REDACTED, REDACTED_LENGTH + 1); + return result; +} + +bool JfrRedactedEvents::emit_initial_environment_variables(bool log) { + if (_initial_environment_variables == nullptr) { + ensure_initialized(); + char** envp = os::get_environ(); + if (envp == nullptr) { + return false; + } + _initial_environment_variables = make_array(0); + for (char** p = envp; *p != nullptr; ++p) { + char* variable = *p; + char* equal_sign = strchr(variable, '='); + if (equal_sign != nullptr) { + ptrdiff_t length = equal_sign - variable; + String* key = new String(variable, (size_t)length); + const char* value = equal_sign + 1; + if (is_redacted_key(key->text())) { + value = REDACTED; + if (log) { + log_debug(jfr, redact)("Redacted initial environment variable named '%s'", key->text()); + } + } + _initial_environment_variables->append(new StringKeyValue(key, value)); + } + } + } + JfrTicks time_stamp = JfrTicks::now(); + for (int i = 0; i < _initial_environment_variables->length(); i++) { + StringKeyValue* pair = _initial_environment_variables->at(i); + EventInitialEnvironmentVariable event(UNTIMED); + event.set_starttime(time_stamp); + event.set_endtime(time_stamp); + event.set_key(pair->key()); + event.set_value(pair->value()); + event.commit(); + } + return true; +} + +void JfrRedactedEvents::emit_initial_system_properties(bool log) { + if (_initial_system_properties == nullptr) { + ensure_initialized(); + _initial_system_properties = make_array(0); + for (SystemProperty* p = Arguments::system_properties(); p != nullptr; p = p->next()) { + if (!p->internal()) { + const char* value = p->value(); + if (strcmp(p->key(), "sun.java.command") == 0) { + value = String::c_str(_redacted_java_command_line); + } + if (is_redacted_key(p->key())) { + value = REDACTED; + } + if (log && value != nullptr && strstr(value, REDACTED) != nullptr) { + log_debug(jfr, redact)("Redacted initial system property named '%s'", p->key()); + } + _initial_system_properties->append(new StringKeyValue(p->key(), value)); + } + } + } + + JfrTicks time_stamp = JfrTicks::now(); + for (int i = 0; i < _initial_system_properties->length(); i++) { + StringKeyValue* pair = _initial_system_properties->at(i); + EventInitialSystemProperty event(UNTIMED); + event.set_key(pair->key()); + event.set_value(pair->value()); + event.set_starttime(time_stamp); + event.set_endtime(time_stamp); + event.commit(); + } +} + +bool JfrRedactedEvents::match_flag(const char* flag_name, const char* arg) { + if (flag_name == nullptr || arg == nullptr) { + return false; + } + while (*flag_name) { + if (*arg != *flag_name) { + return false; + } + flag_name++; + arg++; + } + return *arg == '\0' || *arg == '='; +} + +void JfrRedactedEvents::emit_string_flags(bool log) { + if (_string_flags == nullptr) { + ensure_initialized(); + _string_flags = make_array(0); + JVMFlag *flag = JVMFlag::flags; + while (flag->name() != nullptr) { + if (flag->is_ccstr() && flag->is_unlocked()) { + const char* value = nullptr; + bool redacted = false; + for (int i = 0; i < _redacted_arguments->length(); i++) { + if (match_flag(flag->name(), _redacted_arguments->at(i)->text())) { + value = REDACTED; + redacted = true; + break; + } + } + if (strcmp("FlightRecorderOptions", flag->name()) == 0) { + if (_redacted_flight_recorder_options != nullptr) { + value = _redacted_flight_recorder_options->text(); + redacted = true; + } + } + if (log && redacted) { + log_debug(jfr, redact)("Redacted string flag '%s'", flag->name()); + } + _string_flags->append(new StringFlag(flag, value)); + } + ++flag; + } + } + for (int i = 0; i < _string_flags->length(); i++) { + StringFlag* sf = _string_flags->at(i); + const JVMFlag* flag = sf->jvm_flag(); + EventStringFlag event; + event.set_name(flag->name()); + if (sf->redacted_value() != nullptr) { + // If a flag is redacted and later changed at runtime, + // the event will still show the redacted text. + event.set_value(sf->redacted_value()); + } else { + // If a flag is not redacted the first time, + // it will not be redacted later if it's changed at runtime. + event.set_value(flag->get_ccstr()); + } + event.set_origin(static_cast(flag->get_origin())); + event.commit(); + } +} + +void JfrRedactedEvents::log_jvm_information_redaction(const char* type, const String* redacted) { + const char* text = String::c_str(redacted); + if (text != nullptr && strstr(text, REDACTED) != nullptr) { + log_debug(jfr, redact)("Redacted %s '%s'", type, text); + } +} + +void JfrRedactedEvents::emit_jvm_information(bool log) { + ensure_initialized(); + EventJVMInformation event; + event.set_jvmName(VM_Version::vm_name()); + event.set_jvmVersion(VM_Version::internal_vm_info_string()); + event.set_javaArguments(String::c_str(_redacted_java_command_line)); + event.set_jvmArguments(String::c_str(_redacted_jvm_command_line)); + event.set_jvmFlags(String::c_str(_redacted_flags_command_line)); + event.set_jvmStartTime(Management::vm_init_done_time()); + event.set_pid(os::current_process_id()); + event.commit(); + if (log) { + log_jvm_information_redaction("Java Arguments", _redacted_java_command_line); + log_jvm_information_redaction("JVM Arguments", _redacted_jvm_command_line); + log_jvm_information_redaction("JVM Flags", _redacted_flags_command_line); + } +} + +void JfrRedactedEvents::ensure_initialized() { + if (_initialized) { + return; + } + if (_key_filters == nullptr) { + _key_filters = new StringArray(); + add_default_filters(_key_filters, false); + } + if (_argument_filters == nullptr) { + _argument_filters = new StringArray(); + add_default_filters(_argument_filters, true); + } + if (FlightRecorderOptions != nullptr) { + if (strstr(FlightRecorderOptions, REDACT_ARGUMENT_EQUAL) != nullptr) { + DCmdIter iterator(FlightRecorderOptions, ','); + stringStream result; + size_t pos = 0; + while(iterator.has_next()) { + CmdLine line = iterator.next(); + const char* start = line.cmd_addr(); + if (strncmp(start, REDACT_ARGUMENT_EQUAL, REDACT_ARGUMENT_EQUAL_LENGTH) == 0) { + result.print(REDACT_ARGUMENT_EQUAL); + result.print(REDACTED); + // Preserve ',' if there are more tokens + pos = iterator.has_next() ? iterator.cursor() - 1 : iterator.cursor(); + } + while (pos < iterator.cursor()) { + result.write(FlightRecorderOptions + pos, 1); + pos++; + } + } + _redacted_flight_recorder_options = new String(result.base()); + } else { + _redacted_flight_recorder_options = new String(FlightRecorderOptions); + } + } + + _redacted_arguments = new StringArray(); + + StringArray* java_args = make_java_args_array(); + _redacted_java_command_line = redact_command_line(java_args); + delete java_args; + + StringArray* jvm_args = make_jvm_args_array(Arguments::jvm_args_array(), Arguments::num_jvm_args()); + _redacted_jvm_command_line = redact_command_line(jvm_args); + delete jvm_args; + + StringArray* flags_args = make_jvm_args_array(Arguments::jvm_flags_array(), Arguments::num_jvm_flags()); + _redacted_flags_command_line = redact_command_line(flags_args); + delete flags_args; + + _initialized = true; +} + +String* JfrRedactedEvents::redact_command_line(StringArray* arguments) { + if (arguments == nullptr) { + return nullptr; + } + GrowableArray* filters = make_array(_argument_filters->length()); + for (int i = 0 ; i < _argument_filters->length(); i++) { + filters->append(make_filter_array(_argument_filters->at(i)->text())); + } + StringArray* result = new StringArray(); + int arg_index = 0; + while (arg_index < arguments->length()) { + int next_index = arg_index; + for (int i = 0; i < filters->length(); i++) { + next_index = match_arguments(filters->at(i), arguments, arg_index); + if (next_index > arg_index) { + break; + } + } + if (next_index > arg_index) { + for (int j = arg_index; j < next_index; j++) { + result->add(REDACTED); + const char* arg = arguments->at(j)->text(); + if (arg != nullptr && strncmp(arg, "-XX:", 4) == 0) { + _redacted_arguments->add(arg + 4); + } + } + arg_index = next_index; + } else { + result->add_contents(arguments->at(arg_index)); + arg_index++; + } + } + destroy_array(filters); + String* ret = concatenate(result); + delete result; + return ret; +} + +String* JfrRedactedEvents::concatenate(StringArray* strings) { + size_t length = 0; + for (int i = 0; i < strings->length(); i++) { + length += strings->at(i)->length(); + if (i != 0) { + length += 1; + } + } + String* text = new String(length); + size_t index = 0; + for (int i = 0; i < strings->length(); i++) { + if (i != 0) { + text->set(index++, ' '); + } + const String* s = strings->at(i); + size_t len = s->length(); + for (size_t j = 0; j < len; j++) { + text->set(index++, s->at(j)); + } + } + return text; +} + +bool JfrRedactedEvents::equals_case_insensitive(char a, char b) { + return tolower((unsigned char)a) == tolower((unsigned char)b); +} + +bool JfrRedactedEvents::is_redacted_key(const char* key) { + return match_key(_key_filters, key); +} + +bool JfrRedactedEvents::is_separator(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +bool JfrRedactedEvents::is_whitespace(char c) { + return c == ' '; +} + +StringArray* JfrRedactedEvents::make_filter_array(const char* filter) { + StringArray* result = new StringArray(); + + while (*filter) { + const char* q = strstr(filter, DELIMITER); + if (q == nullptr) { + size_t len = strlen(filter); + if (len > 0) { + result->add(filter, len); + } + break; + } + size_t len = (size_t)(q - filter); + if (len > 0) { + result->add(filter, len); + } + filter = q + DELIMITER_LENGTH; + } + + return result; +} + +StringArray* JfrRedactedEvents::make_jvm_args_array(char** jvm_args_array, int array_length) { + if (jvm_args_array == nullptr) { + return nullptr; + } + StringArray* result = new StringArray(array_length); + for(int i = 0; i < array_length; i++) { + char* argument = jvm_args_array[i]; + if (_redacted_flight_recorder_options != nullptr && + strncmp(argument, "-XX:FlightRecorderOptions", 25) == 0) { + const char* text = _redacted_flight_recorder_options->text(); + size_t length = _redacted_flight_recorder_options->length(); + // Length must be at least 26 or the JVM will not start. + result->add(new String(argument, 26, text, length)); + continue; + } + if (strncmp(argument, "-D", 2) == 0) { + const char* key_start = argument + 2; + const char* eq = strchr(key_start, '='); + if (eq != nullptr) { + size_t key_length = (size_t)(eq - key_start); + String* key_tmp = new String(key_start, key_length); + bool redact = match_key(_key_filters, key_tmp->text()); + delete key_tmp; + if (redact) { + size_t unsensitive_length = (size_t)(eq - argument) + 1; + result->add(new String(argument, unsensitive_length, REDACTED, REDACTED_LENGTH)); + continue; + } + } + } + result->add(argument); + } + return result; +} + +StringArray* JfrRedactedEvents::make_java_args_array() { + StringArray* array = new StringArray(); + const char* p = Arguments::java_command(); + if (p == nullptr) { + return array; + } + const char* end = p + strlen(p); + while (p < end) { + while (p < end && is_whitespace(*p)) { // Launcher separates arguments with ' '. + p++; + } + if (p >= end) { + break; + } + const char* start = p; + while (p < end && !is_whitespace(*p)) { // Launcher separates arguments with ' '. + p++; + } + size_t length = (size_t)(p - start); + if (length > 0) { + array->add(start, length); + } + } + return array; +} + +bool JfrRedactedEvents::match(const char* filter, const char* text) { + const char* filter_last = nullptr; + const char* text_last = nullptr; + while (*text != '\0') { + if (*filter == '*') { + filter_last = filter; + text_last = text; + filter++; + } else if (*filter == '?' || equals_case_insensitive(*filter, *text)) { + filter++; + text++; + } else if (filter_last != nullptr) { + text_last++; + text = text_last; + filter = filter_last + 1; + } else { + return false; + } + } + while (*filter == '*') { + filter++; + } + return *filter == '\0'; +} + +int JfrRedactedEvents::match_arguments(StringArray* filter, StringArray* arguments, int arg_index) { + int index = arg_index; + if (arg_index + filter->length() <= arguments->length()) { + for (int i = 0; i < filter->length(); i++) { + if (!match(filter->at(i)->text(), arguments->at(index)->text())) { + return arg_index; + } + index++; + } + } + return index; +} + +bool JfrRedactedEvents::match_key(StringArray* filters, const char* text) { + for (int i = 0; i < filters->length(); i++) { + const char* filter = filters->at(i)->text(); + if (match(filter, text)) { + return true; + } + } + return false; +} + +bool JfrRedactedEvents::read_file(StringArray* target, const char* filename) { + FILE* file = os::fopen(filename, "r"); + if (file == nullptr) { + log_error(jfr, redact)("Failed to open redaction file: %s", filename); + return false; + } + + stringStream ss; + while (true) { + const int c = fgetc(file); + if (c == EOF && ferror(file) != 0) { + log_error(jfr, redact)("Error reading redaction file: %s", filename); + fclose(file); + return false; + } + if (c == EOF || c == '\n') { + const char* line = ss.base(); + size_t end = ss.size(); + while (end > 0 && is_separator(line[end - 1])) { + end--; + } + if (end > 0) { + target->add(line, end); + } + ss.reset(); + if (c == EOF) { + break; + } + } else { + ss.put((char)c); + } + } + fclose(file); + log_debug(jfr, redact)("Redaction file %s read successfully", filename); + return true; +} + +StringArray* JfrRedactedEvents::split(const char* text, char separator) { + const char* last = text; + StringArray* result = new StringArray(); + for (const char* position = text; *position != '\0'; position++) { + if (*position == separator) { + if (position > last) { + result->add(last, (size_t)(position - last)); + } + last = position + 1; + } + } + if (*last != '\0') { + result->add(last, strlen(last)); + } + return result; +} diff --git a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp new file mode 100644 index 00000000000..38c93310365 --- /dev/null +++ b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2026, 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. + * + */ + +#ifndef SHARE_JFR_PERIODIC_JFRREDACTEDEVENTS_HPP +#define SHARE_JFR_PERIODIC_JFRREDACTEDEVENTS_HPP + +#include "memory/allocation.hpp" +#include "memory/allStatic.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/growableArray.hpp" + +#include + +class JVMFlag; + +class JfrRedactedEvents: public AllStatic { + public: + class StringArray; + // Called at startup + static void log_redaction(); + static char* new_redacted_text(); + static bool set_argument_filter(const char* filters); + static bool set_key_filter(const char* filters); + static StringArray* argument_filters(); + static StringArray* key_filters(); + + // Synchronized in Java + static bool emit_initial_environment_variables(bool log = false); + static void emit_initial_system_properties(bool log = false); + static void emit_jvm_information(bool log = false); + static void emit_string_flags(bool log = false); + + template static GrowableArray* make_array(int size) { + return new (mtTracing) GrowableArray(size, mtTracing); + } + + template static void destroy_array(GrowableArray* array) { + if (array == nullptr) { + return; + } + for (int i = 0; i < array->length(); ++i) { + delete array->at(i); + } + delete array; + } + + class String: public CHeapObj { + public: + String(size_t length) : _length(length) { + _text = NEW_C_HEAP_ARRAY(char, length + 1, mtTracing); + memset(_text, 0, length + 1); + } + String(const char* source, size_t length) : String(length) { + memcpy(_text, source, length); + } + String(const char* source) : String(source, strlen(source)) { + } + String(const char* prefix, size_t prefix_length, const char* postfix, size_t postfix_length) + : String(prefix_length + postfix_length) { + memcpy(_text, prefix, prefix_length); + memcpy(_text + prefix_length, postfix, postfix_length); + } + ~String() { + FREE_C_HEAP_ARRAY(_text); + } + const char* text() const { + return _text; + } + char at(size_t index) const { + assert(index < _length, "out of bounds"); + return _text[index]; + } + void set(size_t index, char c) { + assert(index < _length, "out of bounds"); + _text[index] = c; + } + size_t length() const { + return _length; + } + static String* from_cstr(const char* s) { + return s == nullptr ? nullptr : new String(s); + } + static const char* c_str(const String* s) { + return s == nullptr ? nullptr : s->text(); + } + private: + const size_t _length; + char* _text; + }; + + class StringArray: public CHeapObj { + public: + StringArray() : _array(make_array(0)) { + } + StringArray(int capacity) : _array(make_array(capacity)) { + } + StringArray(const char* const array[], int count) : _array(make_array(count)) { + for (int i = 0; i < count; ++i) { + _array->append(new String(array[i])); + } + } + ~StringArray() { + destroy_array(_array); + } + int length() const { + return _array->length(); + } + const String* at(int i) const { + return _array->at(i); + } + void add(String* s) { + _array->append(s); + } + void add_contents(const String* s) { + add(s->text()); + } + void add(const char* s) { + _array->append(new String(s)); + } + void add(const char* s, size_t length) { + _array->append(new String(s, length)); + } + private: + GrowableArray* const _array; + }; + + class StringKeyValue: public CHeapObj { + public: + StringKeyValue(String* key, const char* value): _key(key), _value(String::from_cstr(value)) { + } + StringKeyValue(const char* key, const char* value) : StringKeyValue(new String(key), value) { + } + ~StringKeyValue() { + delete _key; + delete _value; + } + const char* key() const { + return _key->text(); + } + const char* value() const { + return String::c_str(_value); + } + private: + String* const _key; + String* const _value; + }; + + class StringFlag: public CHeapObj { + public: + StringFlag(const JVMFlag* flag, const char* redacted_value) + : _flag(flag), + _redacted_value(String::from_cstr(redacted_value)) { + } + ~StringFlag() { + delete _redacted_value; + } + const JVMFlag* jvm_flag() const { + return _flag; + } + const char* redacted_value() const { + return _redacted_value == nullptr ? nullptr : _redacted_value->text(); + } + private: + const JVMFlag* const _flag; + String* _redacted_value; + }; + + private: + static bool _initialized; + static StringArray* _argument_filters; + static StringArray* _key_filters; + static StringArray* _redacted_arguments; + static String* _redacted_java_command_line; + static String* _redacted_jvm_command_line; + static String* _redacted_flags_command_line; + static String* _redacted_flight_recorder_options; + static GrowableArray* _initial_system_properties; + static GrowableArray* _initial_environment_variables; + static GrowableArray* _string_flags; + + static bool append_filters(StringArray* target, bool argument, const char* filters); + static void add_default_filters(StringArray* target, bool argument); + static String* concatenate(StringArray* strings); + static bool equals_case_insensitive(char a, char b); + static bool is_redacted_key(const char* key); + static bool is_separator(char c); + static bool is_whitespace(char c); + static void ensure_initialized(); + static StringArray* make_java_args_array(); + static StringArray* make_jvm_args_array(char** jvm_args_array, int array_length); + static StringArray* make_filter_array(const char* filter); + static void log_jvm_information_redaction(const char* type, const String* redacted); + static bool match(const char* pattern, const char* text); + static bool match_flag(const char* flag_name, const char* argument); + static int match_arguments(StringArray* filter_array, StringArray* arguments, int arg_index); + static bool match_key(StringArray* array, const char* text); + static bool read_file(StringArray* target, const char* filename); + static String* redact_command_line(StringArray* arguments); + static StringArray* split(const char* text, char separator); +}; + +#endif // SHARE_JFR_PERIODIC_JFRREDACTEDEVENTS_HPP diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp index 5c12d5213ca..bb17b1d0eec 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp @@ -29,6 +29,7 @@ #include "jfr/jni/jfrJavaSupport.hpp" #include "jfr/leakprofiler/sampling/objectSampler.hpp" #include "jfr/periodic/jfrOSInterface.hpp" +#include "jfr/periodic/jfrRedactedEvents.hpp" #include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/periodic/sampling/jfrThreadSampler.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" @@ -243,6 +244,9 @@ bool JfrRecorder::on_create_vm_2() { } bool JfrRecorder::on_create_vm_3() { + if (log_is_enabled(Debug, jfr, redact)) { + JfrRedactedEvents::log_redaction(); + } JVMTI_ONLY( assert(JvmtiEnvBase::get_phase() == JVMTI_PHASE_LIVE, "invalid init sequence, phase is %d", (int)JvmtiEnvBase::get_phase()); ) return CDSConfig::is_dumping_archive() || launch_command_line_recordings(JavaThread::current()); } diff --git a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp index ec8ab36d75a..bdd42ba881e 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -25,6 +25,7 @@ #include "cds/cdsConfig.hpp" #include "classfile/javaClasses.hpp" #include "jfr/dcmd/jfrDcmds.hpp" +#include "jfr/periodic/jfrRedactedEvents.hpp" #include "jfr/recorder/service/jfrMemorySizer.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" #include "jfr/utilities/jfrAllocation.hpp" @@ -262,6 +263,20 @@ static DCmdArgument _dcmd_preserve_repository( false, default_preserve_repository); +static DCmdArgument _dcmd_redact_argument( + "redact-argument", + "Redact command line arguments", + "STRING", + false, + nullptr); + +static DCmdArgument _dcmd_redact_key( + "redact-key", + "Redact environment variables and system properties", + "STRING", + false, + nullptr); + static DCmdParser _parser; static void register_parser_options() { @@ -277,6 +292,8 @@ static void register_parser_options() { _parser.add_dcmd_option(&_dcmd_retransform); _parser.add_dcmd_option(&_dcmd_old_object_queue_size); _parser.add_dcmd_option(&_dcmd_preserve_repository); + _parser.add_dcmd_option(&_dcmd_redact_argument); + _parser.add_dcmd_option(&_dcmd_redact_key); DEBUG_ONLY(_parser.add_dcmd_option(&_dcmd_sample_protection);) } @@ -285,6 +302,11 @@ static bool parse_flight_recorder_options_internal(TRAPS) { return true; } const size_t length = strlen((const char*)FlightRecorderOptions); + if (strcmp(FlightRecorderOptions, "help") == 0) { + JfrConfigureFlightRecorderDCmd::print_help(tty, true); + vm_exit(0); + } + CmdLine cmdline((const char*)FlightRecorderOptions, length, true); _parser.parse(&cmdline, ',', THREAD); if (HAS_PENDING_EXCEPTION) { @@ -332,6 +354,20 @@ bool JfrOptionSet::initialize(JavaThread* thread) { set_retransform(_dcmd_retransform.value()); } set_old_object_queue_size(_dcmd_old_object_queue_size.value()); + if (_dcmd_redact_argument.is_set()) { + if (!JfrRedactedEvents::set_argument_filter(_dcmd_redact_argument.value())) { + return false; + } + _dcmd_redact_argument.destroy_value(); + _dcmd_redact_argument.set_value(JfrRedactedEvents::new_redacted_text()); + } + if (_dcmd_redact_key.is_set()) { + if (!JfrRedactedEvents::set_key_filter(_dcmd_redact_key.value())) { + return false; + } + _dcmd_redact_key.destroy_value(); + _dcmd_redact_key.set_value(JfrRedactedEvents::new_redacted_text()); + } return adjust_memory_options(); } diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index 848ff78678a..15f45d04af3 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -169,6 +169,7 @@ class outputStream; LOG_TAG(ptrqueue) \ LOG_TAG(purge) \ LOG_TAG(record) \ + LOG_TAG(redact) \ LOG_TAG(redefine) \ LOG_TAG(ref) \ LOG_TAG(refine) \ diff --git a/src/hotspot/share/services/diagnosticFramework.hpp b/src/hotspot/share/services/diagnosticFramework.hpp index 5f9253b492e..5fd0182fd77 100644 --- a/src/hotspot/share/services/diagnosticFramework.hpp +++ b/src/hotspot/share/services/diagnosticFramework.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, 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 @@ -85,6 +85,9 @@ public: // instance to the caller. return line; } + size_t cursor() const { + return _cursor; + } }; // Iterator class to iterate over diagnostic command arguments diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 290f729c0ca..70824046824 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -1148,8 +1148,10 @@ These `java` options control the runtime behavior of the Java HotSpot VM. option is disabled. [`-XX:FlightRecorderOptions=`]{#-XX_FlightRecorderOptions}*parameter*`=`*value* (or) `-XX:FlightRecorderOptions:`*parameter*`=`*value* -: Sets the parameters that control the behavior of JFR. Multiple parameters can be specified - by separating them with a comma. +: Sets the parameters that control the behavior of JFR. + `-XX:FlightRecorderOptions:help` prints the available options, default + redaction filters, and example command lines. Multiple parameters can be + specified by separating them with a comma. The following list contains the available JFR *parameter*`=`*value* entries: @@ -1196,6 +1198,43 @@ These `java` options control the runtime behavior of the Java HotSpot VM. false, instrumentation is added when event classes are loaded. By default, this parameter is enabled. + `redact-argument=`argument-filter + : Replace command-line arguments that match a semicolon-separated list + of glob patterns, for example, `*secret*;password*`. Matching is + case-insensitive, and the supported wildcards are `*` and `?`. To redact + multiple arguments, use a literal space (`' '`) as a separator. + For example, to match the two arguments `--auth username:token`, use the + filter `--auth *:*`. Filters containing spaces must be quoted as a single + command-line argument, for example, + `-XX:FlightRecorderOptions='redact-argument=--auth *:*'`. + Arguments containing spaces might not be matched as expected. To load + patterns from a file (one per line) use `@`. To add to the + default patterns instead of replacing them, prefix the whole list with + `+`, for example, `+*foo*;@redact.txt`. Use `none` (lowercase) to disable + all redaction filters for command-line arguments. Redacted arguments will + be replaced with `[REDACTED]`. The option `redact-argument` is best-effort + and applies only to command-line arguments in the `jdk.JVMInformation` + event and to the `java.command` system property in the + `jdk.InitialSystemProperty` event. Other events, such as `jdk.ProcessStart` + (child processes), are not redacted. Use `-XX:FlightRecorderOptions:help` + to see the default filters used by the `redact-argument` option. + + `redact-key=`key-filter + : Replace the value of environment variables and system properties + whose key matches a semicolon-separated list of glob patterns, + for example, `*password*;*token*`. Matching is case-insensitive, and + the supported wildcards are `*` and `?`. To load patterns from a file + (one per line), use `@`. To add to the default patterns + instead of replacing them, prefix the whole list with `+`, + for example, `+*cred*;@keys.txt`. Use `none` (lowercase) to + disable all redaction filters for key matching. Redacted values + will be replaced with `[REDACTED]`. The option `redact-key` is + best-effort and applies only to the `jdk.InitialSystemProperty`, + `jdk.InitialEnvironmentVariable` and `jdk.JVMInformation` (-Dkey=...) + events. Other events, such as `jdk.InitialSecurityProperty`, are not + redacted. Use `-XX:FlightRecorderOptions:help` to see the default filters + used by the `redact-key` option. + `stackdepth=`*depth* : Stack depth for stack traces. By default, the depth is set to 64 method calls. The maximum is 2048. Values greater than 64 could create diff --git a/test/jdk/jdk/jfr/startupargs/Application.java b/test/jdk/jdk/jfr/startupargs/Application.java new file mode 100644 index 00000000000..74235b04f7e --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/Application.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, 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. + */ + +package jdk.jfr.startupargs; + +// Purpose of this class is to allow tests capture environment variables, +// system properties and command line arguments. Using 'java -version' will +// not work as Java command line arguments are not captured. +public class Application { + public static void main(String... args) { + } +} diff --git a/test/jdk/jdk/jfr/startupargs/TestOptionsHelp.java b/test/jdk/jdk/jfr/startupargs/TestOptionsHelp.java new file mode 100644 index 00000000000..16f7ce8500c --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/TestOptionsHelp.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, 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. + */ + +package jdk.jfr.startupargs; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +/** + * @test + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main jdk.jfr.startupargs.TestOptionsHelp + */ +public class TestOptionsHelp { + + public static void main(String... args) throws Exception { + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder("-XX:FlightRecorderOptions:help"); + OutputAnalyzer out = ProcessTools.executeProcess(pb); + out.shouldContain("Syntax : -XX:FlightRecorderOptions:[options]"); + out.shouldContain("numglobalbuffers"); + out.shouldContain("Multiple options are separated"); + out.shouldHaveExitValue(0); + } +} diff --git a/test/jdk/jdk/jfr/startupargs/TestRedact.java b/test/jdk/jdk/jfr/startupargs/TestRedact.java new file mode 100644 index 00000000000..2d96408a3f5 --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/TestRedact.java @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2026, 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. + */ + +package jdk.jfr.startupargs; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import jdk.jfr.FlightRecorder; +import jdk.jfr.Recording; +import jdk.jfr.RecordingState; +import jdk.jfr.consumer.EventStream; +import jdk.jfr.consumer.RecordingFile; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.CommonHelper; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +/** + * @test + * @summary Test that redaction of sensitive data works + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @build jdk.jfr.startupargs.Application + * @run main/timeout=900 jdk.jfr.startupargs.TestRedact + */ +public class TestRedact { + private static Path TEST_DIRECTORY = Path.of(System.getProperty("test.src", ".")).toAbsolutePath(); + private static Path FILE_REDACTED_ARGUMENTS = TEST_DIRECTORY.resolve("redacted-arguments.txt"); + private static Path FILE_REDACTED_KEYS = TEST_DIRECTORY.resolve("redacted-keys.txt"); + + private record Execution( + Path file, + String jvmArgs, + String jvmFlags, + String javaArgs, + Map environment, + Map systemProperties, + Map stringFlags, + OutputAnalyzer output + ) { + + void assertUnredacted(String text) throws Exception { + if (jvmArgs != null && jvmArgs.contains(text)) { + return; + } + if (javaArgs != null && javaArgs.contains(text)) { + return; + } + if (systemProperties.containsValue(text) || environment.containsValue(text)) { + return; + } + printSystemProperties(); + printEnvironment(); + printCommandLine(); + throw new Exception("Could not find text '" + text + "'. Likely it's been incorrectly redacted."); + } + + private void printCommandLine() { + System.out.println("JVM Args: " + jvmArgs); + System.out.println("JVM Flags: " + jvmFlags); + System.out.println("Java Args: " + javaArgs); + System.out.println(); + } + + void assertRedactedKey(String key) throws Exception { + String v = systemProperties.get(key); + if (v != null) { + printSystemProperties(); + if (!v.equals("[REDACTED]")) { + throw new Exception("Expected system property value of key '" + key + "' to be [REDACTED]"); + } + return; + } + v = environment.get(key); + if (v == null) { + printEnvironment(); + printSystemProperties(); + throw new Exception("Expected key '" + key + "' in environment variable or system property"); + } + if (!v.equals("[REDACTED]")) { + printEnvironment(); + throw new Exception("Expected environment value of key '" + key + "' to be [REDACTED]"); + } + } + + void printEnvironment() { + printProperties("Environment Variables", environment); + } + + void printStringFlags() { + printProperties("String Flags", stringFlags); + } + + void printSystemProperties() { + printProperties("System Properties", systemProperties); + } + + private void printProperties(String title, Map map) { + System.out.println(title); + System.out.println("=".repeat(title.length())); + for (var entry : map.entrySet()) { + System.out.println(entry.getKey() + " = " + entry.getValue()); + } + System.out.println(); + } + + boolean hasCapitalizedLetter(String text) { + return text.chars().anyMatch(Character::isUpperCase); + } + + void assertRedactedArgument(String argument) throws Exception { + if (!hasCapitalizedLetter(argument)) { + throw new IllegalArgumentException("Redacted arguments in test must have at least one capitalized letter to avoid clash with random UUID in temporary filenames."); + } + checkArgument("JVM arguments", jvmArgs, argument); + checkArgument("Java arguments", javaArgs, argument); + checkArgument("Flags", jvmFlags, argument); + checkArgument("sun.java.command", systemProperties.get("sun.java.command"), argument); + } + + private void checkArgument(String kind, String arguments, String argument) throws Exception { + if (arguments != null && arguments.contains(argument)) { + System.out.println("ARGS: " + arguments); + throw new Exception("Found '" + argument + "' in " + kind + ". Should have been [REDACTED]"); + } + } + + public void print() { + printCommandLine(); + printEnvironment(); + printSystemProperties(); + printStringFlags(); + } + } + + public static void main(String... args) throws Exception { + testRedactionOfRedacted(); + testRedactKey(); + testRedactArgument(); + testRedactMultiple(); + testWildcards(); + testDefaults(); + testRedactFile(); + testAppendable(); + testEmpty(); + testBinary(); + } + + private static void testRedactionOfRedacted() throws Exception { + Execution e = run( + "-XX:FlightRecorderOptions:" + + "maxchunksize=1MB,redact-argument=Zebra," + + "old-object-queue-size=256," + + "redact-key=tiger", + "Zebra", + "Tiger" + ); + e.assertRedactedArgument("Zebra"); + e.assertRedactedArgument("redact-argument=Zebra"); + e.assertUnredacted("Tiger"); + } + + private static void testAppendable() throws Exception { + Execution e = run( + Map.of( + "Secret", "Thing", // For default filter + "Cat", "Bird" // For user-defined filter + ), + Map.of( + "Secret", "Stuff", // For default filter + "Cat", "Dog" // For user-defined filter + ), + "-XX:FlightRecorderOptions:redact-argument=+Foo;Bar,redact-key=+Cat", + "Foo", // For user-defined + "Bar", // For user-defined + "Secret"); // For default filter + e.assertRedactedArgument("Foo"); // Matched by user-defined filter + e.assertRedactedArgument("Bar"); // Matched by user-defined filter + e.assertRedactedKey("Cat"); // Matched by user-defined filter + e.assertRedactedArgument("Secret"); // Matched by default filter *secret* + e.assertRedactedKey("Secret"); // Matched by default filter *secret* + } + + private static void testWildcards() throws Exception { + Execution e = run( + "-XX:FlightRecorderOptions:redact-argument=*PPLE;ORAN*;C*HE*RY;N?2?4?;A?C*?F", + "APPLE", "ORANGE", "CHERRY", "N12345", "ABCDEF", "N4711" + ); + e.assertRedactedArgument("APPLE"); + e.assertRedactedArgument("ORANGE"); + e.assertRedactedArgument("CHERRY"); + e.assertRedactedArgument("N12345"); + e.assertRedactedArgument("ABCDEF"); + e.assertUnredacted("N4711"); + // Tests that only fully matched filters are redacted + Execution f = run( + "-XX:FlightRecorderOptions:redact-argument=-*TIGER* *", + "--TIGER" + ); + f.assertUnredacted("--TIGER"); + } + + private static void testBinary() throws Exception { + Execution unredacted = run( + Map.of("stuff1", "hammock"), + Map.of("stuff2", "haberdashery"), + "-XX:FlightRecorderOptions:stackdepth=64", + "hammock" + ); + if (!contains(unredacted.file, "hammock")) { + throw new Exception("Expected 'hammock' to be in recording file"); + } + if (!contains(unredacted.file, "haberdashery")) { + throw new Exception("Expected 'haberdashery' to be in recording file"); + } + + Execution redacted = run( + Map.of("what", "zippers"), + Map.of("where", "haberdashery"), + "-XX:FlightRecorderOptions:redact-argument=zippers,redact-key=what;where", + "zippers" + ); + if (contains(redacted.file, "zippers")) { + redacted.print(); + throw new Exception("Expected all occurrences of 'zippers' to be redacted"); + } + if (contains(redacted.file, "haberdashery")) { + redacted.print(); + throw new Exception("Expected all occurrences of 'haberdashery' to be redacted"); + } + } + + private static boolean contains(Path file, String text) throws IOException { + byte[] bytes = Files.readAllBytes(file); + byte[] chars = text.getBytes(StandardCharsets.UTF_8); + for (int i = 0; i <= bytes.length - chars.length; i++) { + if (matchesAt(bytes, chars, i)) { + System.out.print("Found '" + text + "' at position " + i); + System.out.println(" in file " + file.toAbsolutePath()); + return true; + } + } + return false; + } + + private static boolean matchesAt(byte[] bytes, byte[] chars, int position) { + for (int j = 0; j < chars.length; j++) { + if (bytes[position + j] != chars[j]) { + return false; + } + } + return true; + } + + private static void testRedactFile() throws Exception { + Execution e1 = run( + Map.of("stuff1", "Snake"), + Map.of("stuff2", "Snake"), + "-XX:FlightRecorderOptions:redact-argument=@" + FILE_REDACTED_ARGUMENTS, + "https://John:Smith@www.example.com/myresource", + "-conf-Key=chicken", + "/Foo/Dog", + "-Header", "Authorization:Bearer", "Banana", + "Apple"); + e1.assertRedactedArgument("https://John:Smith@www.example.com/myresource"); + e1.assertRedactedArgument("conf-Key=chicken"); + e1.assertRedactedArgument("/Foo/Dog"); + e1.assertRedactedArgument("-Header"); + e1.assertRedactedArgument("Authorization:Bearer"); + e1.assertRedactedArgument("Banana"); + e1.assertRedactedArgument("Apple"); + e1.assertUnredacted("Snake"); + + Execution e2 = run( + Map.of("confidential", "apple"), + Map.of("very-sensitive", "banana"), + "-XX:FlightRecorderOptions:redact-key=@" + FILE_REDACTED_KEYS, + "Snake"); + e2.assertRedactedKey("confidential"); + e2.assertRedactedKey("very-sensitive"); + e2.assertUnredacted("Snake"); + } + + private static void testEmpty() throws Exception { + var environment = Map.of("API_TOKEN", "Zebra1"); + var properties = Map.of("API_KEY", "Zebra2"); + Execution e1 = run(environment, properties, + "-XX:FlightRecorderOptions:redact-key=,redact-argument=", "Zebra3" + ); + e1.output().shouldContain("Default redaction filters are replaced."); + e1.output().shouldContain("redact-key=none to disable filters without a warning"); + e1.output().shouldContain("redact-argument=none to disable filters without a warning"); + e1.assertUnredacted("Zebra1"); + e1.assertUnredacted("Zebra2"); + e1.assertUnredacted("Zebra3"); + + Execution e2 = run(environment, properties, + "-XX:FlightRecorderOptions:redact-argument=none,redact-key=none", "Zebra3" + ); + e2.output().shouldNotContain("Default redaction filters are replaced."); + e2.output().shouldNotContain("redact-key=none to disable filters without a warning"); + e2.output().shouldNotContain("redact-argument=none to disable filters without a warning"); + e2.assertUnredacted("Zebra1"); + e2.assertUnredacted("Zebra2"); + e2.assertUnredacted("Zebra3"); + } + + private static void testDefaults() throws Exception { + Execution e = run( + Map.of("apiKey","thing"), + Map.of("apiKey", "stuff"), + "-DapiKey=stuff", + "Secret", + "-password=Foo" + ); + e.assertRedactedArgument("Secret"); + e.assertRedactedArgument("-password=Foo"); + e.assertRedactedKey("apiKey"); + } + + private static void testRedactArgument() throws Exception { + Execution e = run( + "-XX:FlightRecorderOptions:redact-argument=" + + // FILTERS + "Plain;" + + "--option *;" + + "--pass-phrase *;" + + "--login * *;" + + "--colon-based:*;" + + "https://*:*@*", + // SENSITIVE INFORMATION + "Plain", + "--option", "Option-value", + "--pass-phrase", "Cant-be-whitespace-outage", + "--login", "John", "N4711", + "--colon-based:Hello", + "https://Smith:abc123@example.com/path", + // UNSENSITIVE INFORMATION + "Banana" + ); + e.assertRedactedArgument("Plain"); + e.assertRedactedArgument("Option-value"); + e.assertRedactedArgument("Cant-be-whitespace-outage"); + e.assertRedactedArgument("Hello"); + e.assertRedactedArgument("John"); + e.assertRedactedArgument("N4711"); + e.assertRedactedArgument("Smith:abc123"); + e.assertUnredacted("Banana"); + } + + private static void testRedactMultiple() throws Exception { + Execution e = run( + Map.of("foo", "bird", "bar, ", "orange"), + Map.of("foo", "tiger", "bar", "banana"), + "-XX:FlightRecorderOptions:redact-key=foo;bar,redact-argument=Baz;Quz", "Baz", "Quz"); + e.assertRedactedKey("foo"); + e.assertRedactedKey("bar"); + e.assertRedactedArgument("Baz"); + e.assertRedactedArgument("Quz"); + } + + private static void testRedactKey() throws Exception { + Execution e = run( + Map.of("cart", "wheel", "banana", "split", "rose", "bud"), + Map.of("banana", "split", "cat", "milk", "carpet","magic", "rose", "bud"), + "-XX:FlightRecorderOptions:redact-key=banana;ca*t", + "rose" + ); + e.assertRedactedKey("banana"); + e.assertRedactedKey("cat"); + e.assertRedactedKey("carpet"); + e.assertUnredacted("rose"); + } + + private static Execution run(String options, String... args) throws Exception { + return run(Map.of(), Map.of(), options, args); + } + + private static Execution run(Map environment, Map properties, String options, String... args) throws Exception { + List arguments = new ArrayList<>(); + Path file = Path.of("file.jfr"); + for (var entry : properties.entrySet()) { + arguments.add("-D" + entry.getKey() + "=" + entry.getValue()); + } + arguments.add("-XX:StartFlightRecording:filename=" + file.toAbsolutePath().toString()); + arguments.add(options); + arguments.add("jdk.jfr.startupargs.Application"); + arguments.addAll(Arrays.asList(args)); + + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(arguments); + pb.environment().put("env.secret", "confidential"); + // An environment variable redacted by default filters + pb.environment().putAll(environment); + + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + System.out.println(output.getOutput()); + output.shouldHaveExitValue(0); + var environmentVariables = new HashMap(); + var systemProperties = new HashMap(); + var stringFlags = new HashMap(); + var jvmArgs = new AtomicReference(); + var jvmFlags= new AtomicReference(); + var javaArgs = new AtomicReference(); + try (var es = EventStream.openFile(file)) { + es.onEvent("jdk.InitialSystemProperty", e -> { + systemProperties.put(e.getString("key"), e.getString("value")); + }); + es.onEvent("jdk.InitialEnvironmentVariable", e -> { + environmentVariables.put(e.getString("key"), e.getString("value")); + }); + es.onEvent("jdk.JVMInformation", e -> { + jvmArgs.set(e.getString("jvmArguments")); + javaArgs.set(e.getString("javaArguments")); + jvmFlags.set(e.getString("jvmFlags")); + }); + es.onEvent("jdk.StringFlag", e -> { + stringFlags.put(e.getString("name"), e.getString("value")); + }); + es.start(); + } + return new Execution(file, + jvmArgs.get(), jvmFlags.get(), javaArgs.get(), + environmentVariables, systemProperties, stringFlags, output); + } +} diff --git a/test/jdk/jdk/jfr/startupargs/redacted-arguments.txt b/test/jdk/jdk/jfr/startupargs/redacted-arguments.txt new file mode 100644 index 00000000000..8f7ffb22569 --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/redacted-arguments.txt @@ -0,0 +1,6 @@ +https://*:*@* +apple +-conf-key=* +/foo/* +--passphrase * +-header Authorization:Bearer * diff --git a/test/jdk/jdk/jfr/startupargs/redacted-keys.txt b/test/jdk/jdk/jfr/startupargs/redacted-keys.txt new file mode 100644 index 00000000000..b63aa66cc70 --- /dev/null +++ b/test/jdk/jdk/jfr/startupargs/redacted-keys.txt @@ -0,0 +1,2 @@ +*sensitive +confidential