mirror of
https://github.com/openjdk/jdk.git
synced 2026-06-06 10:42:45 +00:00
8367584: Implement JEP 536: JFR In-Process Data Redaction
Reviewed-by: mgronlun
This commit is contained in:
parent
8ba600d6f3
commit
39de79eae2
@ -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 <key> or <key>=<value> syntax.");
|
||||
out->print_cr("");
|
||||
out->print_cr("Example usage:");
|
||||
out->print_cr("");
|
||||
out->print_cr(" $ jcmd <pid> JFR.configure");
|
||||
out->print_cr(" $ jcmd <pid> JFR.configure repositorypath=/temporary");
|
||||
out->print_cr(" $ jcmd <pid> JFR.configure stackdepth=256");
|
||||
out->print_cr(" $ jcmd <pid> 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 @<filename>.");
|
||||
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 @<filename>.");
|
||||
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 <key>=<value> 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 <key> or <key>=<value> syntax.");
|
||||
out->print_cr("");
|
||||
out->print_cr("Example usage:");
|
||||
out->print_cr("");
|
||||
out->print_cr(" $ jcmd <pid> JFR.configure");
|
||||
out->print_cr(" $ jcmd <pid> JFR.configure repositorypath=/temporary");
|
||||
out->print_cr(" $ jcmd <pid> JFR.configure stackdepth=256");
|
||||
out->print_cr(" $ jcmd <pid> JFR.configure memorysize=100M");
|
||||
}
|
||||
out->print_cr("");
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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) {
|
||||
|
||||
663
src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp
Normal file
663
src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp
Normal file
@ -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 <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
using StringArray = JfrRedactedEvents::StringArray;
|
||||
using String = JfrRedactedEvents::String;
|
||||
using StringFlag = JfrRedactedEvents::StringFlag;
|
||||
using StringKeyValueArray = GrowableArray<JfrRedactedEvents::StringKeyValue*>*;
|
||||
|
||||
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<StringFlag*>* 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;<filters>'", 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<StringKeyValue*>(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<StringKeyValue*>(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<StringFlag*>(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<u8>(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<StringArray*>* filters = make_array<StringArray*>(_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;
|
||||
}
|
||||
223
src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp
Normal file
223
src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp
Normal file
@ -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 <string.h>
|
||||
|
||||
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<typename T> static GrowableArray<T>* make_array(int size) {
|
||||
return new (mtTracing) GrowableArray<T>(size, mtTracing);
|
||||
}
|
||||
|
||||
template<typename T> static void destroy_array(GrowableArray<T*>* array) {
|
||||
if (array == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < array->length(); ++i) {
|
||||
delete array->at(i);
|
||||
}
|
||||
delete array;
|
||||
}
|
||||
|
||||
class String: public CHeapObj<mtTracing> {
|
||||
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<mtTracing> {
|
||||
public:
|
||||
StringArray() : _array(make_array<String*>(0)) {
|
||||
}
|
||||
StringArray(int capacity) : _array(make_array<String*>(capacity)) {
|
||||
}
|
||||
StringArray(const char* const array[], int count) : _array(make_array<String*>(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<String*>* const _array;
|
||||
};
|
||||
|
||||
class StringKeyValue: public CHeapObj<mtTracing> {
|
||||
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<mtTracing> {
|
||||
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<StringKeyValue*>* _initial_system_properties;
|
||||
static GrowableArray<StringKeyValue*>* _initial_environment_variables;
|
||||
static GrowableArray<StringFlag*>* _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
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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<bool> _dcmd_preserve_repository(
|
||||
false,
|
||||
default_preserve_repository);
|
||||
|
||||
static DCmdArgument<char*> _dcmd_redact_argument(
|
||||
"redact-argument",
|
||||
"Redact command line arguments",
|
||||
"STRING",
|
||||
false,
|
||||
nullptr);
|
||||
|
||||
static DCmdArgument<char*> _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();
|
||||
}
|
||||
|
||||
|
||||
@ -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) \
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 `@<filename>`. 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 `@<filename>`. 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
|
||||
|
||||
32
test/jdk/jdk/jfr/startupargs/Application.java
Normal file
32
test/jdk/jdk/jfr/startupargs/Application.java
Normal file
@ -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) {
|
||||
}
|
||||
}
|
||||
47
test/jdk/jdk/jfr/startupargs/TestOptionsHelp.java
Normal file
47
test/jdk/jdk/jfr/startupargs/TestOptionsHelp.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
456
test/jdk/jdk/jfr/startupargs/TestRedact.java
Normal file
456
test/jdk/jdk/jfr/startupargs/TestRedact.java
Normal file
@ -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<String, String> environment,
|
||||
Map<String, String> systemProperties,
|
||||
Map<String, String> 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<String, String> 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<String, String> environment, Map<String, String> properties, String options, String... args) throws Exception {
|
||||
List<String> 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<String, String>();
|
||||
var systemProperties = new HashMap<String, String>();
|
||||
var stringFlags = new HashMap<String, String>();
|
||||
var jvmArgs = new AtomicReference<String>();
|
||||
var jvmFlags= new AtomicReference<String>();
|
||||
var javaArgs = new AtomicReference<String>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
6
test/jdk/jdk/jfr/startupargs/redacted-arguments.txt
Normal file
6
test/jdk/jdk/jfr/startupargs/redacted-arguments.txt
Normal file
@ -0,0 +1,6 @@
|
||||
https://*:*@*
|
||||
apple
|
||||
-conf-key=*
|
||||
/foo/*
|
||||
--passphrase *
|
||||
-header Authorization:Bearer *
|
||||
2
test/jdk/jdk/jfr/startupargs/redacted-keys.txt
Normal file
2
test/jdk/jdk/jfr/startupargs/redacted-keys.txt
Normal file
@ -0,0 +1,2 @@
|
||||
*sensitive
|
||||
confidential
|
||||
Loading…
x
Reference in New Issue
Block a user