Skip to content

Commit 554b96a

Browse files
authored
[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`
1 parent 7f05ab9 commit 554b96a

File tree

8 files changed

+276
-15
lines changed

8 files changed

+276
-15
lines changed

libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
import java.nio.channels.AsynchronousSocketChannel;
5454
import java.nio.channels.CompletionHandler;
5555
import java.nio.channels.DatagramChannel;
56+
import java.nio.channels.SelectableChannel;
57+
import java.nio.channels.Selector;
5658
import java.nio.channels.ServerSocketChannel;
5759
import java.nio.channels.SocketChannel;
5860
import java.nio.channels.spi.SelectorProvider;
@@ -588,6 +590,16 @@ public interface EntitlementChecker {
588590
* (not instrumentable).
589591
*/
590592

593+
void check$java_nio_channels_spi_AbstractSelectableChannel$register(
594+
Class<?> callerClass,
595+
SelectableChannel that,
596+
Selector sel,
597+
int ops,
598+
Object att
599+
);
600+
601+
void check$java_nio_channels_SelectableChannel$register(Class<?> callerClass, SelectableChannel that, Selector sel, int ops);
602+
591603
// bind
592604

593605
void check$java_nio_channels_AsynchronousServerSocketChannel$bind(
@@ -611,6 +623,12 @@ public interface EntitlementChecker {
611623

612624
void check$sun_nio_ch_ServerSocketChannelImpl$bind(Class<?> callerClass, ServerSocketChannel that, SocketAddress local, int backlog);
613625

626+
void check$java_nio_channels_SocketChannel$$open(Class<?> callerClass);
627+
628+
void check$java_nio_channels_SocketChannel$$open(Class<?> callerClass, java.net.ProtocolFamily family);
629+
630+
void check$java_nio_channels_SocketChannel$$open(Class<?> callerClass, SocketAddress remote);
631+
614632
void check$sun_nio_ch_SocketChannelImpl$bind(Class<?> callerClass, SocketChannel that, SocketAddress local);
615633

616634
// connect
@@ -658,6 +676,18 @@ public interface EntitlementChecker {
658676
// provider methods (dynamic)
659677
void checkSelectorProviderInheritedChannel(Class<?> callerClass, SelectorProvider that);
660678

679+
void checkSelectorProviderOpenDatagramChannel(Class<?> callerClass, SelectorProvider that);
680+
681+
void checkSelectorProviderOpenDatagramChannel(Class<?> callerClass, SelectorProvider that, java.net.ProtocolFamily family);
682+
683+
void checkSelectorProviderOpenServerSocketChannel(Class<?> callerClass, SelectorProvider that);
684+
685+
void checkSelectorProviderOpenServerSocketChannel(Class<?> callerClass, SelectorProvider that, java.net.ProtocolFamily family);
686+
687+
void checkSelectorProviderOpenSocketChannel(Class<?> callerClass, SelectorProvider that);
688+
689+
void checkSelectorProviderOpenSocketChannel(Class<?> callerClass, SelectorProvider that, java.net.ProtocolFamily family);
690+
661691
/// /////////////////
662692
//
663693
// Load native libraries

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import org.elasticsearch.core.SuppressForbidden;
1515

16+
import java.io.IOException;
1617
import java.io.InputStream;
1718
import java.io.OutputStream;
1819
import java.net.DatagramPacket;
@@ -41,9 +42,12 @@
4142
import java.nio.channels.ReadableByteChannel;
4243
import java.nio.channels.SeekableByteChannel;
4344
import java.nio.channels.SelectableChannel;
45+
import java.nio.channels.SelectionKey;
46+
import java.nio.channels.Selector;
4447
import java.nio.channels.ServerSocketChannel;
4548
import java.nio.channels.SocketChannel;
4649
import java.nio.channels.WritableByteChannel;
50+
import java.nio.channels.spi.AbstractSelectableChannel;
4751
import java.nio.channels.spi.AbstractSelector;
4852
import java.nio.channels.spi.AsynchronousChannelProvider;
4953
import java.nio.channels.spi.SelectorProvider;
@@ -846,4 +850,71 @@ public void implCloseChannel(SelectableChannel sc) {}
846850
@Override
847851
public void implReleaseChannel(SelectableChannel sc) {}
848852
}
853+
854+
static class DummySelectableChannel extends AbstractSelectableChannel {
855+
protected DummySelectableChannel(SelectorProvider provider) {
856+
super(provider);
857+
}
858+
859+
@Override
860+
protected void implCloseSelectableChannel() throws IOException {
861+
862+
}
863+
864+
@Override
865+
protected void implConfigureBlocking(boolean block) throws IOException {
866+
867+
}
868+
869+
@Override
870+
public int validOps() {
871+
return SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT;
872+
}
873+
}
874+
875+
static class DummySelector extends AbstractSelector {
876+
protected DummySelector(SelectorProvider provider) {
877+
super(provider);
878+
}
879+
880+
@Override
881+
protected void implCloseSelector() throws IOException {
882+
883+
}
884+
885+
@Override
886+
protected SelectionKey register(AbstractSelectableChannel ch, int ops, Object att) {
887+
return null;
888+
}
889+
890+
@Override
891+
public Set<SelectionKey> keys() {
892+
return Set.of();
893+
}
894+
895+
@Override
896+
public Set<SelectionKey> selectedKeys() {
897+
return Set.of();
898+
}
899+
900+
@Override
901+
public int selectNow() throws IOException {
902+
return 0;
903+
}
904+
905+
@Override
906+
public int select(long timeout) throws IOException {
907+
return 0;
908+
}
909+
910+
@Override
911+
public int select() throws IOException {
912+
return 0;
913+
}
914+
915+
@Override
916+
public Selector wakeup() {
917+
return null;
918+
}
919+
}
849920
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.net.ServerSocket;
2626
import java.net.Socket;
2727
import java.net.SocketException;
28+
import java.net.StandardProtocolFamily;
2829
import java.net.URL;
2930
import java.net.URLConnection;
3031
import java.net.URLStreamHandler;
@@ -203,6 +204,20 @@ static void socketChannelConnect() throws IOException {
203204
}
204205
}
205206

207+
@EntitlementTest(expectedAccess = PLUGINS)
208+
static void socketChannelOpenProtocol() throws IOException {
209+
SocketChannel.open(StandardProtocolFamily.INET).close();
210+
}
211+
212+
@EntitlementTest(expectedAccess = PLUGINS)
213+
static void socketChannelOpenAddress() throws IOException {
214+
try {
215+
SocketChannel.open(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)).close();
216+
} catch (SocketException ex) {
217+
// Some sort of SocketException is expected, we are trying to connect to port 0
218+
}
219+
}
220+
206221
@EntitlementTest(expectedAccess = PLUGINS)
207222
static void asynchronousSocketChannelBind() throws IOException {
208223
try (var socketChannel = AsynchronousSocketChannel.open()) {

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515

1616
import java.io.FileDescriptor;
1717
import java.io.IOException;
18+
import java.net.StandardProtocolFamily;
1819
import java.nio.channels.AsynchronousFileChannel;
1920
import java.nio.channels.FileChannel;
21+
import java.nio.channels.SelectionKey;
22+
import java.nio.channels.spi.SelectorProvider;
2023
import java.nio.file.StandardOpenOption;
2124
import java.util.Set;
2225

@@ -85,4 +88,50 @@ static void asynchronousFileChannelOpenForReadWithOptions() throws IOException {
8588
static void channelsReadWriteSelectableChannel() throws IOException {
8689
jdk.nio.Channels.readWriteSelectableChannel(new FileDescriptor(), new DummyImplementations.DummySelectableChannelCloser()).close();
8790
}
91+
92+
@EntitlementTest(expectedAccess = PLUGINS)
93+
static void selectableChannelRegisterConnect() throws IOException {
94+
try (var selectableChannel = new DummyImplementations.DummySelectableChannel(SelectorProvider.provider())) {
95+
selectableChannel.configureBlocking(false);
96+
selectableChannel.register(new DummyImplementations.DummySelector(SelectorProvider.provider()), SelectionKey.OP_CONNECT);
97+
}
98+
}
99+
100+
@EntitlementTest(expectedAccess = PLUGINS)
101+
static void selectableChannelRegisterAccept() throws IOException {
102+
try (var selectableChannel = new DummyImplementations.DummySelectableChannel(SelectorProvider.provider())) {
103+
selectableChannel.configureBlocking(false);
104+
selectableChannel.register(new DummyImplementations.DummySelector(SelectorProvider.provider()), SelectionKey.OP_ACCEPT);
105+
}
106+
}
107+
108+
@EntitlementTest(expectedAccess = PLUGINS)
109+
static void selectorProviderOpenSocketChannel() throws IOException {
110+
SelectorProvider.provider().openSocketChannel().close();
111+
}
112+
113+
@EntitlementTest(expectedAccess = PLUGINS)
114+
static void selectorProviderOpenDatagramChannel() throws IOException {
115+
SelectorProvider.provider().openDatagramChannel().close();
116+
}
117+
118+
@EntitlementTest(expectedAccess = PLUGINS)
119+
static void selectorProviderOpenServerSocketChannel() throws IOException {
120+
SelectorProvider.provider().openServerSocketChannel().close();
121+
}
122+
123+
@EntitlementTest(expectedAccess = PLUGINS)
124+
static void selectorProviderOpenSocketChannelWithProtocol() throws IOException {
125+
SelectorProvider.provider().openSocketChannel(StandardProtocolFamily.INET).close();
126+
}
127+
128+
@EntitlementTest(expectedAccess = PLUGINS)
129+
static void selectorProviderOpenDatagramChannelWithProtocol() throws IOException {
130+
SelectorProvider.provider().openDatagramChannel(StandardProtocolFamily.INET).close();
131+
}
132+
133+
@EntitlementTest(expectedAccess = PLUGINS)
134+
static void selectorProviderOpenServerSocketChannelWithProtocol() throws IOException {
135+
SelectorProvider.provider().openServerSocketChannel(StandardProtocolFamily.INET).close();
136+
}
88137
}

libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/DynamicInstrumentation.java

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,20 +122,7 @@ static void initialize(Instrumentation inst, Class<?> checkerInterface, boolean
122122
private static Map<MethodKey, CheckMethod> getMethodsToInstrument(Class<?> checkerInterface) throws ClassNotFoundException,
123123
NoSuchMethodException {
124124
Map<MethodKey, CheckMethod> checkMethods = new HashMap<>(INSTRUMENTATION_SERVICE.lookupMethods(checkerInterface));
125-
Stream.of(
126-
fileSystemProviderChecks(),
127-
fileStoreChecks(),
128-
pathChecks(),
129-
Stream.of(
130-
INSTRUMENTATION_SERVICE.lookupImplementationMethod(
131-
SelectorProvider.class,
132-
"inheritedChannel",
133-
SelectorProvider.provider().getClass(),
134-
EntitlementChecker.class,
135-
"checkSelectorProviderInheritedChannel"
136-
)
137-
)
138-
)
125+
Stream.of(fileSystemProviderChecks(), fileStoreChecks(), pathChecks(), selectorProviderChecks())
139126
.flatMap(Function.identity())
140127
.forEach(instrumentation -> checkMethods.put(instrumentation.targetMethod(), instrumentation.checkMethod()));
141128

@@ -257,6 +244,39 @@ private static Stream<InstrumentationService.InstrumentationInfo> pathChecks() {
257244
});
258245
}
259246

247+
private static Stream<InstrumentationService.InstrumentationInfo> selectorProviderChecks() {
248+
var selectorProviderClass = SelectorProvider.provider().getClass();
249+
250+
var instrumentation = new InstrumentationInfoFactory() {
251+
@Override
252+
public InstrumentationService.InstrumentationInfo of(String methodName, Class<?>... parameterTypes)
253+
throws ClassNotFoundException, NoSuchMethodException {
254+
return INSTRUMENTATION_SERVICE.lookupImplementationMethod(
255+
SelectorProvider.class,
256+
methodName,
257+
selectorProviderClass,
258+
EntitlementChecker.class,
259+
"checkSelectorProvider" + Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1),
260+
parameterTypes
261+
);
262+
}
263+
};
264+
265+
try {
266+
return Stream.of(
267+
instrumentation.of("inheritedChannel"),
268+
instrumentation.of("openDatagramChannel"),
269+
instrumentation.of("openDatagramChannel", java.net.ProtocolFamily.class),
270+
instrumentation.of("openServerSocketChannel"),
271+
instrumentation.of("openServerSocketChannel", java.net.ProtocolFamily.class),
272+
instrumentation.of("openSocketChannel"),
273+
instrumentation.of("openSocketChannel", java.net.ProtocolFamily.class)
274+
);
275+
} catch (NoSuchMethodException | ClassNotFoundException e) {
276+
throw new RuntimeException(e);
277+
}
278+
}
279+
260280
private static Class<?>[] findClassesToRetransform(Class<?>[] loadedClasses, Set<String> classesToTransform) {
261281
List<Class<?>> retransform = new ArrayList<>();
262282
for (Class<?> loadedClass : loadedClasses) {

libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ public static void initialize(Instrumentation inst) throws Exception {
8585
* transformed and undergo verification. In order to avoid circularity errors as much as possible, we force a partial order.
8686
*/
8787
private static void ensureClassesSensitiveToVerificationAreInitialized() {
88-
var classesToInitialize = Set.of("sun.net.www.protocol.http.HttpURLConnection");
88+
var classesToInitialize = Set.of(
89+
"sun.net.www.protocol.http.HttpURLConnection",
90+
"sun.nio.ch.DatagramChannelImpl",
91+
"sun.nio.ch.ServerSocketChannelImpl"
92+
);
8993
for (String className : classesToInitialize) {
9094
try {
9195
Class.forName(className);

0 commit comments

Comments
 (0)