Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x][Entitlements] Network access checks for NIO classes (#120138) #120394

Open
wants to merge 1 commit into
base: 8.x
Choose a base branch
from
Open
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 @@ -33,6 +33,13 @@
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.DatagramChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.cert.CertStoreParameters;
import java.util.List;

Expand Down Expand Up @@ -291,4 +298,73 @@ public interface EntitlementChecker {
// We need to check the LDAPCertStore, as this will connect, but this is internal/created via SPI,
// so we instrument the general factory instead and then filter in the check method implementation
void check$java_security_cert_CertStore$$getInstance(Class<?> callerClass, String type, CertStoreParameters params);

/* NIO
* For NIO, we are sometime able to check a method on the public surface/interface (e.g. AsynchronousServerSocketChannel#bind)
* but most of the time these methods are abstract in the public classes/interfaces (e.g. ServerSocketChannel#accept,
* NetworkChannel#bind), so we are forced to implement the "impl" classes.
* You can distinguish the 2 cases form the namespaces: java_nio_channels for the public ones, sun_nio_ch for the implementation
* classes. When you see a check on a sun_nio_ch class/method, this means the matching method on the public class is abstract
* (not instrumentable).
*/

// bind

void check$java_nio_channels_AsynchronousServerSocketChannel$bind(
Class<?> callerClass,
AsynchronousServerSocketChannel that,
SocketAddress local
);

void check$sun_nio_ch_AsynchronousServerSocketChannelImpl$bind(
Class<?> callerClass,
AsynchronousServerSocketChannel that,
SocketAddress local,
int backlog
);

void check$sun_nio_ch_AsynchronousSocketChannelImpl$bind(Class<?> callerClass, AsynchronousSocketChannel that, SocketAddress local);

void check$sun_nio_ch_DatagramChannelImpl$bind(Class<?> callerClass, DatagramChannel that, SocketAddress local);

void check$java_nio_channels_ServerSocketChannel$bind(Class<?> callerClass, ServerSocketChannel that, SocketAddress local);

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

void check$sun_nio_ch_SocketChannelImpl$bind(Class<?> callerClass, SocketChannel that, SocketAddress local);

// connect

void check$sun_nio_ch_SocketChannelImpl$connect(Class<?> callerClass, SocketChannel that, SocketAddress remote);

void check$sun_nio_ch_AsynchronousSocketChannelImpl$connect(Class<?> callerClass, AsynchronousSocketChannel that, SocketAddress remote);

void check$sun_nio_ch_AsynchronousSocketChannelImpl$connect(
Class<?> callerClass,
AsynchronousSocketChannel that,
SocketAddress remote,
Object attachment,
CompletionHandler<Void, Object> handler
);

void check$sun_nio_ch_DatagramChannelImpl$connect(Class<?> callerClass, DatagramChannel that, SocketAddress remote);

// accept

void check$sun_nio_ch_ServerSocketChannelImpl$accept(Class<?> callerClass, ServerSocketChannel that);

void check$sun_nio_ch_AsynchronousServerSocketChannelImpl$accept(Class<?> callerClass, AsynchronousServerSocketChannel that);

void check$sun_nio_ch_AsynchronousServerSocketChannelImpl$accept(
Class<?> callerClass,
AsynchronousServerSocketChannel that,
Object attachment,
CompletionHandler<AsynchronousSocketChannel, Object> handler
);

// send/receive

void check$sun_nio_ch_DatagramChannelImpl$send(Class<?> callerClass, DatagramChannel that, ByteBuffer src, SocketAddress target);

void check$sun_nio_ch_DatagramChannelImpl$receive(Class<?> callerClass, DatagramChannel that, ByteBuffer dst);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,24 @@
import java.net.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.DatagramChannel;
import java.nio.channels.NotYetBoundException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertStore;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;

@SuppressForbidden(reason = "Testing entitlement check on forbidden action")
class NetworkAccessCheckActions {

static void serverSocketAccept() throws IOException {
Expand All @@ -46,7 +57,6 @@ static void serverSocketBind() throws IOException {
}
}

@SuppressForbidden(reason = "Testing entitlement check on forbidden action")
static void createSocketWithProxy() throws IOException {
try (Socket socket = new Socket(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(0)))) {
assert socket.isBound() == false;
Expand All @@ -59,14 +69,12 @@ static void socketBind() throws IOException {
}
}

@SuppressForbidden(reason = "Testing entitlement check on forbidden action")
static void socketConnect() throws IOException {
try (Socket socket = new DummyImplementations.DummySocket()) {
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
}
}

@SuppressForbidden(reason = "Testing entitlement check on forbidden action")
static void urlOpenConnectionWithProxy() throws URISyntaxException, IOException {
var url = new URI("http://localhost").toURL();
var urlConnection = url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(0)));
Expand All @@ -82,4 +90,154 @@ static void createLDAPCertStore() throws NoSuchAlgorithmException {
assert Arrays.stream(ex.getStackTrace()).anyMatch(e -> e.getClassName().endsWith("LDAPCertStore"));
}
}

static void serverSocketChannelBind() throws IOException {
try (var serverSocketChannel = ServerSocketChannel.open()) {
serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
}
}

static void serverSocketChannelBindWithBacklog() throws IOException {
try (var serverSocketChannel = ServerSocketChannel.open()) {
serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 50);
}
}

