Skip to content

.rpc_id( should not require "call_local" to run locally. #98588

Open
@greenfox1505

Description

@greenfox1505

Tested versions

Reproduceable in all Godot 4.x version, including current master branch

System information

All

Issue description

Godot should trust the developer when calling rpc_id( on a locally owned node and continue the call locally even when "call_local" isn't part of it's @rpc(. There is clear security problems with trusting external calls. These security issues do not exist in local->local calls and adds confusion. If a developer is calling a function to a particular to a particular id, it should trust that they intended that. By blocking programmer intention to call on a target that just happens to be local owner, it adds confusion.

This diff should fix this and similify some of the SceneRPCInterface::rpcp, I can commit pull request this if this behavior change is agreeable.

diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp
index 0938d7ef99..dc38586a37 100644
--- a/modules/multiplayer/scene_rpc_interface.cpp
+++ b/modules/multiplayer/scene_rpc_interface.cpp
@@ -477,57 +477,53 @@ Error SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_
 	ERR_FAIL_COND_V_MSG(peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_CONNECTION_ERROR, "Trying to call an RPC via a multiplayer peer which is not connected.");
 
 	int caller_id = multiplayer->get_unique_id();
-	bool call_local_native = false;
-	bool call_local_script = false;
 	const RPCConfigCache &config_cache = _get_node_config(node);
 	uint16_t rpc_id = config_cache.ids.has(p_method) ? config_cache.ids[p_method] : UINT16_MAX;
 	ERR_FAIL_COND_V_MSG(rpc_id == UINT16_MAX, ERR_INVALID_PARAMETER,
 			vformat("Unable to get the RPC configuration for the function \"%s\" at path: \"%s\". This happens when the method is missing or not marked for RPCs in the local script.", p_method, node->get_path()));
 	const RPCConfig &config = config_cache.configs[rpc_id];
 
-	ERR_FAIL_COND_V_MSG(p_peer_id == caller_id && !config.call_local, ERR_INVALID_PARAMETER, "RPC '" + p_method + "' on yourself is not allowed by selected mode.");
-
-	if (p_peer_id == 0 || p_peer_id == caller_id || (p_peer_id < 0 && p_peer_id != -caller_id)) {
-		if (rpc_id & (1 << 15)) {
-			call_local_native = config.call_local;
-		} else {
-			call_local_script = config.call_local;
-		}
-	}
+	// ERR_FAIL_COND_V_MSG(p_peer_id == caller_id && !config.call_local, ERR_INVALID_PARAMETER, "RPC '" + p_method + "' on yourself is not allowed by selected mode.");
 
+	WARN_PRINT("hit this line");
 	if (p_peer_id != caller_id) {
 		_send_rpc(node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount);
 	}
 
-	if (call_local_native) {
-		Callable::CallError ce;
-
-		multiplayer->set_remote_sender_override(multiplayer->get_unique_id());
-		node->callp(p_method, p_arg, p_argcount, ce);
-		multiplayer->set_remote_sender_override(0);
-
-		if (ce.error != Callable::CallError::CALL_OK) {
-			String error = Variant::get_call_error_text(node, p_method, p_arg, p_argcount, ce);
-			error = "rpc() aborted in local call:  - " + error + ".";
-			ERR_PRINT(error);
-			return FAILED;
-		}
-	}
-
-	if (call_local_script) {
-		Callable::CallError ce;
-		ce.error = Callable::CallError::CALL_OK;
-
-		multiplayer->set_remote_sender_override(multiplayer->get_unique_id());
-		node->get_script_instance()->callp(p_method, p_arg, p_argcount, ce);
-		multiplayer->set_remote_sender_override(0);
-
-		if (ce.error != Callable::CallError::CALL_OK) {
-			String error = Variant::get_call_error_text(node, p_method, p_arg, p_argcount, ce);
-			error = "rpc() aborted in script local call:  - " + error + ".";
-			ERR_PRINT(error);
-			return FAILED;
+	// call local:
+	// - if directly called on this peer
+	// - if broadcast to all peers
+	// - if broadcast to all peers except not this peer
+	if (p_peer_id == caller_id || ((p_peer_id == 0 && config.call_local) || (p_peer_id < 0 && p_peer_id != -caller_id))) {
+		if (rpc_id & (1 << 15)) { //call_local_native
+			Callable::CallError ce;
+
+			multiplayer->set_remote_sender_override(multiplayer->get_unique_id());
+			node->callp(p_method, p_arg, p_argcount, ce);
+			multiplayer->set_remote_sender_override(0);
+
+			if (ce.error != Callable::CallError::CALL_OK) {
+				String error = Variant::get_call_error_text(node, p_method, p_arg, p_argcount, ce);
+				error = "rpc() aborted in local call:  - " + error + ".";
+				ERR_PRINT(error);
+				return FAILED;
+			}
+		} else { //call_local_script
+			Callable::CallError ce;
+			ce.error = Callable::CallError::CALL_OK;
+
+			multiplayer->set_remote_sender_override(multiplayer->get_unique_id());
+			node->get_script_instance()->callp(p_method, p_arg, p_argcount, ce);
+			multiplayer->set_remote_sender_override(0);
+
+			if (ce.error != Callable::CallError::CALL_OK) {
+				String error = Variant::get_call_error_text(node, p_method, p_arg, p_argcount, ce);
+				error = "rpc() aborted in script local call:  - " + error + ".";
+				ERR_PRINT(error);
+				return FAILED;
+			}
 		}
 	}
+	
 	return OK;
 }

Steps to reproduce

Calling any rpc_id with the id being the local player causes the rpc to block.

extends Node

func _ready() -> void:
	call_remote.rpc_id(multiplayer.get_unique_id(),"This is being blocked, but shouldn't be.")

@rpc("any_peer","call_remote")
func call_remote(text):
	print(text)

produces the following error:

ERROR: RPC 'echo' on yourself is not allowed by selected mode.
   at: (modules\multiplayer\scene_rpc_interface.cpp:488)

Minimal reproduction project (MRP)

extends Node


func _ready() -> void:
	call_local.rpc("This should always run locally")
	call_remote.rpc("This should never run locally")

	call_local.rpc_id(multiplayer.get_unique_id(),"This should run regardless")
	call_remote.rpc_id(multiplayer.get_unique_id(),"This is being blocked, but shouldn't be.")

@rpc("any_peer","call_local")
func call_local(text):
	print(text)
	
@rpc("any_peer","call_remote")
func call_remote(text):
	print(text)

In any project should be enough to repro this issue.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions