[Entitlements] Instrument nio path (#122507)

This commit is contained in:
Moritz Mack 2025-02-17 14:01:57 +01:00 committed by GitHub
parent 780cac5a6d
commit 7fd1addccf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 185 additions and 26 deletions

View file

@ -58,6 +58,8 @@ import java.nio.file.FileStore;
import java.nio.file.LinkOption; import java.nio.file.LinkOption;
import java.nio.file.OpenOption; import java.nio.file.OpenOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchService;
import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.UserPrincipal; import java.nio.file.attribute.UserPrincipal;
import java.nio.file.spi.FileSystemProvider; import java.nio.file.spi.FileSystemProvider;
@ -654,6 +656,19 @@ public interface EntitlementChecker {
void checkType(Class<?> callerClass, FileStore that); void checkType(Class<?> callerClass, FileStore that);
// path
void checkPathToRealPath(Class<?> callerClass, Path that, LinkOption... options);
void checkPathRegister(Class<?> callerClass, Path that, WatchService watcher, WatchEvent.Kind<?>... events);
void checkPathRegister(
Class<?> callerClass,
Path that,
WatchService watcher,
WatchEvent.Kind<?>[] events,
WatchEvent.Modifier... modifiers
);
//////////////////// ////////////////////
// //
// Thread management // Thread management
@ -674,5 +689,4 @@ public interface EntitlementChecker {
void check$java_lang_Thread$setUncaughtExceptionHandler(Class<?> callerClass, Thread thread, Thread.UncaughtExceptionHandler ueh); void check$java_lang_Thread$setUncaughtExceptionHandler(Class<?> callerClass, Thread thread, Thread.UncaughtExceptionHandler ueh);
void check$java_lang_ThreadGroup$setMaxPriority(Class<?> callerClass, ThreadGroup threadGroup, int pri); void check$java_lang_ThreadGroup$setMaxPriority(Class<?> callerClass, ThreadGroup threadGroup, int pri);
} }

View file

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.entitlement.qa.test;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.LinkOption;
import java.nio.file.WatchEvent;
import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.PLUGINS;
class PathActions {
@EntitlementTest(expectedAccess = PLUGINS)
static void checkToRealPath() throws IOException {
FileCheckActions.readFile().toRealPath();
}
@EntitlementTest(expectedAccess = PLUGINS)
static void checkToRealPathNoFollow() throws IOException {
FileCheckActions.readFile().toRealPath(LinkOption.NOFOLLOW_LINKS);
}
@SuppressWarnings("rawtypes")
@EntitlementTest(expectedAccess = PLUGINS)
static void checkRegister() throws IOException {
try (var watchService = FileSystems.getDefault().newWatchService()) {
FileCheckActions.readFile().register(watchService, new WatchEvent.Kind[0]);
} catch (IllegalArgumentException e) {
// intentionally no events registered
}
}
@SuppressWarnings("rawtypes")
@EntitlementTest(expectedAccess = PLUGINS)
static void checkRegisterWithModifiers() throws IOException {
try (var watchService = FileSystems.getDefault().newWatchService()) {
FileCheckActions.readFile().register(watchService, new WatchEvent.Kind[0], new WatchEvent.Modifier[0]);
} catch (IllegalArgumentException e) {
// intentionally no events registered
}
}
}

View file

@ -190,6 +190,7 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
getTestEntries(ManageThreadsActions.class), getTestEntries(ManageThreadsActions.class),
getTestEntries(NativeActions.class), getTestEntries(NativeActions.class),
getTestEntries(NioFileSystemActions.class), getTestEntries(NioFileSystemActions.class),
getTestEntries(PathActions.class),
getTestEntries(SpiActions.class), getTestEntries(SpiActions.class),
getTestEntries(SystemActions.class) getTestEntries(SystemActions.class)
) )

View file

