plugin: remove features when plugin is disabled
What changed, and why it matters
This commit fixes a bug in Core Lightning where a plugin could advertise certain protocol features when it first starts up, then later disable itself during initialization, but leave those feature bits still advertised by the node. The fix stores the feature bits a plugin claims and removes them if the plugin later disables itself. A leftover feature bit could mislead other nodes about what this node supports, potentially causing connection or protocol issues, though it is not a direct theft-of-funds bug.
Apply the patch. Operators running custom or optional plugins should ensure they are on a version containing this fix, especially if plugins may self-disable during init. No immediate emergency response is warranted because the issue is a consistency bug rather than a direct exploit.
Security signals we found
Feature-bit desynchronization between advertised capabilities and actual node capabilities
Disabled plugin leaving stale protocol features in node announcement / init messages
Potential protocol confusion or interoperability failure with peer nodes
No direct memory safety or cryptographic bug; logic-level consistency fix
Evidence from the diff
The patch adds a struct feature_set *fset field to struct plugin. In plugin_parse_getmanifest_response, when a plugin’s manifest includes feature bits, the code now duplicates the feature set into plugin->fset; otherwise it sets it to NULL. In plugin_config_cb, if the plugin’s init response returns disable, the code calls feature_set_sub to subtract the stored feature set from ld->our_features before killing the plugin. This prevents disabled plugins from leaving stale feature bits in the node’s advertised feature set.
Changed components
lightningd/plugin.clightningd/plugin.hPlugin manifest feature-bit handlingPlugin init disable handlingNode feature set (`our_features`)Inspect captured patch +12 / −0
diff --git a/lightningd/plugin.c b/lightningd/plugin.c
index 1dba9a94..5d031e73 100644
--- a/lightningd/plugin.c
+++ b/lightningd/plugin.c
@@ -1819,6 +1819,11 @@ static const char *plugin_parse_getmanifest_response(const char *buffer,
return tal_fmt(plugin,
"Custom featurebits already present");
}
+
+ /* Store fset to allow to remove feature bits when init returns disabled */
+ plugin->fset = tal_dup_or_null(plugin, struct feature_set, fset);
+ } else {
+ plugin->fset = NULL;
}
custommsgtok = json_get_member(buffer, resulttok, "custommessages");
@@ -2148,6 +2153,10 @@ static void plugin_config_cb(const char *buffer,
JSON_SCAN_TAL(tmpctx, json_strdup, &disable)) == NULL) {
/* Don't get upset if this was a built-in! */
plugin->important = false;
+ if (plugin->fset)
+ /* We don't have those features anymore! */
+ feature_set_sub(plugin->plugins->ld->our_features,
+ plugin->fset);
plugin_kill(plugin, LOG_DBG,
"disabled itself at init: %s",
disable);
diff --git a/lightningd/plugin.h b/lightningd/plugin.h
index 621f3b8d..3481d356 100644
--- a/lightningd/plugin.h
+++ b/lightningd/plugin.h
@@ -120,6 +120,9 @@ struct plugin {
/* Can this handle check commands? */
bool can_check;
+
+ /* custom feature-bits */
+ struct feature_set *fset;
};
/**
Why this scored 51/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.