This is an automated email from the ASF dual-hosted git repository.
peter-toth pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new e16ae3a22611 [SPARK-57889][CORE] Authorize StreamRequest consistently
with ChunkFetchRequest in the transport layer
e16ae3a22611 is described below
commit e16ae3a22611dfe4e7fd4a6568b1d31570ba4405
Author: Peter Toth <[email protected]>
AuthorDate: Fri Jul 3 10:24:25 2026 +0200
[SPARK-57889][CORE] Authorize StreamRequest consistently with
ChunkFetchRequest in the transport layer
### What changes were proposed in this pull request?
`TransportRequestHandler.processStreamRequest` now calls
`streamManager.checkAuthorization` before `openStream`, mirroring
`ChunkFetchRequestHandler`. A `checkAuthorization(TransportClient, String)`
overload is added to `StreamManager` (no-op default) and overridden in
`OneForOneStreamManager` to parse the `"streamId_chunkIndex"` id and delegate
to the existing `checkAuthorization(TransportClient, long)`.
### Why are the changes needed?
The transport layer serves both `ChunkFetchRequest` and `StreamRequest`
from the same `StreamManager`. `ChunkFetchRequestHandler` calls
`streamManager.checkAuthorization` to enforce per-application ownership
(`clientId == appId`), but `processStreamRequest` called `openStream` directly
with no such check. Because `OneForOneStreamManager.openStream` addresses the
same registered streams by their string id, a `StreamRequest` could read a
stream that the `ChunkFetchRequest` path would ha [...]
### Does this PR introduce _any_ user-facing change?
No. This is internal transport-layer authorization; like the existing
`ChunkFetchRequest` check it enforces only for authenticated clients (non-null
client id), and behavior is unchanged when authentication is disabled.
### How was this patch tested?
New `OneForOneStreamManagerSuite.testStreamRequestAuthorization` (owner
allowed, other-application rejected with `SecurityException`, null client id
no-op). Existing `OneForOneStreamManagerSuite`, `TransportRequestHandlerSuite`,
and `ChunkFetchRequestHandlerSuite` pass.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8)
Closes #56970 from peter-toth/SPARK-57889-streamrequest-authz.
Authored-by: Peter Toth <[email protected]>
Signed-off-by: Peter Toth <[email protected]>
(cherry picked from commit fd05a3bd52e78e079df6763fffbf74f901756cc4)
Signed-off-by: Peter Toth <[email protected]>
---
.../network/server/OneForOneStreamManager.java | 8 ++++++
.../apache/spark/network/server/StreamManager.java | 10 ++++++++
.../network/server/TransportRequestHandler.java | 1 +
.../server/OneForOneStreamManagerSuite.java | 29 ++++++++++++++++++++++
4 files changed, 48 insertions(+)
diff --git
a/common/network-common/src/main/java/org/apache/spark/network/server/OneForOneStreamManager.java
b/common/network-common/src/main/java/org/apache/spark/network/server/OneForOneStreamManager.java
index cb53d565e7e8..e13bc1f143cf 100644
---
a/common/network-common/src/main/java/org/apache/spark/network/server/OneForOneStreamManager.java
+++
b/common/network-common/src/main/java/org/apache/spark/network/server/OneForOneStreamManager.java
@@ -178,6 +178,14 @@ public class OneForOneStreamManager extends StreamManager {
}
}
+ @Override
+ public void checkAuthorization(TransportClient client, String streamChunkId)
{
+ // StreamRequest addresses the same registered streams as
ChunkFetchRequest, via a
+ // "streamId_chunkIndex" string. Authorize on the underlying stream id so
that this path
+ // enforces the same per-application ownership check as the chunk-fetch
path.
+ checkAuthorization(client, parseStreamChunkId(streamChunkId).getLeft());
+ }
+
@Override
public void chunkBeingSent(long streamId) {
StreamState streamState = streams.get(streamId);
diff --git
a/common/network-common/src/main/java/org/apache/spark/network/server/StreamManager.java
b/common/network-common/src/main/java/org/apache/spark/network/server/StreamManager.java
index e48d27be1126..63230e15a41d 100644
---
a/common/network-common/src/main/java/org/apache/spark/network/server/StreamManager.java
+++
b/common/network-common/src/main/java/org/apache/spark/network/server/StreamManager.java
@@ -73,6 +73,16 @@ public abstract class StreamManager {
*/
public void checkAuthorization(TransportClient client, long streamId) { }
+ /**
+ * Verify that the client is authorized to read from the given stream. This
variant covers the
+ * {@link #openStream(String)} path (i.e. {@code StreamRequest}). The
default implementation is a
+ * no-op; subclasses that key streams on a string id should override it,
typically by delegating
+ * to {@link #checkAuthorization(TransportClient, long)}.
+ *
+ * @throws SecurityException If client is not authorized.
+ */
+ public void checkAuthorization(TransportClient client, String streamId) { }
+
/**
* Return the number of chunks being transferred and not finished yet in
this StreamManager.
*/
diff --git
a/common/network-common/src/main/java/org/apache/spark/network/server/TransportRequestHandler.java
b/common/network-common/src/main/java/org/apache/spark/network/server/TransportRequestHandler.java
index 464d4d9eb378..803b26c0678b 100644
---
a/common/network-common/src/main/java/org/apache/spark/network/server/TransportRequestHandler.java
+++
b/common/network-common/src/main/java/org/apache/spark/network/server/TransportRequestHandler.java
@@ -140,6 +140,7 @@ public class TransportRequestHandler extends
MessageHandler<RequestMessage> {
}
ManagedBuffer buf;
try {
+ streamManager.checkAuthorization(reverseClient, req.streamId);
buf = streamManager.openStream(req.streamId);
} catch (Exception e) {
logger.error("Error opening stream {} for request from {}", e,
diff --git
a/common/network-common/src/test/java/org/apache/spark/network/server/OneForOneStreamManagerSuite.java
b/common/network-common/src/test/java/org/apache/spark/network/server/OneForOneStreamManagerSuite.java
index 136154cc2aa6..31401f104fa5 100644
---
a/common/network-common/src/test/java/org/apache/spark/network/server/OneForOneStreamManagerSuite.java
+++
b/common/network-common/src/test/java/org/apache/spark/network/server/OneForOneStreamManagerSuite.java
@@ -29,6 +29,7 @@ import org.mockito.Mockito;
import org.apache.spark.network.TestManagedBuffer;
import org.apache.spark.network.buffer.ManagedBuffer;
+import org.apache.spark.network.client.TransportClient;
public class OneForOneStreamManagerSuite {
@@ -162,4 +163,32 @@ public class OneForOneStreamManagerSuite {
Mockito.verify(mockManagedBuffer, Mockito.times(1)).release();
Assertions.assertEquals(0, manager.numStreamStates());
}
+
+ @Test
+ public void testStreamRequestAuthorization() {
+ OneForOneStreamManager manager = new OneForOneStreamManager();
+ Channel dummyChannel = Mockito.mock(Channel.class,
Mockito.RETURNS_SMART_NULLS);
+ long streamId = manager.registerStream(
+ "app1", new ArrayList<ManagedBuffer>().iterator(), dummyChannel);
+ // StreamRequest addresses a registered stream via a "streamId_chunkIndex"
string.
+ String streamChunkId = OneForOneStreamManager.genStreamChunkId(streamId,
0);
+
+ // A client authenticated as a different application is rejected, matching
the
+ // ChunkFetchRequest path.
+ TransportClient otherApp = Mockito.mock(TransportClient.class);
+ Mockito.when(otherApp.getClientId()).thenReturn("app2");
+ Assertions.assertThrows(SecurityException.class,
+ () -> manager.checkAuthorization(otherApp, streamChunkId));
+
+ // The owning application is allowed.
+ TransportClient owner = Mockito.mock(TransportClient.class);
+ Mockito.when(owner.getClientId()).thenReturn("app1");
+ manager.checkAuthorization(owner, streamChunkId);
+
+ // With authentication disabled the client id is null and the check is a
no-op, preserving
+ // the default behavior.
+ TransportClient noAuth = Mockito.mock(TransportClient.class);
+ Mockito.when(noAuth.getClientId()).thenReturn(null);
+ manager.checkAuthorization(noAuth, streamChunkId);
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]