Skip to content

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions src/java.base/share/classes/java/security/Security.java
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
Expand Down Expand Up @@ -253,6 +253,10 @@ static void loadInclude(String propFile) {
"from a non-regular properties file " +
"(e.g. HTTP served file)");
}
// Inside loadFromPath, we have performed symlinks
// resolution on currentFile under the rationale that
// the original file writer is the one who decided
// where the relative includes should resolve.
path = currentPath.resolveSibling(path);
}
loadFromPath(path, LoadingMode.APPEND);
Expand All @@ -265,13 +269,14 @@ static void loadInclude(String propFile) {
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)) {
throw new IOException("Is a directory");
} else {
path = path.toAbsolutePath();
}
// 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.
path = path.toFile().getCanonicalFile().toPath();
if (activePaths.contains(path)) {
throw new InternalError("Cyclic include of '" + path + "'");
}
Expand Down
147 changes: 147 additions & 0 deletions test/jdk/java/security/Security/ConfigFileTestDirPermissions.java
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");
}
}