Use Java 9 FilePermission model (#26302)
This commit makes the security code aware of the Java 9 FilePermission changes (see #21534) and allows us to remove the `jdk.io.permissionsUseCanonicalPath` system property.
This commit is contained in:
parent
bdefcbdcd6
commit
3d8feff66e
|
@ -549,8 +549,6 @@ class BuildPlugin implements Plugin<Project> {
|
|||
systemProperty 'tests.artifact', project.name
|
||||
systemProperty 'tests.task', path
|
||||
systemProperty 'tests.security.manager', 'true'
|
||||
// Breaking change in JDK-9, revert to JDK-8 behavior for now, see https://github.com/elastic/elasticsearch/issues/21534
|
||||
systemProperty 'jdk.io.permissionsUseCanonicalPath', 'true'
|
||||
systemProperty 'jna.nosys', 'true'
|
||||
// default test sysprop values
|
||||
systemProperty 'tests.ifNoTests', 'fail'
|
||||
|
|
|
@ -107,3 +107,6 @@ java.util.Collections#EMPTY_MAP
|
|||
java.util.Collections#EMPTY_SET
|
||||
|
||||
java.util.Collections#shuffle(java.util.List) @ Use java.util.Collections#shuffle(java.util.List, java.util.Random) with a reproducible source of randomness
|
||||
|
||||
@defaultMessage Avoid creating FilePermission objects directly, but use FilePermissionUtils instead
|
||||
java.io.FilePermission#<init>(java.lang.String,java.lang.String)
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.bootstrap;
|
||||
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
|
||||
import java.io.FilePermission;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Permissions;
|
||||
|
||||
public class FilePermissionUtils {
|
||||
|
||||
/** no instantiation */
|
||||
private FilePermissionUtils() {}
|
||||
|
||||
private static final boolean VERSION_IS_AT_LEAST_JAVA_9 = JavaVersion.current().compareTo(JavaVersion.parse("9")) >= 0;
|
||||
|
||||
/**
|
||||
* Add access to single file path
|
||||
* @param policy current policy to add permissions to
|
||||
* @param path the path itself
|
||||
* @param permissions set of file permissions to grant to the path
|
||||
*/
|
||||
@SuppressForbidden(reason = "only place where creating Java-9 compatible FilePermission objects is possible")
|
||||
public static void addSingleFilePath(Permissions policy, Path path, String permissions) throws IOException {
|
||||
policy.add(new FilePermission(path.toString(), permissions));
|
||||
if (VERSION_IS_AT_LEAST_JAVA_9 && Files.exists(path)) {
|
||||
// Java 9 FilePermission model requires this due to the removal of pathname canonicalization,
|
||||
// see also https://github.com/elastic/elasticsearch/issues/21534
|
||||
Path realPath = path.toRealPath();
|
||||
if (path.toString().equals(realPath.toString()) == false) {
|
||||
policy.add(new FilePermission(realPath.toString(), permissions));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add access to path (and all files underneath it); this also creates the directory if it does not exist.
|
||||
*
|
||||
* @param policy current policy to add permissions to
|
||||
* @param configurationName the configuration name associated with the path (for error messages only)
|
||||
* @param path the path itself
|
||||
* @param permissions set of file permissions to grant to the path
|
||||
*/
|
||||
@SuppressForbidden(reason = "only place where creating Java-9 compatible FilePermission objects is possible")
|
||||
public static void addDirectoryPath(Permissions policy, String configurationName, Path path, String permissions) throws IOException {
|
||||
// paths may not exist yet, this also checks accessibility
|
||||
try {
|
||||
Security.ensureDirectoryExists(path);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to access '" + configurationName + "' (" + path + ")", e);
|
||||
}
|
||||
|
||||
// add each path twice: once for itself, again for files underneath it
|
||||
policy.add(new FilePermission(path.toString(), permissions));
|
||||
policy.add(new FilePermission(path.toString() + path.getFileSystem().getSeparator() + "-", permissions));
|
||||
if (VERSION_IS_AT_LEAST_JAVA_9) {
|
||||
// Java 9 FilePermission model requires this due to the removal of pathname canonicalization,
|
||||
// see also https://github.com/elastic/elasticsearch/issues/21534
|
||||
Path realPath = path.toRealPath();
|
||||
if (path.toString().equals(realPath.toString()) == false) {
|
||||
policy.add(new FilePermission(realPath.toString(), permissions));
|
||||
policy.add(new FilePermission(realPath.toString() + realPath.getFileSystem().getSeparator() + "-", permissions));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,14 +23,12 @@ import org.elasticsearch.SecureSM;
|
|||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.network.NetworkModule;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.http.HttpTransportSettings;
|
||||
import org.elasticsearch.plugins.PluginInfo;
|
||||
import org.elasticsearch.transport.TcpTransport;
|
||||
|
||||
import java.io.FilePermission;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketPermission;
|
||||
import java.net.URISyntaxException;
|
||||
|
@ -52,6 +50,9 @@ import java.util.LinkedHashSet;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.bootstrap.FilePermissionUtils.addDirectoryPath;
|
||||
import static org.elasticsearch.bootstrap.FilePermissionUtils.addSingleFilePath;
|
||||
|
||||
/**
|
||||
* Initializes SecurityManager with necessary permissions.
|
||||
* <br>
|
||||
|
@ -240,10 +241,10 @@ final class Security {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
// resource itself
|
||||
policy.add(new FilePermission(path.toString(), "read,readlink"));
|
||||
// classes underneath
|
||||
if (Files.isDirectory(path)) {
|
||||
policy.add(new FilePermission(path.toString() + path.getFileSystem().getSeparator() + "-", "read,readlink"));
|
||||
addDirectoryPath(policy, "class.path", path, "read,readlink");
|
||||
} else {
|
||||
addSingleFilePath(policy, path, "read,readlink");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -251,22 +252,23 @@ final class Security {
|
|||
/**
|
||||
* Adds access to all configurable paths.
|
||||
*/
|
||||
static void addFilePermissions(Permissions policy, Environment environment) {
|
||||
static void addFilePermissions(Permissions policy, Environment environment) throws IOException {
|
||||
// read-only dirs
|
||||
addPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.binFile(), "read,readlink");
|
||||
addPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.libFile(), "read,readlink");
|
||||
addPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.modulesFile(), "read,readlink");
|
||||
addPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.pluginsFile(), "read,readlink");
|
||||
addPath(policy, "path.conf'", environment.configFile(), "read,readlink");
|
||||
addDirectoryPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.binFile(), "read,readlink");
|
||||
addDirectoryPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.libFile(), "read,readlink");
|
||||
addDirectoryPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.modulesFile(), "read,readlink");
|
||||
addDirectoryPath(policy, Environment.PATH_HOME_SETTING.getKey(), environment.pluginsFile(), "read,readlink");
|
||||
addDirectoryPath(policy, "path.conf'", environment.configFile(), "read,readlink");
|
||||
// read-write dirs
|
||||
addPath(policy, "java.io.tmpdir", environment.tmpFile(), "read,readlink,write,delete");
|
||||
addPath(policy, Environment.PATH_LOGS_SETTING.getKey(), environment.logsFile(), "read,readlink,write,delete");
|
||||
addDirectoryPath(policy, "java.io.tmpdir", environment.tmpFile(), "read,readlink,write,delete");
|
||||
addDirectoryPath(policy, Environment.PATH_LOGS_SETTING.getKey(), environment.logsFile(), "read,readlink,write,delete");
|
||||
if (environment.sharedDataFile() != null) {
|
||||
addPath(policy, Environment.PATH_SHARED_DATA_SETTING.getKey(), environment.sharedDataFile(), "read,readlink,write,delete");
|
||||
addDirectoryPath(policy, Environment.PATH_SHARED_DATA_SETTING.getKey(), environment.sharedDataFile(),
|
||||
"read,readlink,write,delete");
|
||||
}
|
||||
final Set<Path> dataFilesPaths = new HashSet<>();
|
||||
for (Path path : environment.dataFiles()) {
|
||||
addPath(policy, Environment.PATH_DATA_SETTING.getKey(), path, "read,readlink,write,delete");
|
||||
addDirectoryPath(policy, Environment.PATH_DATA_SETTING.getKey(), path, "read,readlink,write,delete");
|
||||
/*
|
||||
* We have to do this after adding the path because a side effect of that is that the directory is created; the Path#toRealPath
|
||||
* invocation will fail if the directory does not already exist. We use Path#toRealPath to follow symlinks and handle issues
|
||||
|
@ -282,11 +284,11 @@ final class Security {
|
|||
}
|
||||
}
|
||||
for (Path path : environment.repoFiles()) {
|
||||
addPath(policy, Environment.PATH_REPO_SETTING.getKey(), path, "read,readlink,write,delete");
|
||||
addDirectoryPath(policy, Environment.PATH_REPO_SETTING.getKey(), path, "read,readlink,write,delete");
|
||||
}
|
||||
if (environment.pidFile() != null) {
|
||||
// we just need permission to remove the file if its elsewhere.
|
||||
policy.add(new FilePermission(environment.pidFile().toString(), "delete"));
|
||||
addSingleFilePath(policy, environment.pidFile(), "delete");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -367,27 +369,6 @@ final class Security {
|
|||
policy.add(new SocketPermission("*:" + portRange, "listen,resolve"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add access to path (and all files underneath it); this also creates the directory if it does not exist.
|
||||
*
|
||||
* @param policy current policy to add permissions to
|
||||
* @param configurationName the configuration name associated with the path (for error messages only)
|
||||
* @param path the path itself
|
||||
* @param permissions set of file permissions to grant to the path
|
||||
*/
|
||||
static void addPath(Permissions policy, String configurationName, Path path, String permissions) {
|
||||
// paths may not exist yet, this also checks accessibility
|
||||
try {
|
||||
ensureDirectoryExists(path);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to access '" + configurationName + "' (" + path + ")", e);
|
||||
}
|
||||
|
||||
// add each path twice: once for itself, again for files underneath it
|
||||
policy.add(new FilePermission(path.toString(), permissions));
|
||||
policy.add(new FilePermission(path.toString() + path.getFileSystem().getSeparator() + "-", permissions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures configured directory {@code path} exists.
|
||||
* @throws IOException if {@code path} exists, but is not a directory, not accessible, or broken symbolic link.
|
||||
|
|
|
@ -63,9 +63,6 @@
|
|||
# exceptions because stack traces are important for debugging
|
||||
-XX:-OmitStackTraceInFastThrow
|
||||
|
||||
# use old-style file permissions on JDK9
|
||||
-Djdk.io.permissionsUseCanonicalPath=true
|
||||
|
||||
# flags to configure Netty
|
||||
-Dio.netty.noUnsafe=true
|
||||
-Dio.netty.noKeySetOptimization=true
|
||||
|
|
|
@ -27,17 +27,19 @@ import org.apache.tika.parser.AutoDetectParser;
|
|||
import org.apache.tika.parser.Parser;
|
||||
import org.apache.tika.parser.ParserDecorator;
|
||||
import org.elasticsearch.SpecialPermission;
|
||||
import org.elasticsearch.bootstrap.FilePermissionUtils;
|
||||
import org.elasticsearch.bootstrap.JarHell;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FilePermission;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.ReflectPermission;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessController;
|
||||
|
@ -119,27 +121,32 @@ final class TikaImpl {
|
|||
|
||||
// compute some minimal permissions for parsers. they only get r/w access to the java temp directory,
|
||||
// the ability to load some resources from JARs, and read sysprops
|
||||
@SuppressForbidden(reason = "adds access to tmp directory")
|
||||
static PermissionCollection getRestrictedPermissions() {
|
||||
Permissions perms = new Permissions();
|
||||
// property/env access needed for parsing
|
||||
perms.add(new PropertyPermission("*", "read"));
|
||||
perms.add(new RuntimePermission("getenv.TIKA_CONFIG"));
|
||||
|
||||
// add permissions for resource access:
|
||||
// classpath
|
||||
addReadPermissions(perms, JarHell.parseClassPath());
|
||||
// plugin jars
|
||||
if (TikaImpl.class.getClassLoader() instanceof URLClassLoader) {
|
||||
URL[] urls = ((URLClassLoader)TikaImpl.class.getClassLoader()).getURLs();
|
||||
Set<URL> set = new LinkedHashSet<>(Arrays.asList(urls));
|
||||
if (set.size() != urls.length) {
|
||||
throw new AssertionError("duplicate jars: " + Arrays.toString(urls));
|
||||
try {
|
||||
// add permissions for resource access:
|
||||
// classpath
|
||||
addReadPermissions(perms, JarHell.parseClassPath());
|
||||
// plugin jars
|
||||
if (TikaImpl.class.getClassLoader() instanceof URLClassLoader) {
|
||||
URL[] urls = ((URLClassLoader)TikaImpl.class.getClassLoader()).getURLs();
|
||||
Set<URL> set = new LinkedHashSet<>(Arrays.asList(urls));
|
||||
if (set.size() != urls.length) {
|
||||
throw new AssertionError("duplicate jars: " + Arrays.toString(urls));
|
||||
}
|
||||
addReadPermissions(perms, set);
|
||||
}
|
||||
addReadPermissions(perms, set);
|
||||
// jvm's java.io.tmpdir (needs read/write)
|
||||
FilePermissionUtils.addDirectoryPath(perms, "java.io.tmpdir",
|
||||
PathUtils.get(System.getProperty("java.io.tmpdir")), "read,readlink,write,delete");
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
// jvm's java.io.tmpdir (needs read/write)
|
||||
perms.add(new FilePermission(System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "-",
|
||||
"read,readlink,write,delete"));
|
||||
// current hacks needed for POI/PDFbox issues:
|
||||
perms.add(new SecurityPermission("putProviderProperty.BC"));
|
||||
perms.add(new SecurityPermission("insertProvider"));
|
||||
|
@ -152,14 +159,15 @@ final class TikaImpl {
|
|||
|
||||
// add resources to (what is typically) a jar, but might not be (e.g. in tests/IDE)
|
||||
@SuppressForbidden(reason = "adds access to jar resources")
|
||||
static void addReadPermissions(Permissions perms, Set<URL> resources) {
|
||||
static void addReadPermissions(Permissions perms, Set<URL> resources) throws IOException {
|
||||
try {
|
||||
for (URL url : resources) {
|
||||
Path path = PathUtils.get(url.toURI());
|
||||
// resource itself
|
||||
perms.add(new FilePermission(path.toString(), "read,readlink"));
|
||||
// classes underneath
|
||||
perms.add(new FilePermission(path.toString() + System.getProperty("file.separator") + "-", "read,readlink"));
|
||||
if (Files.isDirectory(path)) {
|
||||
FilePermissionUtils.addDirectoryPath(perms, "class.path", path, "read,readlink");
|
||||
} else {
|
||||
FilePermissionUtils.addSingleFilePath(perms, path, "read,readlink");
|
||||
}
|
||||
}
|
||||
} catch (URISyntaxException bogus) {
|
||||
throw new RuntimeException(bogus);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package org.elasticsearch.bootstrap;
|
||||
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.FilePermission;
|
||||
|
@ -44,6 +45,7 @@ public class ESPolicyUnitTests extends ESTestCase {
|
|||
* even though ProtectionDomain's ctor javadocs might make you think
|
||||
* that the policy won't be consulted.
|
||||
*/
|
||||
@SuppressForbidden(reason = "to create FilePermission object")
|
||||
public void testNullCodeSource() throws Exception {
|
||||
assumeTrue("test cannot run with security manager", System.getSecurityManager() == null);
|
||||
// create a policy with AllPermission
|
||||
|
@ -61,6 +63,7 @@ public class ESPolicyUnitTests extends ESTestCase {
|
|||
* <p>
|
||||
* its unclear when/if this happens, see https://bugs.openjdk.java.net/browse/JDK-8129972
|
||||
*/
|
||||
@SuppressForbidden(reason = "to create FilePermission object")
|
||||
public void testNullLocation() throws Exception {
|
||||
assumeTrue("test cannot run with security manager", System.getSecurityManager() == null);
|
||||
PermissionCollection noPermissions = new Permissions();
|
||||
|
|
|
@ -22,7 +22,6 @@ package org.elasticsearch.bootstrap;
|
|||
import org.apache.lucene.util.Constants;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
@ -73,6 +72,7 @@ public class EvilSecurityTests extends ESTestCase {
|
|||
|
||||
/** test generated permissions for all configured paths */
|
||||
@SuppressWarnings("deprecation") // needs to check settings for deprecated path
|
||||
@SuppressForbidden(reason = "to create FilePermission object")
|
||||
public void testEnvironmentPaths() throws Exception {
|
||||
Path path = createTempDir();
|
||||
// make a fake ES home and ensure we only grant permissions to that.
|
||||
|
@ -217,7 +217,7 @@ public class EvilSecurityTests extends ESTestCase {
|
|||
assumeNoException("test cannot create symbolic links with security manager enabled", e);
|
||||
}
|
||||
Permissions permissions = new Permissions();
|
||||
Security.addPath(permissions, "testing", link, "read");
|
||||
FilePermissionUtils.addDirectoryPath(permissions, "testing", link, "read");
|
||||
assertExactPermissions(new FilePermission(link.toString(), "read"), permissions);
|
||||
assertExactPermissions(new FilePermission(link.resolve("foo").toString(), "read"), permissions);
|
||||
assertExactPermissions(new FilePermission(target.toString(), "read"), permissions);
|
||||
|
@ -227,6 +227,7 @@ public class EvilSecurityTests extends ESTestCase {
|
|||
/**
|
||||
* checks exact file permissions, meaning those and only those for that path.
|
||||
*/
|
||||
@SuppressForbidden(reason = "to create FilePermission object")
|
||||
static void assertExactPermissions(FilePermission expected, PermissionCollection actual) {
|
||||
String target = expected.getName(); // see javadocs
|
||||
Set<String> permissionSet = asSet(expected.getActions().split(","));
|
||||
|
@ -246,6 +247,7 @@ public class EvilSecurityTests extends ESTestCase {
|
|||
/**
|
||||
* checks that this path has no permissions
|
||||
*/
|
||||
@SuppressForbidden(reason = "to create FilePermission object")
|
||||
static void assertNoPermissions(Path path, PermissionCollection actual) {
|
||||
String target = path.toString();
|
||||
assertFalse(actual.implies(new FilePermission(target, "read")));
|
||||
|
|
|
@ -102,19 +102,19 @@ public class BootstrapForTesting {
|
|||
Permissions perms = new Permissions();
|
||||
Security.addClasspathPermissions(perms);
|
||||
// java.io.tmpdir
|
||||
Security.addPath(perms, "java.io.tmpdir", javaTmpDir, "read,readlink,write,delete");
|
||||
FilePermissionUtils.addDirectoryPath(perms, "java.io.tmpdir", javaTmpDir, "read,readlink,write,delete");
|
||||
// custom test config file
|
||||
if (Strings.hasLength(System.getProperty("tests.config"))) {
|
||||
perms.add(new FilePermission(System.getProperty("tests.config"), "read,readlink"));
|
||||
FilePermissionUtils.addSingleFilePath(perms, PathUtils.get(System.getProperty("tests.config")), "read,readlink");
|
||||
}
|
||||
// jacoco coverage output file
|
||||
final boolean testsCoverage =
|
||||
Booleans.parseBoolean(System.getProperty("tests.coverage", "false"));
|
||||
if (testsCoverage) {
|
||||
Path coverageDir = PathUtils.get(System.getProperty("tests.coverage.dir"));
|
||||
perms.add(new FilePermission(coverageDir.resolve("jacoco.exec").toString(), "read,write"));
|
||||
FilePermissionUtils.addSingleFilePath(perms, coverageDir.resolve("jacoco.exec"), "read,write");
|
||||
// in case we get fancy and use the -integration goals later:
|
||||
perms.add(new FilePermission(coverageDir.resolve("jacoco-it.exec").toString(), "read,write"));
|
||||
FilePermissionUtils.addSingleFilePath(perms, coverageDir.resolve("jacoco-it.exec"), "read,write");
|
||||
}
|
||||
// intellij hack: intellij test runner wants setIO and will
|
||||
// screw up all test logging without it!
|
||||
|
|
Loading…
Reference in New Issue