Description
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.