-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8352728: InternalError loading java.security due to Windows parent folder permissions #24465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
308479d
7047a99
a8c5ca7
7abb62c
fc25514
3593347
4cfce61
71718e5
4e77fb3
a8d865c
40bc832
4483469
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||||||||||
| /* | ||||||||||||||
| * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. | ||||||||||||||
| * Copyright (c) 1996, 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 | ||||||||||||||
|
|
@@ -34,6 +34,7 @@ | |||||||||||||
| import java.nio.file.Files; | ||||||||||||||
| import java.nio.file.InvalidPathException; | ||||||||||||||
| import java.nio.file.Path; | ||||||||||||||
| import java.util.ArrayList; | ||||||||||||||
| import java.util.Arrays; | ||||||||||||||
| import java.util.Collections; | ||||||||||||||
| import java.util.Enumeration; | ||||||||||||||
|
|
@@ -112,7 +113,7 @@ private enum LoadingMode {OVERRIDE, APPEND} | |||||||||||||
|
|
||||||||||||||
| private static Path currentPath; | ||||||||||||||
|
|
||||||||||||||
| private static final Set<Path> activePaths = new HashSet<>(); | ||||||||||||||
| private static final List<Path> activePaths = new ArrayList<>(); | ||||||||||||||
|
|
||||||||||||||
| static void loadAll() { | ||||||||||||||
| // first load the master properties file to | ||||||||||||||
|
|
@@ -253,7 +254,10 @@ static void loadInclude(String propFile) { | |||||||||||||
| "from a non-regular properties file " + | ||||||||||||||
| "(e.g. HTTP served file)"); | ||||||||||||||
| } | ||||||||||||||
| path = currentPath.resolveSibling(path); | ||||||||||||||
| // We perform symlinks resolution on currentPath under the | ||||||||||||||
| // rationale that the original file writer is the one who | ||||||||||||||
| // decided where the relative includes should resolve. | ||||||||||||||
| path = resolve(currentPath).resolveSibling(path); | ||||||||||||||
| } | ||||||||||||||
| loadFromPath(path, LoadingMode.APPEND); | ||||||||||||||
| } catch (IOException | InvalidPathException e) { | ||||||||||||||
|
|
@@ -262,20 +266,37 @@ static void loadInclude(String propFile) { | |||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| private static Path resolve(Path path) { | ||||||||||||||
| // JDK-8352728: we prefer java.io.File::getCanonicalFile over | ||||||||||||||
| // java.nio.file.Path::toRealPath because the former is more | ||||||||||||||
| // fault-tolerant, since the canonical form of a pathname is | ||||||||||||||
| // specified to exist even for nonexistent/inaccessible files. | ||||||||||||||
| try { | ||||||||||||||
| return path.toFile().getCanonicalFile().toPath(); | ||||||||||||||
| } catch (IOException e) { | ||||||||||||||
| throw new InternalError("Cannot resolve path", e); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| private static void checkCyclicInclude(Path path) { | ||||||||||||||
| for (Path activePath : activePaths) { | ||||||||||||||
| try { | ||||||||||||||
| if (Files.isSameFile(path, activePath)) { | ||||||||||||||
| throw new InternalError( | ||||||||||||||
| "Cyclic include of '" + path + "'"); | ||||||||||||||
| } | ||||||||||||||
| } catch (IOException ignore) {} | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure you want to ignore this - seems better to let this propagate and be thrown as an
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can make this an
Current code wouldn't fail in this scenario, although I recognize it's a corner case. I decided to ignore the exception under the assumption that |
||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| private static void loadFromPath(Path path, LoadingMode mode) | ||||||||||||||
| throws IOException { | ||||||||||||||
| boolean isRegularFile = Files.isRegularFile(path); | ||||||||||||||
| if (isRegularFile) { | ||||||||||||||
| path = path.toRealPath(); | ||||||||||||||
| } else if (Files.isDirectory(path)) { | ||||||||||||||
| if (!isRegularFile && Files.isDirectory(path)) { | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can a directory ever be a regular file? If not, you don't need the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A directory is not a regular file, but we need
So Previous code was also saving this I/O: jdk/src/java.base/share/classes/java/security/Security.java Lines 268 to 272 in 4b31511
I would prefer not to, but I can remove the condition and inline currentPath = Files.isRegularFile(path) ? path : null; |
||||||||||||||
| throw new IOException("Is a directory"); | ||||||||||||||
| } else { | ||||||||||||||
| path = path.toAbsolutePath(); | ||||||||||||||
| } | ||||||||||||||
| if (activePaths.contains(path)) { | ||||||||||||||
| throw new InternalError("Cyclic include of '" + path + "'"); | ||||||||||||||
| } | ||||||||||||||
| try (InputStream is = Files.newInputStream(path)) { | ||||||||||||||
| checkCyclicInclude(path); | ||||||||||||||
| reset(mode); | ||||||||||||||
| Path previousPath = currentPath; | ||||||||||||||
| currentPath = isRegularFile ? path : null; | ||||||||||||||
|
|
@@ -285,7 +306,7 @@ private static void loadFromPath(Path path, LoadingMode mode) | |||||||||||||
| props.load(is); | ||||||||||||||
| debugLoad(false, path); | ||||||||||||||
| } finally { | ||||||||||||||
| activePaths.remove(path); | ||||||||||||||
| activePaths.removeLast(); | ||||||||||||||
| currentPath = previousPath; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -304,6 +325,7 @@ private static void loadFromUrl(URL url, LoadingMode mode) | |||||||||||||
| private static void debugLoad(boolean start, Object source) { | ||||||||||||||
| if (sdebug != null) { | ||||||||||||||
| int level = activePaths.isEmpty() ? 1 : activePaths.size(); | ||||||||||||||
| source = source instanceof Path path ? resolve(path) : source; | ||||||||||||||
| sdebug.println((start ? | ||||||||||||||
| ">".repeat(level) + " starting to process " : | ||||||||||||||
| "<".repeat(level) + " finished processing ") + source); | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| # | ||
| # Copyright (c) 2025, Red Hat, Inc. | ||
| # | ||
| # 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 | ||
| # @summary Ensures the java executable is able to load extra security | ||
| # properties files from anonymous pipes (non-regular files). | ||
| # @bug 8352728 | ||
| # @requires os.family == "linux" | ||
| # @run shell/timeout=30 ConfigFileTestAnonymousPipes.sh | ||
|
|
||
| if [ -z "${TESTJAVA}" ]; then | ||
| JAVA=java | ||
| else | ||
| JAVA="${TESTJAVA}/bin/java" | ||
| fi | ||
|
|
||
| TEST_PROP="ConfigFileTestAnonymousPipes.property.name=PROPERTY_VALUE" | ||
|
|
||
| function check_java() { | ||
| local java_output java_exit_code | ||
| java_output="$("${JAVA}" ${TESTVMOPTS} ${TESTJAVAOPTS} -XshowSettings:security:properties \ | ||
| -Djava.security.debug=properties "$@" -version 2>&1)" | ||
| java_exit_code=$? | ||
| if [ ${java_exit_code} -ne 0 ] || ! grep -qF "${TEST_PROP}" <<<"${java_output}"; then | ||
| echo "TEST FAILED (java exit code: ${java_exit_code})" | ||
| echo "${java_output}" | ||
| exit 1 | ||
| fi | ||
| } | ||
|
|
||
| # https://www.gnu.org/software/bash/manual/bash.html#Pipelines | ||
| echo "Extra properties from pipeline" | ||
| echo "${TEST_PROP}" | check_java -Djava.security.properties=/dev/stdin || exit 1 | ||
|
|
||
| # https://www.gnu.org/software/bash/manual/bash.html#Process-Substitution | ||
| echo "Extra properties from process-substitution, include other from pipeline" | ||
| echo "${TEST_PROP}" | check_java -Djava.security.properties=<(echo "include /dev/stdin") || exit 1 | ||
|
|
||
| echo "TEST PASS - OK" | ||
| exit 0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| /* | ||
| * Copyright (c) 2025, Red Hat, Inc. | ||
| * | ||
| * 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. | ||
| */ | ||
|
|
||
| import jdk.test.lib.process.ProcessTools; | ||
| import jdk.test.lib.util.FileUtils; | ||
|
|
||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.attribute.AclEntry; | ||
| import java.nio.file.attribute.AclEntryType; | ||
| import java.nio.file.attribute.AclFileAttributeView; | ||
| import java.util.List; | ||
|
|
||
| import static java.nio.file.StandardOpenOption.APPEND; | ||
|
|
||
| /* | ||
| * @test | ||
| * @summary Ensures java.security is loadable in Windows and filesystem | ||
| * soft-links are resolved, even when the user does not have permissions | ||
| * on one of the parent directories. | ||
| * @bug 8352728 | ||
| * @requires os.family == "windows" | ||
| * @library /test/lib | ||
| * @run main ConfigFileTestDirPermissions | ||
| */ | ||
|
|
||
| public class ConfigFileTestDirPermissions { | ||
| private static final String LF = System.lineSeparator(); | ||
| private static final String TEST_PROPERTY = | ||
| "test.property.name=test_property_value"; | ||
|
|
||
| // Unlike symbolic links, directory junctions do not require elevation | ||
| private static void createJunction(Path target, Path link) | ||
| throws IOException, InterruptedException { | ||
| if (!Files.isDirectory(target)) { | ||
| throw new IOException("The target must be a directory: " + target); | ||
| } | ||
| int exitCode = | ||
| new ProcessBuilder("cmd", "/c", "MKLINK", "/J", link.toString(), | ||
| target.toString()).inheritIO().start().waitFor(); | ||
| if (exitCode != 0) { | ||
| throw new IOException("Unexpected exit code: " + exitCode); | ||
| } | ||
| } | ||
|
|
||
| public static void main(String[] args) throws Exception { | ||
| Path temp = Files.createTempDirectory("JDK-8352728-tmp-"); | ||
| // We will create the following directories structure: | ||
| // | ||
| // 📁 JDK-8352728-tmp-*/ | ||
| // ├─🔒 jdk-parent-dir/ (ACL with REMOVED-PERMISSIONS) | ||
| // │ └─📁 jdk/ | ||
| // │ ├─📁 conf/ | ||
| // │ │ ├─📁 security/ | ||
| // │ │ │ ├─📄 java.security | ||
| // │ │ │ │ 📝 include link-to-other-dir/other.properties | ||
| // │ │ │ ├─🔗 link-to-other-dir/ ⟹ 📁 JDK-8352728-tmp-*/other-dir | ||
| // │ │ │ └─... (JUNCTION) | ||
| // │ │ └─... | ||
| // │ └─... | ||
| // ├─📁 other-dir/ | ||
| // │ └─📄 other.properties | ||
| // │ 📝 include ../relatively.included.properties | ||
| // └─📄 relatively.included.properties | ||
| // 📝 test.property.name=test_property_value | ||
| try { | ||
| // Copy the jdk to a different directory | ||
| Path originalJdk = Path.of(System.getProperty("test.jdk")); | ||
| Path jdk = temp.resolve("jdk-parent-dir", "jdk"); | ||
| Files.createDirectories(jdk); | ||
| FileUtils.copyDirectory(originalJdk, jdk); | ||
|
|
||
| // Create a properties file with a relative include in it | ||
| Path otherDir = temp.resolve("other-dir"); | ||
| Files.createDirectories(otherDir); | ||
| Path other = otherDir.resolve("other.properties"); | ||
| Path included = temp.resolve("relatively.included.properties"); | ||
| Files.writeString(included, TEST_PROPERTY + LF); | ||
| Files.writeString(other, | ||
| "include ../" + included.getFileName() + LF); | ||
|
|
||
| // Create a junction to the properties file dir, from the jdk dir | ||
| Path javaSec = jdk.resolve("conf", "security", "java.security"); | ||
| Path linkDir = javaSec.resolveSibling("link-to-other-dir"); | ||
| createJunction(otherDir, linkDir); | ||
|
|
||
| // Include the properties file from java.security (through the link) | ||
| Files.writeString(javaSec, | ||
| LF + "include " + linkDir.getFileName() + "/" + | ||
| other.getFileName() + LF, APPEND); | ||
|
|
||
| // Remove current user permissions from jdk-parent-dir | ||
| Path parent = jdk.getParent(); | ||
| AclFileAttributeView view = Files.getFileAttributeView(parent, | ||
| AclFileAttributeView.class); | ||
| List<AclEntry> originalAcl = List.copyOf(view.getAcl()); | ||
| view.setAcl(List.of(AclEntry.newBuilder().setType(AclEntryType.DENY) | ||
| .setPrincipal(Files.getOwner(parent)).build())); | ||
|
|
||
| try { | ||
| // Make sure the permissions are affecting the current user | ||
| try { | ||
| jdk.toRealPath(); | ||
| throw new jtreg.SkippedException("Must run non-elevated!"); | ||
| } catch (IOException expected) { } | ||
|
|
||
| // Execute the copied jdk, ensuring java.security.Security is | ||
| // loaded (i.e. use -XshowSettings:security:properties) | ||
| ProcessTools.executeProcess(new ProcessBuilder( | ||
| jdk.resolve("bin", "java.exe").toString(), | ||
| "-Djava.security.debug=properties", | ||
| "-XshowSettings:security:properties", | ||
| "-version")) | ||
| .shouldHaveExitValue(0) | ||
| .shouldContain(TEST_PROPERTY); | ||
| } finally { | ||
| view.setAcl(originalAcl); | ||
| } | ||
| } finally { | ||
| FileUtils.deleteFileTreeUnchecked(temp); | ||
| } | ||
|
|
||
| System.out.println("TEST PASS - OK"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@seanjmullan If this change is integrated, can we create a follow-up on issue to replace it? I strongly disagree with the changes in this PR, we should not be using File::getCanonicalFile here to work around an issue with an inaccessible parent directory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wanted to note that I also dislike this change, but is the only way I found to support relative includes in Windows UWP Java applications.
I'm open to other suggestions, including going back to
Path::toRealPathand dropping that use-case (while still fixing the original JDK-8352728 issue), even though that isn't my preference.@AlanBateman: do you disagree with other changes in this PR? I'm not comfortable integrating something that we haven't agreed upon.