Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
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.UserPrincipal;
import java.nio.file.spi.FileSystemProvider;
Expand Down Expand Up @@ -654,6 +656,19 @@ void checkNewByteChannel(

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
Expand All @@ -674,5 +689,4 @@ void checkNewByteChannel(
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);

}
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
getTestEntries(ManageThreadsActions.class),
getTestEntries(NativeActions.class),
getTestEntries(NioFileSystemActions.class),
getTestEntries(PathActions.class),
getTestEntries(SpiActions.class),
getTestEntries(SystemActions.class)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
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.spi.FileSystemProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -96,6 +99,7 @@ public static void initialize(Instrumentation inst) throws Exception {
Stream.of(
fileSystemProviderChecks(),
fileStoreChecks(),
pathChecks(),
Stream.of(
INSTRUMENTATION_SERVICE.lookupImplementationMethod(
SelectorProvider.class,
Expand Down Expand Up @@ -149,33 +153,49 @@ private static PolicyManager createPolicyManager() {
new LoadNativeLibrariesEntitlement(),
new ManageThreadsEntitlement(),
new FilesEntitlement(
List.of(
FileData.ofPath(bootstrapArgs.tempDir(), READ_WRITE),
FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE),
// OS release on Linux
FileData.ofPath(Path.of("/etc/os-release"), READ),
FileData.ofPath(Path.of("/etc/system-release"), READ),
FileData.ofPath(Path.of("/usr/lib/os-release"), READ),
// read max virtual memory areas
FileData.ofPath(Path.of("/proc/sys/vm/max_map_count"), READ),
FileData.ofPath(Path.of("/proc/meminfo"), READ),
// load averages on Linux
FileData.ofPath(Path.of("/proc/loadavg"), READ),
// control group stats on Linux. cgroup v2 stats are in an unpredicable
// location under `/sys/fs/cgroup`, so unfortunately we have to allow
// read access to the entire directory hierarchy.
FileData.ofPath(Path.of("/proc/self/cgroup"), READ),
FileData.ofPath(Path.of("/sys/fs/cgroup/"), READ),
// // io stats on Linux
FileData.ofPath(Path.of("/proc/self/mountinfo"), READ),
FileData.ofPath(Path.of("/proc/diskstats"), READ)
)
Stream.concat(
Stream.of(
FileData.ofPath(bootstrapArgs.tempDir(), READ_WRITE),
FileData.ofPath(bootstrapArgs.configDir(), READ),
FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE),
// OS release on Linux
FileData.ofPath(Path.of("/etc/os-release"), READ),
FileData.ofPath(Path.of("/etc/system-release"), READ),
FileData.ofPath(Path.of("/usr/lib/os-release"), READ),
// read max virtual memory areas
FileData.ofPath(Path.of("/proc/sys/vm/max_map_count"), READ),
FileData.ofPath(Path.of("/proc/meminfo"), READ),
// load averages on Linux
FileData.ofPath(Path.of("/proc/loadavg"), READ),
// control group stats on Linux. cgroup v2 stats are in an unpredicable
// location under `/sys/fs/cgroup`, so unfortunately we have to allow
// read access to the entire directory hierarchy.
FileData.ofPath(Path.of("/proc/self/cgroup"), READ),
FileData.ofPath(Path.of("/sys/fs/cgroup/"), 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("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.elasticsearch.nativeaccess",
Expand Down Expand Up @@ -289,6 +309,33 @@ public InstrumentationService.InstrumentationInfo of(String methodName, Class<?>
});
}

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.
* For checkers, we have (optionally) version specific classes, each with a prefix (e.g. Java23).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
Expand Down Expand Up @@ -60,10 +61,13 @@
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
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.UserPrincipal;
import java.nio.file.spi.FileSystemProvider;
Expand Down Expand Up @@ -1369,4 +1373,38 @@ public void checkName(Class<?> callerClass, FileStore that) {
public void checkType(Class<?> callerClass, FileStore that) {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
import org.apache.tika.parser.ParserDecorator;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.bootstrap.FilePermissionUtils;
import org.elasticsearch.core.Booleans;
import org.elasticsearch.core.PathUtils;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.jdk.JarHell;
import org.elasticsearch.jdk.RuntimeVersionFeature;

import java.io.ByteArrayInputStream;
import java.io.IOException;
Expand Down Expand Up @@ -122,15 +124,22 @@ static String parse(final byte content[], final Metadata metadata, final int lim

// 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
private static final AccessControlContext RESTRICTED_CONTEXT = new AccessControlContext(
new ProtectionDomain[] { new ProtectionDomain(null, getRestrictedPermissions()) }
);
private static final AccessControlContext RESTRICTED_CONTEXT = isUsingSecurityManager()
? 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,
// 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"));
Expand Down