static void serverSocketChannelAccept() throws IOException {
try (var serverSocketChannel = ServerSocketChannel.open()) {
serverSocketChannel.configureBlocking(false);
try {
serverSocketChannel.accept();
} catch (NotYetBoundException e) {
// It's OK, we did not call bind on the socket on purpose so we can just test "accept"
// "accept" will be called and exercise the Entitlement check, we don't care if it fails afterward for this known reason.
}
}
}

static void asynchronousServerSocketChannelBind() throws IOException {
try (var serverSocketChannel = AsynchronousServerSocketChannel.open()) {
serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
}
}

static void asynchronousServerSocketChannelBindWithBacklog() throws IOException {
try (var serverSocketChannel = AsynchronousServerSocketChannel.open()) {
serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 50);
}
}

static void asynchronousServerSocketChannelAccept() throws IOException {
try (var serverSocketChannel = AsynchronousServerSocketChannel.open()) {
try {
var future = serverSocketChannel.accept();
future.cancel(true);
} catch (NotYetBoundException e) {
// It's OK, we did not call bind on the socket on purpose so we can just test "accept"
// "accept" will be called and exercise the Entitlement check, we don't care if it fails afterward for this known reason.
}
}
}

static void asynchronousServerSocketChannelAcceptWithHandler() throws IOException {
try (var serverSocketChannel = AsynchronousServerSocketChannel.open()) {
try {
serverSocketChannel.accept(null, new CompletionHandler<>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {}

@Override
public void failed(Throwable exc, Object attachment) {
assert exc.getClass().getSimpleName().equals("NotEntitledException") == false;
}
});
} catch (NotYetBoundException e) {
// It's OK, we did not call bind on the socket on purpose so we can just test "accept"
// "accept" will be called and exercise the Entitlement check, we don't care if it fails afterward for this known reason.
}
}
}

static void socketChannelBind() throws IOException {
try (var socketChannel = SocketChannel.open()) {
socketChannel.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
}
}

static void socketChannelConnect() throws IOException {
try (var socketChannel = SocketChannel.open()) {
try {
socketChannel.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
} catch (SocketException e) {
// We expect to fail, not a valid address to connect to.
// "connect" will be called and exercise the Entitlement check, we don't care if it fails afterward for this known reason.
}
}
}

static void asynchronousSocketChannelBind() throws IOException {
try (var socketChannel = AsynchronousSocketChannel.open()) {
socketChannel.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
}
}

static void asynchronousSocketChannelConnect() throws IOException, InterruptedException {
try (var socketChannel = AsynchronousSocketChannel.open()) {
var future = socketChannel.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
try {
future.get();
} catch (ExecutionException e) {
assert e.getCause().getClass().getSimpleName().equals("NotEntitledException") == false;
} finally {
future.cancel(true);
}
}
}

static void asynchronousSocketChannelConnectWithCompletion() throws IOException {
try (var socketChannel = AsynchronousSocketChannel.open()) {
socketChannel.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), null, new CompletionHandler<>() {
@Override
public void completed(Void result, Object attachment) {}

@Override
public void failed(Throwable exc, Object attachment) {
assert exc.getClass().getSimpleName().equals("NotEntitledException") == false;
}
});
}
}

static void datagramChannelBind() throws IOException {
try (var channel = DatagramChannel.open()) {
channel.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
}
}

static void datagramChannelConnect() throws IOException {
try (var channel = DatagramChannel.open()) {
channel.configureBlocking(false);
try {
channel.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
} catch (SocketException e) {
// We expect to fail, not a valid address to connect to.
// "connect" will be called and exercise the Entitlement check, we don't care if it fails afterward for this known reason.
}
}
}