@ -46,9 +46,12 @@ import java.nio.file.FileSystems;
import java.nio.file.LinkOption; import java.nio.file.LinkOption;
import java.nio.file.OpenOption; import java.nio.file.OpenOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchService;
import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttribute;
import java.nio.file.spi.FileSystemProvider; import java.nio.file.spi.FileSystemProvider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -96,6 +99,7 @@ public class EntitlementInitialization {
Stream.of( Stream.of(
fileSystemProviderChecks(), fileSystemProviderChecks(),
fileStoreChecks(), fileStoreChecks(),
pathChecks(),
Stream.of( Stream.of(
INSTRUMENTATION_SERVICE.lookupImplementationMethod( INSTRUMENTATION_SERVICE.lookupImplementationMethod(
SelectorProvider.class, SelectorProvider.class,
@ -149,33 +153,49 @@ public class EntitlementInitialization {
new LoadNativeLibrariesEntitlement(), new LoadNativeLibrariesEntitlement(),
new ManageThreadsEntitlement(), new ManageThreadsEntitlement(),
new FilesEntitlement( new FilesEntitlement(
List.of( Stream.concat(
FileData.ofPath(bootstrapArgs.tempDir(), READ_WRITE), Stream.of(
FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE), FileData.ofPath(bootstrapArgs.tempDir(), READ_WRITE),
// OS release on Linux FileData.ofPath(bootstrapArgs.configDir(), READ),
FileData.ofPath(Path.of("/etc/os-release"), READ), FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE),
FileData.ofPath(Path.of("/etc/system-release"), READ), // OS release on Linux
FileData.ofPath(Path.of("/usr/lib/os-release"), READ), FileData.ofPath(Path.of("/etc/os-release"), READ),
// read max virtual memory areas FileData.ofPath(Path.of("/etc/system-release"), READ),
FileData.ofPath(Path.of("/proc/sys/vm/max_map_count"), READ), FileData.ofPath(Path.of("/usr/lib/os-release"), READ),
FileData.ofPath(Path.of("/proc/meminfo"), READ), // read max virtual memory areas
// load averages on Linux FileData.ofPath(Path.of("/proc/sys/vm/max_map_count"), READ),
FileData.ofPath(Path.of("/proc/loadavg"), READ), FileData.ofPath(Path.of("/proc/meminfo"), READ),
// control group stats on Linux. cgroup v2 stats are in an unpredicable // load averages on Linux
// location under `/sys/fs/cgroup`, so unfortunately we have to allow FileData.ofPath(Path.of("/proc/loadavg"), READ),
// read access to the entire directory hierarchy. // control group stats on Linux. cgroup v2 stats are in an unpredicable
FileData.ofPath(Path.of("/proc/self/cgroup"), READ), // location under `/sys/fs/cgroup`, so unfortunately we have to allow
FileData.ofPath(Path.of("/sys/fs/cgroup/"), READ), // read access to the entire directory hierarchy.
// // io stats on Linux FileData.ofPath(Path.of("/proc/self/cgroup"), READ),
FileData.ofPath(Path.of("/proc/self/mountinfo"), READ), FileData.ofPath(Path.of("/sys/fs/cgroup/"), READ),
FileData.ofPath(Path.of("/proc/diskstats"), READ) // // io stats on Linux
) FileData.ofPath(Path.of("/proc/self/mountinfo"), READ),
FileData.ofPath(Path.of("/proc/diskstats"), READ)
),
Arrays.stream(bootstrapArgs.dataDirs()).map(d -> FileData.ofPath(d, READ))
).toList()
) )
) )
), ),
new Scope("org.apache.httpcomponents.httpclient", List.of(new OutboundNetworkEntitlement())), new Scope("org.apache.httpcomponents.httpclient", List.of(new OutboundNetworkEntitlement())),
new Scope("io.netty.transport", List.of(new InboundNetworkEntitlement(), new OutboundNetworkEntitlement())), new Scope("io.netty.transport", List.of(new InboundNetworkEntitlement(), new OutboundNetworkEntitlement())),
new Scope("org.apache.lucene.core", List.of(new LoadNativeLibrariesEntitlement(), new ManageThreadsEntitlement())), new Scope(
"org.apache.lucene.core",
List.of(
new LoadNativeLibrariesEntitlement(),
new ManageThreadsEntitlement(),
new FilesEntitlement(
Stream.concat(
Stream.of(FileData.ofPath(bootstrapArgs.configDir(), READ)),
Arrays.stream(bootstrapArgs.dataDirs()).map(d -> FileData.ofPath(d, READ_WRITE))
).toList()
)
)
),
new Scope("org.apache.logging.log4j.core", List.of(new ManageThreadsEntitlement())), new Scope("org.apache.logging.log4j.core", List.of(new ManageThreadsEntitlement())),
new Scope( new Scope(
"org.elasticsearch.nativeaccess", "org.elasticsearch.nativeaccess",
@ -289,6 +309,33 @@ public class EntitlementInitialization {
}); });
} }
private static Stream<InstrumentationService.InstrumentationInfo> pathChecks() {
var pathClasses = StreamSupport.stream(FileSystems.getDefault().getRootDirectories().spliterator(), false)
.map(Path::getClass)
.distinct();
return pathClasses.flatMap(pathClass -> {
InstrumentationInfoFactory instrumentation = (String methodName, Class<?>... parameterTypes) -> INSTRUMENTATION_SERVICE
.lookupImplementationMethod(
Path.class,
methodName,
pathClass,
EntitlementChecker.class,
"checkPath" + Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1),
parameterTypes
);
try {
return Stream.of(
instrumentation.of("toRealPath", LinkOption[].class),
instrumentation.of("register", WatchService.class, WatchEvent.Kind[].class),
instrumentation.of("register", WatchService.class, WatchEvent.Kind[].class, WatchEvent.Modifier[].class)
);
} catch (NoSuchMethodException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
});
}
/** /**
* Returns the "most recent" checker class compatible with the current runtime Java version. * Returns the "most recent" checker class compatible with the current runtime Java version.
* For checkers, we have (optionally) version specific classes, each with a prefix (e.g. Java23). * For checkers, we have (optionally) version specific classes, each with a prefix (e.g. Java23).

View file

@ -14,6 +14,7 @@ import org.elasticsearch.entitlement.bridge.EntitlementChecker;
import org.elasticsearch.entitlement.runtime.policy.PolicyManager; import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.PrintWriter; import java.io.PrintWriter;
@ -60,10 +61,13 @@ import java.nio.file.AccessMode;
import java.nio.file.CopyOption; import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream;
import java.nio.file.FileStore; import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.LinkOption; import java.nio.file.LinkOption;
import java.nio.file.OpenOption; import java.nio.file.OpenOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.nio.file.WatchEvent;
import java.nio.file.WatchService;
import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.UserPrincipal; import java.nio.file.attribute.UserPrincipal;
import java.nio.file.spi.FileSystemProvider; import java.nio.file.spi.FileSystemProvider;
@ -1369,4 +1373,38 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
public void checkType(Class<?> callerClass, FileStore that) { public void checkType(Class<?> callerClass, FileStore that) {
policyManager.checkReadStoreAttributes(callerClass); policyManager.checkReadStoreAttributes(callerClass);
} }
@Override
public void checkPathToRealPath(Class<?> callerClass, Path that, LinkOption... options) {
boolean followLinks = true;
for (LinkOption option : options) {
if (option == LinkOption.NOFOLLOW_LINKS) {
followLinks = false;
}
}
if (followLinks) {
try {
policyManager.checkFileRead(callerClass, Files.readSymbolicLink(that));
} catch (IOException | UnsupportedOperationException e) {
// that is not a link, or unrelated IOException or unsupported
}
}
policyManager.checkFileRead(callerClass, that);
}
@Override
public void checkPathRegister(Class<?> callerClass, Path that, WatchService watcher, WatchEvent.Kind<?>... events) {
policyManager.checkFileRead(callerClass, that);
}
@Override
public void checkPathRegister(
Class<?> callerClass,
Path that,
WatchService watcher,
WatchEvent.Kind<?>[] events,
WatchEvent.Modifier... modifiers
) {
policyManager.checkFileRead(callerClass, that);
}
} }

View file

@ -18,9 +18,11 @@ import org.apache.tika.parser.Parser;
import org.apache.tika.parser.ParserDecorator; import org.apache.tika.parser.ParserDecorator;
import org.elasticsearch.SpecialPermission; import org.elasticsearch.SpecialPermission;
import org.elasticsearch.bootstrap.FilePermissionUtils; import org.elasticsearch.bootstrap.FilePermissionUtils;
import org.elasticsearch.core.Booleans;
import org.elasticsearch.core.PathUtils; import org.elasticsearch.core.PathUtils;
import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.jdk.JarHell; import org.elasticsearch.jdk.JarHell;
import org.elasticsearch.jdk.RuntimeVersionFeature;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
@ -122,15 +124,22 @@ final class TikaImpl {
// apply additional containment for parsers, this is intersected with the current permissions // apply additional containment for parsers, this is intersected with the current permissions
// its hairy, but worth it so we don't have some XML flaw reading random crap from the FS // its hairy, but worth it so we don't have some XML flaw reading random crap from the FS
private static final AccessControlContext RESTRICTED_CONTEXT = new AccessControlContext( private static final AccessControlContext RESTRICTED_CONTEXT = isUsingSecurityManager()
new ProtectionDomain[] { new ProtectionDomain(null, getRestrictedPermissions()) } ? new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, getRestrictedPermissions()) })
); : null;
private static boolean isUsingSecurityManager() {
boolean entitlementsEnabled = Booleans.parseBoolean(System.getProperty("es.entitlements.enabled"), false)
|| RuntimeVersionFeature.isSecurityManagerAvailable() == false;
return entitlementsEnabled == false;
}
// compute some minimal permissions for parsers. they only get r/w access to the java temp directory, // 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 // the ability to load some resources from JARs, and read sysprops
@SuppressForbidden(reason = "adds access to tmp directory") @SuppressForbidden(reason = "adds access to tmp directory")
static PermissionCollection getRestrictedPermissions() { static PermissionCollection getRestrictedPermissions() {
Permissions perms = new Permissions(); Permissions perms = new Permissions();
// property/env access needed for parsing // property/env access needed for parsing
perms.add(new PropertyPermission("*", "read")); perms.add(new PropertyPermission("*", "read"));
perms.add(new RuntimePermission("getenv.TIKA_CONFIG")); perms.add(new RuntimePermission("getenv.TIKA_CONFIG"));