diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/RPC.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/RPC.java index 4209827172f705..317232192841bf 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/RPC.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/RPC.java @@ -1098,9 +1098,22 @@ Map getProtocolImplMap(RPC.RpcKind rpcKind) { new HashMap(10)); } } - return protocolImplMapArray.get(rpcKind.ordinal()); + return protocolImplMapArray.get(rpcKind.ordinal()); } - + + /** + * Returns {@code true} only if at least one protocol has been registered + * on this server instance for the given {@link RPC.RpcKind}. + * Used to reject incoming requests for unsupported RPC kinds before any + * deserialization of the request payload takes place. + * @param rpcKind the RPC kind from the incoming request header. + * @return {@code true} if at least one protocol is registered for this kind. + */ + boolean hasRegisteredProtocols(RPC.RpcKind rpcKind) { + Map implMap = getProtocolImplMap(rpcKind); + return implMap != null && !implMap.isEmpty(); + } + // Register protocol and its impl for rpc calls void registerProtocolAndImpl(RpcKind rpcKind, Class protocolClass, Object protocolImpl) { diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java index 48c8dec61a28dc..d0e3838511f221 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java @@ -2946,15 +2946,27 @@ private void processRpcRequest(RpcRequestHeaderProto header, throw new FatalRpcServerException( RpcErrorCodeProto.FATAL_INVALID_RPC_HEADER, err); } - Class rpcRequestClass = + // Reject requests for RPC kinds with no registered protocols on this + // server instance. This prevents deserialization of untrusted payloads + // for unsupported kinds. See HADOOP-19864. + if (Server.this instanceof RPC.Server server) { + final RPC.RpcKind kind = ProtoUtil.convert(header.getRpcKind()); + if (!server.hasRegisteredProtocols(kind)) { + final String err = "No protocols registered on this server for RpcKind " + + header.getRpcKind() + + ". Rejecting request without deserialization."; + LOG.info("{} Client: {}", err, getHostAddress()); + throw new FatalRpcServerException( + RpcErrorCodeProto.FATAL_INVALID_RPC_HEADER, err); + } + } + Class rpcRequestClass = getRpcRequestWrapper(header.getRpcKind()); if (rpcRequestClass == null) { - LOG.warn("Unknown rpc kind " + header.getRpcKind() + - " from client " + getHostAddress()); - final String err = "Unknown rpc kind in rpc header" + - header.getRpcKind(); + LOG.warn("Unknown rpc kind {} from client {}", header.getRpcKind(), getHostAddress()); throw new FatalRpcServerException( - RpcErrorCodeProto.FATAL_INVALID_RPC_HEADER, err); + RpcErrorCodeProto.FATAL_INVALID_RPC_HEADER, + "Unknown rpc kind in rpc header" + header.getRpcKind()); } Writable rpcRequest; try { //Read the rpc request @@ -2962,12 +2974,12 @@ private void processRpcRequest(RpcRequestHeaderProto header, } catch (RpcServerException rse) { // lets tests inject failures. throw rse; } catch (Throwable t) { // includes runtime exception from newInstance - LOG.warn("Unable to read call parameters for client " + - getHostAddress() + "on connection protocol " + - this.protocolName + " for rpcKind " + header.getRpcKind(), t); - String err = "IPC server unable to read call parameters: "+ t.getMessage(); + LOG.warn( + "Unable to read call parameters for client {}on connection protocol {} for rpcKind {}", + getHostAddress(), this.protocolName, header.getRpcKind(), t); throw new FatalRpcServerException( - RpcErrorCodeProto.FATAL_DESERIALIZING_REQUEST, err); + RpcErrorCodeProto.FATAL_DESERIALIZING_REQUEST, + "IPC server unable to read call parameters: "+ t.getMessage()); } Span span = null; diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestRPC.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestRPC.java index 6cb4697ddaa27b..e24833a161f35d 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestRPC.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestRPC.java @@ -2116,6 +2116,31 @@ public void testNumTotalRequestsMetrics() throws Exception { } + /** + * Test that a Protobuf-only RPC server rejects requests for RpcKinds + * that have no registered protocols, without deserializing the payload. + */ + @Test + @Timeout(value = 30) + public void testUnregisteredRpcKindRejectedWithoutDeserialization() + throws Exception { + // Standard test server: only RPC_PROTOCOL_BUFFER protocols are registered. + RPC.Server server = setupTestServer(conf, 1); + try { + // RPC_PROTOCOL_BUFFER has registered protocols — must be accepted. + assertThat(server.hasRegisteredProtocols(RPC.RpcKind.RPC_PROTOCOL_BUFFER)) + .as("RPC_PROTOCOL_BUFFER should have registered protocols") + .isTrue(); + + // RPC_BUILTIN has no protocols registered on this server — must be rejected. + assertThat(server.hasRegisteredProtocols(RPC.RpcKind.RPC_BUILTIN)) + .as("RPC_BUILTIN should have no registered protocols on a Protobuf-only server") + .isFalse(); + } finally { + server.stop(); + } + } + public static void main(String[] args) throws Exception { new TestRPC().testCallsInternal(conf); }