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

This PR adds some additional instrumentation to ensure we capture more cases in which we use async network usage via channels and `select`
This commit is contained in:
Lorenzo Dematté 2025-05-29 18:52:10 +02:00 committed by GitHub
parent 7f05ab9cf6
commit 554b96aec9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 276 additions and 15 deletions

View file

@ -53,6 +53,8 @@ import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
@ -588,6 +590,16 @@ public interface EntitlementChecker {
* (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
void check$java_nio_channels_AsynchronousServerSocketChannel$bind(
@ -611,6 +623,12 @@ public interface EntitlementChecker {
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);
// connect
@ -658,6 +676,18 @@ public interface EntitlementChecker {
// provider methods (dynamic)
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

View file

@ -13,6 +13,7 @@ import jdk.nio.Channels;
import org.elasticsearch.core.SuppressForbidden;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
@ -41,9 +42,12 @@ import java.nio.channels.Pipe;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.AbstractSelector;
import java.nio.channels.spi.AsynchronousChannelProvider;
import java.nio.channels.spi.SelectorProvider;
@ -846,4 +850,71 @@ class DummyImplementations {
@Override
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;
}
}
}

View file

@ -25,6 +25,7 @@ import java.net.ResponseCache;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.StandardProtocolFamily;
import java.net.URL;
import java.net.URLConnection;
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)
static void asynchronousSocketChannelBind() throws IOException {
try (var socketChannel = AsynchronousSocketChannel.open()) {

View file

@ -15,8 +15,11 @@ import org.elasticsearch.entitlement.qa.entitled.EntitledActions;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.StandardProtocolFamily;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.spi.SelectorProvider;
import java.nio.file.StandardOpenOption;
import java.util.Set;
@ -85,4 +88,50 @@ class NioChannelsActions {
static void channelsReadWriteSelectableChannel() throws IOException {
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();
}
}

View file

@ -122,20 +122,7 @@ class DynamicInstrumentation {
private static Map<MethodKey, CheckMethod> getMethodsToInstrument(Class<?> checkerInterface) throws ClassNotFoundException,
NoSuchMethodException {
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())
.forEach(instrumentation -> checkMethods.put(instrumentation.targetMethod(), instrumentation.checkMethod()));
@ -257,6 +244,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) {
List<Class<?>> retransform = new ArrayList<>();
for (Class<?> loadedClass : loadedClasses) {

View file

@ -85,7 +85,11 @@ public class EntitlementInitialization {
* transformed and undergo verification. In order to avoid circularity errors as much as possible, we force a partial order.
*/
private static void ensureClassesSensitiveToVerificationAreInitialized() {
var classesToInitialize = Set.of("sun.net.www.protocol.http.HttpURLConnection");
var classesToInitialize = Set.of(
"sun.net.www.protocol.http.HttpURLConnection",
"sun.nio.ch.DatagramChannelImpl",
"sun.nio.ch.ServerSocketChannelImpl"
);
for (String className : classesToInitialize) {
try {
Class.forName(className);

View file

@ -40,6 +40,7 @@ import java.net.InetSocketAddress;
import java.net.JarURLConnection;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.ProtocolFamily;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.ResponseCache;
@ -59,6 +60,9 @@ import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
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.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
@ -1146,6 +1150,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
public void check$java_nio_channels_AsynchronousServerSocketChannel$bind(
Class<?> callerClass,
@ -1194,6 +1219,21 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
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
public void check$sun_nio_ch_SocketChannelImpl$bind(Class<?> callerClass, SocketChannel that, SocketAddress local) {
policyChecker.checkOutboundNetworkAccess(callerClass);
@ -1283,6 +1323,36 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
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
public void check$java_lang_Runtime$load(Class<?> callerClass, Runtime that, String filename) {
policyChecker.checkFileRead(callerClass, Path.of(filename));

View file

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