Browse Source

[Entitlements] Add missing NIO async network instrumentation (#128582) (#128629)

This PR adds some additional instrumentation to ensure we capture more cases in which we use async network usage via channels and `select`

Co-authored-by: Lorenzo Dematté <lorenzo.dematte@elastic.co>
Patrick Doyle 5 months ago
parent
commit
b1cfb3934e

+ 30 - 0
libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java

@@ -46,6 +46,8 @@ import java.nio.channels.AsynchronousServerSocketChannel;
 import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.CompletionHandler;
 import java.nio.channels.CompletionHandler;
 import java.nio.channels.DatagramChannel;
 import java.nio.channels.DatagramChannel;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.Selector;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.spi.SelectorProvider;
 import java.nio.channels.spi.SelectorProvider;
@@ -580,6 +582,16 @@ public interface EntitlementChecker {
      * (not instrumentable).
      * (not instrumentable).
      */
      */
 
 
+    void check$java_nio_channels_spi_AbstractSelectableChannel$register(
+        Class<?> callerClass,
+        SelectableChannel that,
+        Selector sel,
+        int ops,
+        Object att
+    );
+
+    void check$java_nio_channels_SelectableChannel$register(Class<?> callerClass, SelectableChannel that, Selector sel, int ops);
+
     // bind
     // bind
 
 
     void check$java_nio_channels_AsynchronousServerSocketChannel$bind(
     void check$java_nio_channels_AsynchronousServerSocketChannel$bind(
@@ -603,6 +615,12 @@ public interface EntitlementChecker {
 
 
     void check$sun_nio_ch_ServerSocketChannelImpl$bind(Class<?> callerClass, ServerSocketChannel that, SocketAddress local, int backlog);
     void check$sun_nio_ch_ServerSocketChannelImpl$bind(Class<?> callerClass, ServerSocketChannel that, SocketAddress local, int backlog);
 
 
+    void check$java_nio_channels_SocketChannel$$open(Class<?> callerClass);
+
+    void check$java_nio_channels_SocketChannel$$open(Class<?> callerClass, java.net.ProtocolFamily family);
+
+    void check$java_nio_channels_SocketChannel$$open(Class<?> callerClass, SocketAddress remote);
+
     void check$sun_nio_ch_SocketChannelImpl$bind(Class<?> callerClass, SocketChannel that, SocketAddress local);
     void check$sun_nio_ch_SocketChannelImpl$bind(Class<?> callerClass, SocketChannel that, SocketAddress local);
 
 
     // connect
     // connect
@@ -650,6 +668,18 @@ public interface EntitlementChecker {
     // provider methods (dynamic)
     // provider methods (dynamic)
     void checkSelectorProviderInheritedChannel(Class<?> callerClass, SelectorProvider that);
     void checkSelectorProviderInheritedChannel(Class<?> callerClass, SelectorProvider that);
 
 
+    void checkSelectorProviderOpenDatagramChannel(Class<?> callerClass, SelectorProvider that);
+
+    void checkSelectorProviderOpenDatagramChannel(Class<?> callerClass, SelectorProvider that, java.net.ProtocolFamily family);
+
+    void checkSelectorProviderOpenServerSocketChannel(Class<?> callerClass, SelectorProvider that);
+
+    void checkSelectorProviderOpenServerSocketChannel(Class<?> callerClass, SelectorProvider that, java.net.ProtocolFamily family);
+
+    void checkSelectorProviderOpenSocketChannel(Class<?> callerClass, SelectorProvider that);
+
+    void checkSelectorProviderOpenSocketChannel(Class<?> callerClass, SelectorProvider that, java.net.ProtocolFamily family);
+
     /// /////////////////
     /// /////////////////
     //
     //
     // Load native libraries
     // Load native libraries

+ 71 - 0
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/DummyImplementations.java

@@ -13,6 +13,7 @@ import jdk.nio.Channels;
 
 
 import org.elasticsearch.core.SuppressForbidden;
 import org.elasticsearch.core.SuppressForbidden;
 
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStream;
 import java.net.DatagramPacket;
 import java.net.DatagramPacket;
@@ -41,9 +42,12 @@ import java.nio.channels.Pipe;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.SeekableByteChannel;
 import java.nio.channels.SeekableByteChannel;
 import java.nio.channels.SelectableChannel;
 import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.WritableByteChannel;
 import java.nio.channels.WritableByteChannel;
+import java.nio.channels.spi.AbstractSelectableChannel;
 import java.nio.channels.spi.AbstractSelector;
 import java.nio.channels.spi.AbstractSelector;
 import java.nio.channels.spi.AsynchronousChannelProvider;
 import java.nio.channels.spi.AsynchronousChannelProvider;
 import java.nio.channels.spi.SelectorProvider;
 import java.nio.channels.spi.SelectorProvider;
@@ -846,4 +850,71 @@ class DummyImplementations {
         @Override
         @Override
         public void implReleaseChannel(SelectableChannel sc) {}
         public void implReleaseChannel(SelectableChannel sc) {}
     }
     }
+
+    static class DummySelectableChannel extends AbstractSelectableChannel {
+        protected DummySelectableChannel(SelectorProvider provider) {
+            super(provider);
+        }
+
+        @Override
+        protected void implCloseSelectableChannel() throws IOException {
+
+        }
+
+        @Override
+        protected void implConfigureBlocking(boolean block) throws IOException {
+
+        }
+
+        @Override
+        public int validOps() {
+            return SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT;
+        }
+    }
+
+    static class DummySelector extends AbstractSelector {
+        protected DummySelector(SelectorProvider provider) {
+            super(provider);
+        }
+
+        @Override
+        protected void implCloseSelector() throws IOException {
+
+        }
+
+        @Override
+        protected SelectionKey register(AbstractSelectableChannel ch, int ops, Object att) {
+            return null;
+        }
+
+        @Override
+        public Set<SelectionKey> keys() {
+            return Set.of();
+        }
+
+        @Override
+        public Set<SelectionKey> selectedKeys() {
+            return Set.of();
+        }
+
+        @Override
+        public int selectNow() throws IOException {
+            return 0;
+        }
+
+        @Override
+        public int select(long timeout) throws IOException {
+            return 0;
+        }
+
+        @Override
+        public int select() throws IOException {
+            return 0;
+        }
+
+        @Override
+        public Selector wakeup() {
+            return null;
+        }
+    }
 }
 }

+ 15 - 0
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/NetworkAccessCheckActions.java

@@ -25,6 +25,7 @@ import java.net.ResponseCache;
 import java.net.ServerSocket;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.Socket;
 import java.net.SocketException;
 import java.net.SocketException;
+import java.net.StandardProtocolFamily;
 import java.net.URL;
 import java.net.URL;
 import java.net.URLConnection;
 import java.net.URLConnection;
 import java.net.URLStreamHandler;
 import java.net.URLStreamHandler;
@@ -203,6 +204,20 @@ class NetworkAccessCheckActions {
         }
         }
     }
     }
 
 
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void socketChannelOpenProtocol() throws IOException {
+        SocketChannel.open(StandardProtocolFamily.INET).close();
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void socketChannelOpenAddress() throws IOException {
+        try {
+            SocketChannel.open(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)).close();
+        } catch (SocketException ex) {
+            // Some sort of SocketException is expected, we are trying to connect to port 0
+        }
+    }
+
     @EntitlementTest(expectedAccess = PLUGINS)
     @EntitlementTest(expectedAccess = PLUGINS)
     static void asynchronousSocketChannelBind() throws IOException {
     static void asynchronousSocketChannelBind() throws IOException {
         try (var socketChannel = AsynchronousSocketChannel.open()) {
         try (var socketChannel = AsynchronousSocketChannel.open()) {

+ 49 - 0
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/NioChannelsActions.java

@@ -15,8 +15,11 @@ import org.elasticsearch.entitlement.qa.entitled.EntitledActions;
 
 
 import java.io.FileDescriptor;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.IOException;
+import java.net.StandardProtocolFamily;
 import java.nio.channels.AsynchronousFileChannel;
 import java.nio.channels.AsynchronousFileChannel;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.spi.SelectorProvider;
 import java.nio.file.StandardOpenOption;
 import java.nio.file.StandardOpenOption;
 import java.util.Set;
 import java.util.Set;
 
 
@@ -85,4 +88,50 @@ class NioChannelsActions {
     static void channelsReadWriteSelectableChannel() throws IOException {
     static void channelsReadWriteSelectableChannel() throws IOException {
         jdk.nio.Channels.readWriteSelectableChannel(new FileDescriptor(), new DummyImplementations.DummySelectableChannelCloser()).close();
         jdk.nio.Channels.readWriteSelectableChannel(new FileDescriptor(), new DummyImplementations.DummySelectableChannelCloser()).close();
     }
     }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void selectableChannelRegisterConnect() throws IOException {
+        try (var selectableChannel = new DummyImplementations.DummySelectableChannel(SelectorProvider.provider())) {
+            selectableChannel.configureBlocking(false);
+            selectableChannel.register(new DummyImplementations.DummySelector(SelectorProvider.provider()), SelectionKey.OP_CONNECT);
+        }
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void selectableChannelRegisterAccept() throws IOException {
+        try (var selectableChannel = new DummyImplementations.DummySelectableChannel(SelectorProvider.provider())) {
+            selectableChannel.configureBlocking(false);
+            selectableChannel.register(new DummyImplementations.DummySelector(SelectorProvider.provider()), SelectionKey.OP_ACCEPT);
+        }
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void selectorProviderOpenSocketChannel() throws IOException {
+        SelectorProvider.provider().openSocketChannel().close();
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void selectorProviderOpenDatagramChannel() throws IOException {
+        SelectorProvider.provider().openDatagramChannel().close();
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void selectorProviderOpenServerSocketChannel() throws IOException {
+        SelectorProvider.provider().openServerSocketChannel().close();
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void selectorProviderOpenSocketChannelWithProtocol() throws IOException {
+        SelectorProvider.provider().openSocketChannel(StandardProtocolFamily.INET).close();
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void selectorProviderOpenDatagramChannelWithProtocol() throws IOException {
+        SelectorProvider.provider().openDatagramChannel(StandardProtocolFamily.INET).close();
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void selectorProviderOpenServerSocketChannelWithProtocol() throws IOException {
+        SelectorProvider.provider().openServerSocketChannel(StandardProtocolFamily.INET).close();
+    }
 }
 }

+ 34 - 14
libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/DynamicInstrumentation.java

@@ -122,20 +122,7 @@ class DynamicInstrumentation {
     private static Map<MethodKey, CheckMethod> getMethodsToInstrument(Class<?> checkerInterface) throws ClassNotFoundException,
     private static Map<MethodKey, CheckMethod> getMethodsToInstrument(Class<?> checkerInterface) throws ClassNotFoundException,
         NoSuchMethodException {
         NoSuchMethodException {
         Map<MethodKey, CheckMethod> checkMethods = new HashMap<>(INSTRUMENTATION_SERVICE.lookupMethods(checkerInterface));
         Map<MethodKey, CheckMethod> checkMethods = new HashMap<>(INSTRUMENTATION_SERVICE.lookupMethods(checkerInterface));
-        Stream.of(
-            fileSystemProviderChecks(),
-            fileStoreChecks(),
-            pathChecks(),
-            Stream.of(
-                INSTRUMENTATION_SERVICE.lookupImplementationMethod(
-                    SelectorProvider.class,
-                    "inheritedChannel",
-                    SelectorProvider.provider().getClass(),
-                    EntitlementChecker.class,
-                    "checkSelectorProviderInheritedChannel"
-                )
-            )
-        )
+        Stream.of(fileSystemProviderChecks(), fileStoreChecks(), pathChecks(), selectorProviderChecks())
             .flatMap(Function.identity())
             .flatMap(Function.identity())
             .forEach(instrumentation -> checkMethods.put(instrumentation.targetMethod(), instrumentation.checkMethod()));
             .forEach(instrumentation -> checkMethods.put(instrumentation.targetMethod(), instrumentation.checkMethod()));
 
 
@@ -282,6 +269,39 @@ class DynamicInstrumentation {
         });
         });
     }
     }
 
 
+    private static Stream<InstrumentationService.InstrumentationInfo> selectorProviderChecks() {
+        var selectorProviderClass = SelectorProvider.provider().getClass();
+
+        var instrumentation = new InstrumentationInfoFactory() {
+            @Override
+            public InstrumentationService.InstrumentationInfo of(String methodName, Class<?>... parameterTypes)
+                throws ClassNotFoundException, NoSuchMethodException {
+                return INSTRUMENTATION_SERVICE.lookupImplementationMethod(
+                    SelectorProvider.class,
+                    methodName,
+                    selectorProviderClass,
+                    EntitlementChecker.class,
+                    "checkSelectorProvider" + Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1),
+                    parameterTypes
+                );
+            }
+        };
+
+        try {
+            return Stream.of(
+                instrumentation.of("inheritedChannel"),
+                instrumentation.of("openDatagramChannel"),
+                instrumentation.of("openDatagramChannel", java.net.ProtocolFamily.class),
+                instrumentation.of("openServerSocketChannel"),
+                instrumentation.of("openServerSocketChannel", java.net.ProtocolFamily.class),
+                instrumentation.of("openSocketChannel"),
+                instrumentation.of("openSocketChannel", java.net.ProtocolFamily.class)
+            );
+        } catch (NoSuchMethodException | ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private static Class<?>[] findClassesToRetransform(Class<?>[] loadedClasses, Set<String> classesToTransform) {
     private static Class<?>[] findClassesToRetransform(Class<?>[] loadedClasses, Set<String> classesToTransform) {
         List<Class<?>> retransform = new ArrayList<>();
         List<Class<?>> retransform = new ArrayList<>();
         for (Class<?> loadedClass : loadedClasses) {
         for (Class<?> loadedClass : loadedClasses) {

+ 3 - 1
libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

@@ -88,7 +88,9 @@ public class EntitlementInitialization {
         var classesToInitialize = Set.of(
         var classesToInitialize = Set.of(
             "sun.net.www.protocol.http.HttpURLConnection",
             "sun.net.www.protocol.http.HttpURLConnection",
             "sun.nio.ch.SocketChannelImpl",
             "sun.nio.ch.SocketChannelImpl",
-            "java.net.ProxySelector"
+            "java.net.ProxySelector",
+            "sun.nio.ch.DatagramChannelImpl",
+            "sun.nio.ch.ServerSocketChannelImpl"
         );
         );
         for (String className : classesToInitialize) {
         for (String className : classesToInitialize) {
             try {
             try {

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

@@ -33,6 +33,7 @@ import java.net.InetSocketAddress;
 import java.net.JarURLConnection;
 import java.net.JarURLConnection;
 import java.net.MulticastSocket;
 import java.net.MulticastSocket;
 import java.net.NetworkInterface;
 import java.net.NetworkInterface;
+import java.net.ProtocolFamily;
 import java.net.Proxy;
 import java.net.Proxy;
 import java.net.ProxySelector;
 import java.net.ProxySelector;
 import java.net.ResponseCache;
 import java.net.ResponseCache;
@@ -52,6 +53,9 @@ import java.nio.channels.AsynchronousServerSocketChannel;
 import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.CompletionHandler;
 import java.nio.channels.CompletionHandler;
 import java.nio.channels.DatagramChannel;
 import java.nio.channels.DatagramChannel;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.spi.SelectorProvider;
 import java.nio.channels.spi.SelectorProvider;
@@ -1138,6 +1142,27 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
         }
         }
     }
     }
 
 
+    @Override
+    public void check$java_nio_channels_spi_AbstractSelectableChannel$register(
+        Class<?> callerClass,
+        SelectableChannel that,
+        Selector sel,
+        int ops,
+        Object att
+    ) {
+        check$java_nio_channels_SelectableChannel$register(callerClass, that, sel, ops);
+    }
+
+    @Override
+    public void check$java_nio_channels_SelectableChannel$register(Class<?> callerClass, SelectableChannel that, Selector sel, int ops) {
+        if ((ops & SelectionKey.OP_CONNECT) != 0) {
+            policyChecker.checkOutboundNetworkAccess(callerClass);
+        }
+        if ((ops & SelectionKey.OP_ACCEPT) != 0) {
+            policyChecker.checkInboundNetworkAccess(callerClass);
+        }
+    }
+
     @Override
     @Override
     public void check$java_nio_channels_AsynchronousServerSocketChannel$bind(
     public void check$java_nio_channels_AsynchronousServerSocketChannel$bind(
         Class<?> callerClass,
         Class<?> callerClass,
@@ -1186,6 +1211,21 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
         policyChecker.checkInboundNetworkAccess(callerClass);
         policyChecker.checkInboundNetworkAccess(callerClass);
     }
     }
 
 
+    @Override
+    public void check$java_nio_channels_SocketChannel$$open(Class<?> callerClass) {
+        policyChecker.checkOutboundNetworkAccess(callerClass);
+    }
+
+    @Override
+    public void check$java_nio_channels_SocketChannel$$open(Class<?> callerClass, ProtocolFamily family) {
+        policyChecker.checkOutboundNetworkAccess(callerClass);
+    }
+
+    @Override
+    public void check$java_nio_channels_SocketChannel$$open(Class<?> callerClass, SocketAddress remote) {
+        policyChecker.checkOutboundNetworkAccess(callerClass);
+    }
+
     @Override
     @Override
     public void check$sun_nio_ch_SocketChannelImpl$bind(Class<?> callerClass, SocketChannel that, SocketAddress local) {
     public void check$sun_nio_ch_SocketChannelImpl$bind(Class<?> callerClass, SocketChannel that, SocketAddress local) {
         policyChecker.checkOutboundNetworkAccess(callerClass);
         policyChecker.checkOutboundNetworkAccess(callerClass);
@@ -1275,6 +1315,36 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
         policyChecker.checkChangeNetworkHandling(callerClass);
         policyChecker.checkChangeNetworkHandling(callerClass);
     }
     }
 
 
+    @Override
+    public void checkSelectorProviderOpenDatagramChannel(Class<?> callerClass, SelectorProvider that) {
+        policyChecker.checkOutboundNetworkAccess(callerClass);
+    }
+
+    @Override
+    public void checkSelectorProviderOpenDatagramChannel(Class<?> callerClass, SelectorProvider that, ProtocolFamily family) {
+        policyChecker.checkOutboundNetworkAccess(callerClass);
+    }
+
+    @Override
+    public void checkSelectorProviderOpenServerSocketChannel(Class<?> callerClass, SelectorProvider that) {
+        policyChecker.checkInboundNetworkAccess(callerClass);
+    }
+
+    @Override
+    public void checkSelectorProviderOpenServerSocketChannel(Class<?> callerClass, SelectorProvider that, ProtocolFamily family) {
+        policyChecker.checkInboundNetworkAccess(callerClass);
+    }
+
+    @Override
+    public void checkSelectorProviderOpenSocketChannel(Class<?> callerClass, SelectorProvider that) {
+        policyChecker.checkOutboundNetworkAccess(callerClass);
+    }
+
+    @Override
+    public void checkSelectorProviderOpenSocketChannel(Class<?> callerClass, SelectorProvider that, ProtocolFamily family) {
+        policyChecker.checkOutboundNetworkAccess(callerClass);
+    }
+
     @Override
     @Override
     public void check$java_lang_Runtime$load(Class<?> callerClass, Runtime that, String filename) {
     public void check$java_lang_Runtime$load(Class<?> callerClass, Runtime that, String filename) {
         policyChecker.checkFileRead(callerClass, Path.of(filename));
         policyChecker.checkFileRead(callerClass, Path.of(filename));

+ 2 - 0
modules/repository-azure/src/main/plugin-metadata/entitlement-policy.yaml

@@ -8,6 +8,8 @@ io.netty.common:
       mode: "read"
       mode: "read"
     - path: "/proc/sys/net/core/somaxconn"
     - path: "/proc/sys/net/core/somaxconn"
       mode: read
       mode: read
+io.netty.transport:
+  - outbound_network
 com.azure.identity:
 com.azure.identity:
   - outbound_network
   - outbound_network
   - files:
   - files: