8285517: System.getenv() returns unexpected value if environment variable has non ASCII character

Reviewed-by: naoto, rriggs
This commit is contained in:
Ichiroh Takiguchi 2022-05-19 23:38:15 +00:00
parent de74e0e25a
commit 890771e708
5 changed files with 240 additions and 47 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,12 +26,13 @@
package jdk.internal.util;
import java.util.Properties;
import java.nio.charset.Charset;
/**
* System Property access for internal use only.
* Read-only access to System property values initialized during Phase 1
* are cached. Setting, clearing, or modifying the value using
* {@link System#setProperty) or {@link System#getProperties()} is ignored.
* {@link System#setProperty} or {@link System#getProperties()} is ignored.
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in these access methods. The caller of these methods should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
@ -52,6 +53,8 @@ public final class StaticProperty {
private static final String NATIVE_ENCODING;
private static final String FILE_ENCODING;
private static final String JAVA_PROPERTIES_DATE;
private static final String SUN_JNU_ENCODING;
private static final Charset jnuCharset;
private StaticProperty() {}
@ -69,6 +72,8 @@ public final class StaticProperty {
NATIVE_ENCODING = getProperty(props, "native.encoding");
FILE_ENCODING = getProperty(props, "file.encoding");
JAVA_PROPERTIES_DATE = getProperty(props, "java.properties.date", null);
SUN_JNU_ENCODING = getProperty(props, "sun.jnu.encoding");
jnuCharset = Charset.forName(SUN_JNU_ENCODING, Charset.defaultCharset());
}
private static String getProperty(Properties props, String key) {
@ -86,91 +91,77 @@ public final class StaticProperty {
}
/**
* Return the {@code java.home} system property.
* {@return the {@code java.home} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code java.home} system property
*/
public static String javaHome() {
return JAVA_HOME;
}
/**
* Return the {@code user.home} system property.
* {@return the {@code user.home} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code user.home} system property
*/
public static String userHome() {
return USER_HOME;
}
/**
* Return the {@code user.dir} system property.
* {@return the {@code user.dir} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code user.dir} system property
*/
public static String userDir() {
return USER_DIR;
}
/**
* Return the {@code user.name} system property.
* {@return the {@code user.name} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code user.name} system property
*/
public static String userName() {
return USER_NAME;
}
/**
* Return the {@code java.library.path} system property.
* {@return the {@code java.library.path} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code java.library.path} system property
*/
public static String javaLibraryPath() {
return JAVA_LIBRARY_PATH;
}
/**
* Return the {@code java.io.tmpdir} system property.
* {@return the {@code java.io.tmpdir} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code java.io.tmpdir} system property
*/
public static String javaIoTmpDir() {
return JAVA_IO_TMPDIR;
}
/**
* Return the {@code sun.boot.library.path} system property.
* {@return the {@code sun.boot.library.path} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code sun.boot.library.path} system property
*/
public static String sunBootLibraryPath() {
return SUN_BOOT_LIBRARY_PATH;
@ -178,13 +169,11 @@ public final class StaticProperty {
/**
* Return the {@code jdk.serialFilter} system property.
* {@return the {@code jdk.serialFilter} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code jdk.serialFilter} system property
*/
public static String jdkSerialFilter() {
return JDK_SERIAL_FILTER;
@ -192,53 +181,66 @@ public final class StaticProperty {
/**
* Return the {@code jdk.serialFilterFactory} system property.
* {@return the {@code jdk.serialFilterFactory} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code jdk.serialFilterFactory} system property
*/
public static String jdkSerialFilterFactory() {
return JDK_SERIAL_FILTER_FACTORY;
}
/**
* Return the {@code native.encoding} system property.
* {@return the {@code native.encoding} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code native.encoding} system property
*/
public static String nativeEncoding() {
return NATIVE_ENCODING;
}
/**
* Return the {@code file.encoding} system property.
* {@return the {@code file.encoding} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*
* @return the {@code file.encoding} system property
*/
public static String fileEncoding() {
return FILE_ENCODING;
}
/**
* Return the {@code java.properties.date} system property.
* {@return the {@code java.properties.date} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method.</strong>
*
* @return the {@code java.properties.date} system property
*/
public static String javaPropertiesDate() {
return JAVA_PROPERTIES_DATE;
}
/**
* {@return the {@code sun.jnu.encoding} system property}
*
* <strong>{@link SecurityManager#checkPropertyAccess} is NOT checked
* in this method. The caller of this method should take care to ensure
* that the returned property is not made accessible to untrusted code.</strong>
*/
public static String jnuEncoding() {
return SUN_JNU_ENCODING;
}
/**
* {@return {@code Charset} for {@code sun.jnu.encoding} system property}
*
* <strong>If {@code sun.jnu.encoding} system property has invalid
* encoding name, {@link Charset#defaultCharset()} is returned.</strong>
*/
public static Charset jnuCharset() {
return jnuCharset;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2022, 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
@ -55,7 +55,9 @@
package java.lang;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import jdk.internal.util.StaticProperty;
final class ProcessEnvironment
@ -163,7 +165,7 @@ final class ProcessEnvironment
}
public static Variable valueOfQueryOnly(String str) {
return new Variable(str, str.getBytes());
return new Variable(str, str.getBytes(StaticProperty.jnuCharset()));
}
public static Variable valueOf(String str) {
@ -172,7 +174,7 @@ final class ProcessEnvironment
}
public static Variable valueOf(byte[] bytes) {
return new Variable(new String(bytes), bytes);
return new Variable(new String(bytes, StaticProperty.jnuCharset()), bytes);
}
public int compareTo(Variable variable) {
@ -196,7 +198,7 @@ final class ProcessEnvironment
}
public static Value valueOfQueryOnly(String str) {
return new Value(str, str.getBytes());
return new Value(str, str.getBytes(StaticProperty.jnuCharset()));
}
public static Value valueOf(String str) {
@ -205,7 +207,7 @@ final class ProcessEnvironment
}
public static Value valueOf(byte[] bytes) {
return new Value(new String(bytes), bytes);
return new Value(new String(bytes, StaticProperty.jnuCharset()), bytes);
}
public int compareTo(Value value) {

View File

@ -35,6 +35,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Locale;
@ -151,7 +152,7 @@ final class ProcessImpl extends Process {
private static byte[] toCString(String s) {
if (s == null)
return null;
byte[] bytes = s.getBytes();
byte[] bytes = s.getBytes(StaticProperty.jnuCharset());
byte[] result = new byte[bytes.length + 1];
System.arraycopy(bytes, 0,
result, 0,
@ -175,7 +176,7 @@ final class ProcessImpl extends Process {
byte[][] args = new byte[cmdarray.length-1][];
int size = args.length; // For added NUL bytes
for (int i = 0; i < args.length; i++) {
args[i] = cmdarray[i+1].getBytes();
args[i] = cmdarray[i+1].getBytes(StaticProperty.jnuCharset());
size += args[i].length;
}
byte[] argBlock = new byte[size];

View File

@ -27,7 +27,7 @@
* 5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313
* 6464154 6523983 6206031 4960438 6631352 6631966 6850957 6850958
* 4947220 7018606 7034570 4244896 5049299 8003488 8054494 8058464
* 8067796 8224905 8263729 8265173 8272600 8231297 8282219
* 8067796 8224905 8263729 8265173 8272600 8231297 8282219 8285517
* @key intermittent
* @summary Basic tests for Process and Environment Variable code
* @modules java.base/java.lang:open
@ -55,6 +55,7 @@ import static java.lang.ProcessBuilder.Redirect.*;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -605,7 +606,11 @@ public class Basic {
try {
// If round trip conversion works, should be able to set env vars
// correctly in child.
if (new String(tested.getBytes()).equals(tested)) {
String jnuEncoding = System.getProperty("sun.jnu.encoding");
Charset cs = jnuEncoding != null
? Charset.forName(jnuEncoding, Charset.defaultCharset())
: Charset.defaultCharset();
if (new String(tested.getBytes(cs), cs).equals(tested)) {
out.println("Testing " + encoding + " environment values");
ProcessBuilder pb = new ProcessBuilder();
pb.environment().put("ASCIINAME",tested);

View File

@ -0,0 +1,183 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8285517
* @summary System.getenv() and argument don't return locale dependent data by JEP400
* @requires (os.family == "linux")
* @modules jdk.charsets
* @library /test/lib
* @build jdk.test.lib.process.*
* @run main i18nEnvArg
*/
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HexFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jdk.test.lib.process.ProcessTools;
public class i18nEnvArg {
final static String EUC_JP_TEXT = "\u6F22\u5B57";
/*
* Checks OS is Linux and OS has ja_JP.eucjp locale or not.
* Sets EUC_JP's environment variable and argunments against ProcessBuilder
*/
public static void main(String[] args) throws Exception {
ProcessBuilder pb;
List<String> cmds = new ArrayList<>();
cmds.addAll(List.of(
"--add-modules=" + System.getProperty("test.modules"),
"-classpath",
System.getProperty("test.class.path"),
"-Dtest.class.path=" + System.getProperty("test.class.path"),
"-Dtest.modules=" + System.getProperty("test.modules")));
if (args.length == 0) {
cmds.addAll(
List.of("-Dtest.jdk=" + System.getProperty("test.jdk"),
"i18nEnvArg",
"Start"));
} else {
String jnuEncoding = System.getProperty("sun.jnu.encoding");
Charset dcs = jnuEncoding != null
? Charset.forName(jnuEncoding)
: Charset.defaultCharset();
Charset cs = Charset.forName("x-euc-jp-linux");
if (!dcs.equals(cs)) {
return;
}
cmds.addAll(
List.of("--add-opens=java.base/java.lang=ALL-UNNAMED",
"i18nEnvArg$Verify",
EUC_JP_TEXT));
}
pb = ProcessTools.createTestJvm(cmds);
Map<String, String> environ = pb.environment();
environ.clear();
environ.put("LANG", "ja_JP.eucjp");
if (args.length != 0) {
environ.put(EUC_JP_TEXT, EUC_JP_TEXT);
}
ProcessTools.executeProcess(pb)
.outputTo(System.out)
.errorTo(System.err)
.shouldHaveExitValue(0);
}
public static class Verify {
private static String toReadable(String s) {
if (s == null)
return "null";
StringBuilder sb = new StringBuilder();
for(char ch : s.toCharArray()) {
sb.append(String.format("\\u%04X", (int)ch));
}
return sb.toString();
}
/*
* Verify environment variable and argument are encoded by Linux's
* eucjp or not
*/
public static void main(String[] args) throws Exception {
Charset cs = Charset.forName("x-euc-jp-linux");
byte[] euc = EUC_JP_TEXT.getBytes(cs);
try ( ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos); ) {
if (!EUC_JP_TEXT.equals(args[0])) {
ps.println("argument EUC_JP_TEXT is:");
ps.println(" Actual: " + toReadable(args[0]));
ps.println(" Expected: " + toReadable(EUC_JP_TEXT));
}
String s = System.getenv(EUC_JP_TEXT);
if (!EUC_JP_TEXT.equals(s)) {
ps.println("getenv(\"EUC_JP_TEXT\") is:");
ps.println(" Actual: " + toReadable(s));
ps.println(" Expected: " + toReadable(EUC_JP_TEXT));
} else {
try {
Class<?> ProcessEnvironment_cls =
Class.forName("java.lang.ProcessEnvironment");
Field theEnvironment_fid =
ProcessEnvironment_cls.getDeclaredField("theEnvironment");
theEnvironment_fid.setAccessible(true);
HashMap theEnvironment =
(HashMap) theEnvironment_fid.get(null);
Class<?> ExternalData_cls =
Class.forName("java.lang.ProcessEnvironment$ExternalData");
Method getBytes_mid =
ExternalData_cls.getDeclaredMethod("getBytes");
getBytes_mid.setAccessible(true);
HexFormat hf = HexFormat.of()
.withUpperCase()
.withPrefix("\\x");
for (Object k : theEnvironment.keySet()) {
if (EUC_JP_TEXT.equals(k.toString())) {
byte[] ba = (byte[]) getBytes_mid.invoke(k,
(Object[])null);
if (!Arrays.equals(euc, ba)) {
ps.println(
"Variable EUC_JP_TEXT is encoded by:");
ps.println(" Actual: "
+ hf.formatHex(ba));
ps.println(" Expected: "
+ hf.formatHex(euc));
}
ba = (byte[]) getBytes_mid.invoke(
theEnvironment.get(k),
(Object[])null);
if (!Arrays.equals(euc, ba)) {
ps.println(
"Value EUC_JP_TEXT is encoded by:");
ps.println(" Actual: "
+ hf.formatHex(ba));
ps.println(" Expected: "
+ hf.formatHex(euc));
}
}
}
} catch (Exception e) {
ps.println(
"Check ProcessEnvironment class implementation");
e.printStackTrace(ps);
}
}
byte[] ba = baos.toByteArray();
if (ba.length > 0) {
System.err.write(ba);
System.exit(1);
}
}
}
}
}