diff --git a/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java b/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java index aaa43900badb9..97c70837c2187 100644 --- a/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java +++ b/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java @@ -180,7 +180,7 @@ private static Stream maybeAttachEntitlementAgent(boolean useEntitlement } // We instrument classes in these modules to call the bridge. Because the bridge gets patched // into java.base, we must export the bridge from java.base to these modules, as a comma-separated list - String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming"; + String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net"; return Stream.of( "-Des.entitlements.enabled=true", "-XX:+EnableDynamicAgentLoading", diff --git a/libs/entitlement/bridge/src/main/java/module-info.java b/libs/entitlement/bridge/src/main/java/module-info.java index b9055ec5fbf67..518a0a1ef29ec 100644 --- a/libs/entitlement/bridge/src/main/java/module-info.java +++ b/libs/entitlement/bridge/src/main/java/module-info.java @@ -11,6 +11,7 @@ // At build and run time, the bridge is patched into the java.base module. module org.elasticsearch.entitlement.bridge { requires java.net.http; + requires jdk.net; exports org.elasticsearch.entitlement.bridge; } diff --git a/libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java b/libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java index c90fc39bf29b3..f0bbcd9b7d09e 100644 --- a/libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java +++ b/libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java @@ -9,6 +9,8 @@ package org.elasticsearch.entitlement.bridge; +import jdk.nio.Channels; + import java.io.File; import java.io.FileDescriptor; import java.io.FileFilter; @@ -676,6 +678,37 @@ public interface EntitlementChecker { void check$java_util_zip_ZipFile$(Class callerClass, File file, int mode, Charset charset); // nio + // channels + void check$java_nio_channels_FileChannel$(Class callerClass); + + void check$java_nio_channels_FileChannel$$open( + Class callerClass, + Path path, + Set options, + FileAttribute... attrs + ); + + void check$java_nio_channels_FileChannel$$open(Class callerClass, Path path, OpenOption... options); + + void check$java_nio_channels_AsynchronousFileChannel$(Class callerClass); + + void check$java_nio_channels_AsynchronousFileChannel$$open( + Class callerClass, + Path path, + Set options, + ExecutorService executor, + FileAttribute... attrs + ); + + void check$java_nio_channels_AsynchronousFileChannel$$open(Class callerClass, Path path, OpenOption... options); + + void check$jdk_nio_Channels$$readWriteSelectableChannel( + Class callerClass, + FileDescriptor fd, + Channels.SelectableChannelCloser closer + ); + + // files void check$java_nio_file_Files$$getOwner(Class callerClass, Path path, LinkOption... options); void check$java_nio_file_Files$$probeContentType(Class callerClass, Path path); diff --git a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/module-info.java b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/module-info.java index bb4c6fd759426..ee2ae33d34890 100644 --- a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/module-info.java +++ b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/module-info.java @@ -16,4 +16,5 @@ // Modules we'll attempt to use in order to exercise entitlements requires java.logging; requires java.net.http; + requires jdk.net; } diff --git a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/DummyImplementations.java b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/DummyImplementations.java index ca03014634076..929ec4ce731d3 100644 --- a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/DummyImplementations.java +++ b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/DummyImplementations.java @@ -9,6 +9,10 @@ package org.elasticsearch.entitlement.qa.test; +import jdk.nio.Channels; + +import org.elasticsearch.core.SuppressForbidden; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -24,14 +28,23 @@ import java.net.SocketException; import java.net.SocketImpl; import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; import java.nio.channels.AsynchronousChannelGroup; +import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.CompletionHandler; import java.nio.channels.DatagramChannel; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; import java.nio.channels.Pipe; +import java.nio.channels.ReadableByteChannel; import java.nio.channels.SeekableByteChannel; +import java.nio.channels.SelectableChannel; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; +import java.nio.channels.WritableByteChannel; import java.nio.channels.spi.AbstractSelector; import java.nio.channels.spi.AsynchronousChannelProvider; import java.nio.channels.spi.SelectorProvider; @@ -67,6 +80,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.spi.CalendarDataProvider; import java.util.spi.CalendarNameProvider; @@ -676,4 +690,162 @@ public void setAttribute(Path path, String attribute, Object value, LinkOption.. } } + + static class DummyFileChannel extends FileChannel { + @Override + protected void implCloseChannel() throws IOException { + + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return 0; + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { + return 0; + } + + @Override + public int write(ByteBuffer src) throws IOException { + return 0; + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { + return 0; + } + + @Override + public long position() throws IOException { + return 0; + } + + @Override + public FileChannel position(long newPosition) throws IOException { + return null; + } + + @Override + public long size() throws IOException { + return 0; + } + + @Override + public FileChannel truncate(long size) throws IOException { + return null; + } + + @Override + public void force(boolean metaData) throws IOException { + + } + + @Override + public long transferTo(long position, long count, WritableByteChannel target) throws IOException { + return 0; + } + + @Override + public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { + return 0; + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + return 0; + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + return 0; + } + + @Override + public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { + return null; + } + + @Override + public FileLock lock(long position, long size, boolean shared) throws IOException { + return null; + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + return null; + } + } + + static class DummyAsynchronousFileChannel extends AsynchronousFileChannel { + @Override + public boolean isOpen() { + return false; + } + + @Override + public void close() throws IOException { + + } + + @Override + public long size() throws IOException { + return 0; + } + + @Override + public AsynchronousFileChannel truncate(long size) throws IOException { + return null; + } + + @Override + public void force(boolean metaData) throws IOException { + + } + + @Override + public void lock(long position, long size, boolean shared, A attachment, CompletionHandler handler) { + + } + + @Override + public Future lock(long position, long size, boolean shared) { + return null; + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + return null; + } + + @Override + public void read(ByteBuffer dst, long position, A attachment, CompletionHandler handler) { + + } + + @Override + public Future read(ByteBuffer dst, long position) { + return null; + } + + @Override + public void write(ByteBuffer src, long position, A attachment, CompletionHandler handler) { + + } + + @Override + public Future write(ByteBuffer src, long position) { + return null; + } + } + + @SuppressForbidden(reason = "specifically testing readWriteSelectableChannel") + static class DummySelectableChannelCloser implements Channels.SelectableChannelCloser { + @Override + public void implCloseChannel(SelectableChannel sc) throws IOException {} + + @Override + public void implReleaseChannel(SelectableChannel sc) throws IOException {} + } } diff --git a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/NioChannelsActions.java b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/NioChannelsActions.java new file mode 100644 index 0000000000000..777f0fbf67a9f --- /dev/null +++ b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/NioChannelsActions.java @@ -0,0 +1,87 @@ +/* + * 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 org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.entitlement.qa.entitled.EntitledActions; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.FileChannel; +import java.nio.file.StandardOpenOption; +import java.util.Set; + +import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.ALWAYS_DENIED; +import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.PLUGINS; + +class NioChannelsActions { + + @EntitlementTest(expectedAccess = ALWAYS_DENIED) + static void createFileChannel() throws IOException { + new DummyImplementations.DummyFileChannel().close(); + } + + @EntitlementTest(expectedAccess = PLUGINS) + static void fileChannelOpenForWrite() throws IOException { + FileChannel.open(FileCheckActions.readWriteFile(), StandardOpenOption.WRITE).close(); + } + + @EntitlementTest(expectedAccess = PLUGINS) + static void fileChannelOpenForRead() throws IOException { + FileChannel.open(FileCheckActions.readFile()).close(); + } + + @EntitlementTest(expectedAccess = PLUGINS) + static void fileChannelOpenForWriteWithOptions() throws IOException { + FileChannel.open(FileCheckActions.readWriteFile(), Set.of(StandardOpenOption.WRITE)).close(); + } + + @EntitlementTest(expectedAccess = PLUGINS) + static void fileChannelOpenForReadWithOptions() throws IOException { + FileChannel.open(FileCheckActions.readFile(), Set.of(StandardOpenOption.READ)).close(); + } + + @EntitlementTest(expectedAccess = ALWAYS_DENIED) + static void createAsynchronousFileChannel() throws IOException { + new DummyImplementations.DummyAsynchronousFileChannel().close(); + } + + @EntitlementTest(expectedAccess = PLUGINS) + static void asynchronousFileChannelOpenForWrite() throws IOException { + var file = EntitledActions.createTempFileForWrite(); + AsynchronousFileChannel.open(file, StandardOpenOption.WRITE).close(); + } + + @EntitlementTest(expectedAccess = PLUGINS) + static void asynchronousFileChannelOpenForRead() throws IOException { + var file = EntitledActions.createTempFileForRead(); + AsynchronousFileChannel.open(file).close(); + } + + @EntitlementTest(expectedAccess = PLUGINS) + static void asynchronousFileChannelOpenForWriteWithOptions() throws IOException { + var file = EntitledActions.createTempFileForWrite(); + AsynchronousFileChannel.open(file, Set.of(StandardOpenOption.WRITE), EsExecutors.DIRECT_EXECUTOR_SERVICE).close(); + } + + @EntitlementTest(expectedAccess = PLUGINS) + static void asynchronousFileChannelOpenForReadWithOptions() throws IOException { + var file = EntitledActions.createTempFileForRead(); + AsynchronousFileChannel.open(file, Set.of(StandardOpenOption.READ), EsExecutors.DIRECT_EXECUTOR_SERVICE).close(); + } + + @SuppressForbidden(reason = "specifically testing jdk.nio.Channels") + @EntitlementTest(expectedAccess = ALWAYS_DENIED) + static void channelsReadWriteSelectableChannel() throws IOException { + jdk.nio.Channels.readWriteSelectableChannel(new FileDescriptor(), new DummyImplementations.DummySelectableChannelCloser()).close(); + } +} diff --git a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java index d7f2fe92348d7..ef6688a9a8203 100644 --- a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java +++ b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java @@ -189,6 +189,7 @@ static CheckAction alwaysDenied(CheckedRunnable action) { getTestEntries(FileStoreActions.class), getTestEntries(ManageThreadsActions.class), getTestEntries(NativeActions.class), + getTestEntries(NioChannelsActions.class), getTestEntries(NioFilesActions.class), getTestEntries(NioFileSystemActions.class), getTestEntries(PathActions.class), diff --git a/libs/entitlement/src/main/java/module-info.java b/libs/entitlement/src/main/java/module-info.java index 5c8441bcecb9c..697d26747b806 100644 --- a/libs/entitlement/src/main/java/module-info.java +++ b/libs/entitlement/src/main/java/module-info.java @@ -14,6 +14,7 @@ requires org.elasticsearch.base; requires jdk.attach; requires java.net.http; + requires jdk.net; requires static org.elasticsearch.entitlement.bridge; // At runtime, this will be in java.base diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java index 86f7bd39f6c89..f6a45cce3c56c 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java @@ -9,6 +9,8 @@ package org.elasticsearch.entitlement.runtime.api; +import jdk.nio.Channels; + import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.bridge.EntitlementChecker; import org.elasticsearch.entitlement.runtime.policy.PolicyManager; @@ -1350,6 +1352,72 @@ public void checkSelectorProviderInheritedChannel(Class callerClass, Selector // nio + @Override + public void check$java_nio_channels_FileChannel$(Class callerClass) { + policyManager.checkChangeFilesHandling(callerClass); + } + + @Override + public void check$java_nio_channels_FileChannel$$open( + Class callerClass, + Path path, + Set options, + FileAttribute... attrs + ) { + if (isOpenForWrite(options)) { + policyManager.checkFileWrite(callerClass, path); + } else { + policyManager.checkFileRead(callerClass, path); + } + } + + @Override + public void check$java_nio_channels_FileChannel$$open(Class callerClass, Path path, OpenOption... options) { + if (isOpenForWrite(options)) { + policyManager.checkFileWrite(callerClass, path); + } else { + policyManager.checkFileRead(callerClass, path); + } + } + + @Override + public void check$java_nio_channels_AsynchronousFileChannel$(Class callerClass) { + policyManager.checkChangeFilesHandling(callerClass); + } + + @Override + public void check$java_nio_channels_AsynchronousFileChannel$$open( + Class callerClass, + Path path, + Set options, + ExecutorService executor, + FileAttribute... attrs + ) { + if (isOpenForWrite(options)) { + policyManager.checkFileWrite(callerClass, path); + } else { + policyManager.checkFileRead(callerClass, path); + } + } + + @Override + public void check$java_nio_channels_AsynchronousFileChannel$$open(Class callerClass, Path path, OpenOption... options) { + if (isOpenForWrite(options)) { + policyManager.checkFileWrite(callerClass, path); + } else { + policyManager.checkFileRead(callerClass, path); + } + } + + @Override + public void check$jdk_nio_Channels$$readWriteSelectableChannel( + Class callerClass, + FileDescriptor fd, + Channels.SelectableChannelCloser closer + ) { + policyManager.checkFileDescriptorWrite(callerClass); + } + @Override public void check$java_nio_file_Files$$getOwner(Class callerClass, Path path, LinkOption... options) { policyManager.checkFileRead(callerClass, path); diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java index fdc577c848854..66e44576b7452 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java @@ -259,6 +259,13 @@ public void checkChangeNetworkHandling(Class callerClass) { checkChangeJVMGlobalState(callerClass); } + /** + * Check for operations that can modify the way file operations are handled + */ + public void checkChangeFilesHandling(Class callerClass) { + checkChangeJVMGlobalState(callerClass); + } + @SuppressForbidden(reason = "Explicitly checking File apis") public void checkFileRead(Class callerClass, File file) { checkFileRead(callerClass, file.toPath());