From fec042db60b39cedb7f6541432ddaaacf6d37cdf Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Thu, 13 Aug 2026 11:42:41 +0100 Subject: [PATCH 1/2] lightningd: check rpc name collisions with builtin commands We crashed if the name collision happened to be a builtin command. ``` lightningd: FATAL SIGNAL 11 (version v26.06-241-gb35b848-modded) 0x5573cd532b2c send_backtrace common/daemon.c:38 0x5573cd532bb6 crashdump common/daemon.c:83 0x7fe1fd4ccdef ??? ./signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0 0x5573cd4d51a4 plugin_rpcmethod_add lightningd/plugin.c:1396 0x5573cd4d5268 plugin_rpcmethods_add lightningd/plugin.c:1423 0x5573cd4d57ef plugin_parse_getmanifest_response lightningd/plugin.c:1805 0x5573cd4d68db plugin_manifest_cb lightningd/plugin.c:1827 0x5573cd4d2316 plugin_response_handle lightningd/plugin.c:692 0x5573cd4d7443 plugin_read_json lightningd/plugin.c:781 0x5573cd56fd01 next_plan ccan/ccan/io/io.c:60 0x5573cd57018c do_plan ccan/ccan/io/io.c:422 0x5573cd570245 io_ready ccan/ccan/io/io.c:439 0x5573cd571be3 io_loop ccan/ccan/io/poll.c:471 0x5573cd4a5a99 io_loop_with_timers lightningd/io_loop_with_timers.c:22 0x5573cd4d61d1 plugins_init lightningd/plugin.c:2063 0x5573cd4aad6c main lightningd/lightningd.c:1269 0x7fe1fd4b6ca7 __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 0x7fe1fd4b6d64 __libc_start_main_impl ../csu/libc-start.c:360 0x5573cd47a120 ??? _start+0x20:0 0xffffffffffffffff ??? ???:0 ``` Changelog-Fixed: lightningd: checks for rpc name collisions with builtin commands when registering plugin RPC methods Reported-by: Vincenzo Palazzo (Bitcoin Security Council finding 2026-08-11) Signed-off-by: Lagrang3 --- lightningd/plugin.c | 17 ++++++++++++----- tests/plugins/builtin_collision.py | 12 ++++++++++++ tests/plugins/method_collision.py | 12 ++++++++++++ tests/test_plugin.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 5 deletions(-) create mode 100755 tests/plugins/builtin_collision.py create mode 100755 tests/plugins/method_collision.py diff --git a/lightningd/plugin.c b/lightningd/plugin.c index 036903e91dae..deb29c4ce02a 100644 --- a/lightningd/plugin.c +++ b/lightningd/plugin.c @@ -1393,11 +1393,18 @@ static const char *plugin_rpcmethod_add(struct plugin *plugin, if (!jsonrpc_command_add(plugin->plugins->ld->jsonrpc, cmd, usage)) { struct plugin *p = find_plugin_for_command(plugin->plugins->ld, cmd->name); - return tal_fmt( - plugin, - "Could not register method \"%s\", a method with " - "that name is already registered by plugin %s", - cmd->name, p->cmd); + if (p) + return tal_fmt( + plugin, + "Could not register method \"%s\", a method with " + "that name is already registered by plugin %s", + cmd->name, p->cmd); + else + return tal_fmt(plugin, + "Could not register method \"%s\", a " + "builtin method with " + "that name is already registered", + cmd->name); } tal_arr_expand(&plugin->methods, cmd->name); return NULL; diff --git a/tests/plugins/builtin_collision.py b/tests/plugins/builtin_collision.py new file mode 100755 index 000000000000..f367f8000dd3 --- /dev/null +++ b/tests/plugins/builtin_collision.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +from pyln.client import Plugin + +plugin = Plugin() + + +@plugin.method("getinfo") +def getinfo(plugin, **kwargs): + return {} + + +plugin.run() diff --git a/tests/plugins/method_collision.py b/tests/plugins/method_collision.py new file mode 100755 index 000000000000..6333bd8a8886 --- /dev/null +++ b/tests/plugins/method_collision.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +from pyln.client import Plugin + +plugin = Plugin() + + +@plugin.method("myrpcmethod") +def myrpcmethod(plugin, **kwargs): + return {} + + +plugin.run() diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 31c253c134d4..3e934e9cce87 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -6242,3 +6242,32 @@ def test_bwatch_blockdepth_watch_no_fire_before_start_block(node_factory, bitcoi # Clean up l1.rpc.delblockdepthwatch(owner=owner, start_block=future_start) + + +def test_command_collision(node_factory): + """We add a new method with the inline plugin. Then try to register the same + method with another dynamic plugin. lightningd should report back a name + collision.""" + + def some_plugin(plugin): + @plugin.method("myrpcmethod") + def on_mymethod(plugin): + return {} + + l1 = node_factory.get_node(inline_plugin=some_plugin) + + # try register plugin with "myrpcmethod" collision + with pytest.raises( + RpcError, match="a method with that name is already registered by plugin" + ): + l1.rpc.plugin_start( + plugin=os.path.join(os.getcwd(), "tests/plugins/method_collision.py") + ) + + # try register plugin with "getinfo" method which is builtin + with pytest.raises( + RpcError, match="a builtin method with that name is already registered" + ): + l1.rpc.plugin_start( + plugin=os.path.join(os.getcwd(), "tests/plugins/builtin_collision.py") + ) From 43adcce07bc41e858c1c8c0dce9a5045b5d359bc Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 31 Aug 2026 09:39:33 +0100 Subject: [PATCH 2/2] lightningd: sanitize "usage" for registered plugins We first check that the "usage" can be unescaped before handing it down to jsonrpc_command_add. The latter can only fail if there is a method collision. Changelog-None Signed-off-by: Lagrang3 --- lightningd/jsonrpc.c | 10 ++++------ lightningd/plugin.c | 7 ++++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lightningd/jsonrpc.c b/lightningd/jsonrpc.c index fb7bec1014df..c9f61c4fa182 100644 --- a/lightningd/jsonrpc.c +++ b/lightningd/jsonrpc.c @@ -1340,14 +1340,12 @@ bool jsonrpc_command_add(struct jsonrpc *rpc, struct json_command *command, struct cmd_and_usage *cmd; cmd = command_add(rpc, command); - if (!cmd) - return false; - - cmd->usage = json_escape_unescape_len(cmd, usage, strlen(usage)); - if (!cmd->usage) { - tal_free(cmd); + if (!cmd) { + tal_free_if_taken(usage); return false; } + + cmd->usage = tal_strdup(cmd, usage); tal_add_destructor2(command, destroy_json_command, rpc); return true; } diff --git a/lightningd/plugin.c b/lightningd/plugin.c index deb29c4ce02a..5d4b0532b45c 100644 --- a/lightningd/plugin.c +++ b/lightningd/plugin.c @@ -1383,6 +1383,11 @@ static const char *plugin_rpcmethod_add(struct plugin *plugin, return tal_fmt(plugin, "\"usage\" not provided by plugin"); + usage = json_escape_unescape_len(tmpctx, take(usage), strlen(usage)); + if (!usage) + return tal_fmt(plugin, + "\"usage\" contains invalid escape sequences"); + err = json_parse_deprecated(cmd, buffer, deprtok, &cmd->depr_start, &cmd->depr_end); if (err) return tal_steal(plugin, err); @@ -1390,7 +1395,7 @@ static const char *plugin_rpcmethod_add(struct plugin *plugin, cmd->dev_only = false; cmd->dispatch = plugin_rpcmethod_dispatch; cmd->check = plugin_rpcmethod_check; - if (!jsonrpc_command_add(plugin->plugins->ld->jsonrpc, cmd, usage)) { + if (!jsonrpc_command_add(plugin->plugins->ld->jsonrpc, cmd, take(usage))) { struct plugin *p = find_plugin_for_command(plugin->plugins->ld, cmd->name); if (p)