浏览代码

[Entitlements] Instrument nio path (#122507)

Moritz Mack 8 月之前
父节点
当前提交
7fd1addccf

+ 15 - 1
libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java

@@ -58,6 +58,8 @@ import java.nio.file.FileStore;
 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;
@@ -654,6 +656,19 @@ public interface EntitlementChecker {
 
     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
@@ -674,5 +689,4 @@ public interface EntitlementChecker {
     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);
-
 }

+ 50 - 0
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/PathActions.java

@@ -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
+        }
+    }
+}

+ 1 - 0
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java

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

+ 69 - 22
libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

@@ -46,9 +46,12 @@ import java.nio.file.FileSystems;
 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;
@@ -96,6 +99,7 @@ public class EntitlementInitialization {
         Stream.of(
             fileSystemProviderChecks(),
             fileStoreChecks(),
+            pathChecks(),
             Stream.of(
                 INSTRUMENTATION_SERVICE.lookupImplementationMethod(
                     SelectorProvider.class,
@@ -149,33 +153,49 @@ public class EntitlementInitialization {
                         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",
@@ -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.
      * For checkers, we have (optionally) version specific classes, each with a prefix (e.g. Java23).

+ 38 - 0
libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java

@@ -14,6 +14,7 @@ import org.elasticsearch.entitlement.bridge.EntitlementChecker;
 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;
@@ -60,10 +61,13 @@ import java.nio.file.AccessMode;
 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;
@@ -1369,4 +1373,38 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     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);
+    }
 }

+ 12 - 3
modules/ingest-attachment/src/main/java/org/elasticsearch/ingest/attachment/TikaImpl.java

@@ -18,9 +18,11 @@ import org.apache.tika.parser.Parser;
 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;
@@ -122,15 +124,22 @@ final class TikaImpl {
 
     // 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"));