static void datagramChannelSend() throws IOException {
try (var channel = DatagramChannel.open()) {
channel.configureBlocking(false);
channel.send(ByteBuffer.wrap(new byte[] { 0 }), new InetSocketAddress(InetAddress.getLoopbackAddress(), 1234));
}
}

static void datagramChannelReceive() throws IOException {
try (var channel = DatagramChannel.open()) {
channel.configureBlocking(false);
var buffer = new byte[1];
channel.receive(ByteBuffer.wrap(buffer));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
}
}

private static final Map<String, CheckAction> checkActions = Stream.of(
private static final Map<String, CheckAction> checkActions = Stream.<Map.Entry<String, CheckAction>>of(
entry("runtime_exit", deniedToPlugins(RestEntitlementsCheckAction::runtimeExit)),
entry("runtime_halt", deniedToPlugins(RestEntitlementsCheckAction::runtimeHalt)),
entry("system_exit", deniedToPlugins(RestEntitlementsCheckAction::systemExit)),
Expand Down Expand Up @@ -163,7 +163,33 @@ static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
entry("http_client_builder_build", forPlugins(VersionSpecificNetworkChecks::httpClientBuilderBuild)),
entry("http_client_send", forPlugins(VersionSpecificNetworkChecks::httpClientSend)),
entry("http_client_send_async", forPlugins(VersionSpecificNetworkChecks::httpClientSendAsync)),
entry("create_ldap_cert_store", forPlugins(NetworkAccessCheckActions::createLDAPCertStore))
entry("create_ldap_cert_store", forPlugins(NetworkAccessCheckActions::createLDAPCertStore)),

entry("server_socket_channel_bind", forPlugins(NetworkAccessCheckActions::serverSocketChannelBind)),
entry("server_socket_channel_bind_backlog", forPlugins(NetworkAccessCheckActions::serverSocketChannelBindWithBacklog)),
entry("server_socket_channel_accept", forPlugins(NetworkAccessCheckActions::serverSocketChannelAccept)),
entry("asynchronous_server_socket_channel_bind", forPlugins(NetworkAccessCheckActions::asynchronousServerSocketChannelBind)),
entry(
"asynchronous_server_socket_channel_bind_backlog",
forPlugins(NetworkAccessCheckActions::asynchronousServerSocketChannelBindWithBacklog)
),
entry("asynchronous_server_socket_channel_accept", forPlugins(NetworkAccessCheckActions::asynchronousServerSocketChannelAccept)),
entry(
"asynchronous_server_socket_channel_accept_with_handler",
forPlugins(NetworkAccessCheckActions::asynchronousServerSocketChannelAcceptWithHandler)
),
entry("socket_channel_bind", forPlugins(NetworkAccessCheckActions::socketChannelBind)),
entry("socket_channel_connect", forPlugins(NetworkAccessCheckActions::socketChannelConnect)),
entry("asynchronous_socket_channel_bind", forPlugins(NetworkAccessCheckActions::asynchronousSocketChannelBind)),
entry("asynchronous_socket_channel_connect", forPlugins(NetworkAccessCheckActions::asynchronousSocketChannelConnect)),
entry(
"asynchronous_socket_channel_connect_with_completion",
forPlugins(NetworkAccessCheckActions::asynchronousSocketChannelConnectWithCompletion)
),
entry("datagram_channel_bind", forPlugins(NetworkAccessCheckActions::datagramChannelBind)),
entry("datagram_channel_connect", forPlugins(NetworkAccessCheckActions::datagramChannelConnect)),
entry("datagram_channel_send", forPlugins(NetworkAccessCheckActions::datagramChannelSend)),
entry("datagram_channel_receive", forPlugins(NetworkAccessCheckActions::datagramChannelReceive))
)
.filter(entry -> entry.getValue().fromJavaVersion() == null || Runtime.version().feature() >= entry.getValue().fromJavaVersion())
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ private static PolicyManager createPolicyManager() throws IOException {
new NetworkEntitlement(LISTEN_ACTION | CONNECT_ACTION | ACCEPT_ACTION)
)
),
new Scope("org.apache.httpcomponents.httpclient", List.of(new NetworkEntitlement(CONNECT_ACTION)))
new Scope("org.apache.httpcomponents.httpclient", List.of(new NetworkEntitlement(CONNECT_ACTION))),
new Scope("io.netty.transport", List.of(new NetworkEntitlement(LISTEN_ACTION)))
)
);
// agents run without a module, so this is a special hack for the apm agent
Expand Down
Loading