sql: fix crash for large channelmoves tables.
What changed, and why it matters
This commit fixes a crash in Core Lightning's SQL plugin. The bug was a too-strict internal check (an assertion) that could fail when the plugin had a large number of channel moves to refresh. The fix changes how the plugin tracks which refresh work is still needed, so it no longer trips over itself when new work arrives while a refresh is already running. It is a stability fix for a plugin, not a remote exploit.
Treat as a stability/DoS-hardening fix. Backport to the v26.04 release candidate branch if the SQL plugin is enabled by default or widely used. No urgent security response is required unless the SQL plugin is exposed to untrusted data sources.
Security signals we found
Denial-of-service vector: plugin abort via failed assertion
Affected component is the sql plugin, an optional/experimental plugin
Crash requires large channelmoves table and pagination path
No evidence of remote code execution or privilege escalation
Fix removes overzealous assertion and refines refresh-bit clearing
Evidence from the diff
In plugins/sql.c, refresh_by_created_index asserted td->refresh_needs != REFRESH_UNNECESSARY before setting it to REFRESH_UNNECESSARY. Under pagination (dev-sqllistlimit), the wait callback could set the refresh bit again between pages, causing the assertion to fire and abort the plugin. The patch removes the assert and clears only the relevant bit (REFRESH_CREATED or REFRESH_UPDATED) before issuing the JSON-RPC request, allowing the wait callback to re-arm refresh needs without tripping an abort. A test that was marked as expected-to-fail is now enabled.
Changed components
plugins/sql.crefresh_by_created_index()updated_list_done()paginated_refresh()tests/test_plugin.py::test_sql_limit_per_listInspect captured patch +4 / −5
diff --git a/plugins/sql.c b/plugins/sql.c
index a6b9b547..f065fc58 100644
--- a/plugins/sql.c
+++ b/plugins/sql.c
@@ -1746,9 +1746,8 @@ static struct command_result *refresh_by_created_index(struct command *cmd,
struct sql *sql = sql_of(dbq->cmd->plugin);
struct out_req *req;
- /* Since we're relying on watches, mark refreshing unnecessary to start */
- assert(td->refresh_needs != REFRESH_UNNECESSARY);
- td->refresh_needs = REFRESH_UNNECESSARY;
+ /* We no longer need refresh_created, but wait could update this meanwhile. */
+ td->refresh_needs &= ~REFRESH_CREATED;
req = jsonrpc_request_start(cmd, td->cmdname,
limited_list_done, forward_error,
@@ -1782,7 +1781,6 @@ static struct command_result *updated_list_done(struct command *cmd,
return refresh_by_created_index(cmd, td, dbq);
}
- td->refresh_needs = REFRESH_UNNECESSARY;
return one_refresh_done(cmd, dbq, false);
}
@@ -1794,6 +1792,7 @@ static struct command_result *paginated_refresh(struct command *cmd,
* entire thing */
if (td->refresh_needs & REFRESH_DELETED) {
plugin_log(cmd->plugin, LOG_DBG, "%s: total reload due to delete", td->name);
+ /* Since this reloads everything, covers all: updates and creates */
td->refresh_needs = REFRESH_UNNECESSARY;
return default_refresh(cmd, td, dbq);
}
@@ -1802,6 +1801,7 @@ static struct command_result *paginated_refresh(struct command *cmd,
struct out_req *req;
plugin_log(cmd->plugin, LOG_DBG,
"%s: records updated, updating from %"PRIu64, td->name, td->last_updated_index + 1);
+ td->refresh_needs &= ~REFRESH_UPDATED;
req = jsonrpc_request_start(cmd, td->cmdname,
updated_list_done, forward_error,
dbq);
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index a2f197bc..3f4f11cb 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -4410,7 +4410,6 @@ def test_sql_deprecated(node_factory, bitcoind):
assert ret == {'rows': [[1]]}
-@pytest.mark.xfail(strict=True)
def test_sql_limit_per_list(node_factory):
l1, l2, l3 = node_factory.line_graph(
3, wait_for_announce=True, opts=[{}, {"dev-sqllistlimit": 10}, {}]
Why this scored 50/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.