Merge bitcoin/bitcoin#36176: wallet: avoid a crash when creating a wallet with -nosettings
What changed, and why it matters
This commit fixes a crash in Bitcoin Core's graphical wallet (Bitcoin-Qt) when creating a wallet while the user has disabled dynamic settings with -nosettings. Previously, the program would terminate with an uncaught exception because it tried to write to settings.json even though that file was disabled. After the fix, the wallet is created successfully and only shows a warning that the startup preference could not be saved. The same issue also caused RPC commands to return errors after the wallet state had already changed. The fix makes the settings write return a failure instead of throwing, so wallet operations complete normally and warn the user.
Apply the patch. It is a low-severity reliability fix that prevents a local crash and RPC inconsistency when dynamic settings are disabled. No immediate security incident response is needed, but users running -nosettings should upgrade to avoid the crash.
Security signals we found
Denial of service via local configuration: -nosettings triggers uncaught exception in GUI
Uncaught std::logic_error leading to application termination
RPC state inconsistency: error returned after wallet state mutation
Settings write failure now returns false instead of throwing
Functional test added for disabled dynamic settings wallet behavior
Evidence from the diff
The change is in ChainImpl::updateRwSetting in src/node/interfaces.cpp. Previously, when a wallet requested to persist a load-on-startup preference, the code called args().WriteSettingsFile() unconditionally when the update action was WRITE. With -nosettings, WriteSettingsFile() throws std::logic_error because dynamic settings are disabled. In Qt this was uncaught and crashed the application; in RPC it produced an error after the wallet state had already changed. The fix adds a check for args().GetSettingsPath() before calling WriteSettingsFile(), so the function returns false (persistence failure) instead of throwing. Callers already handle false by emitting a warning and keeping in-memory changes. The header comment is updated to document this behavior, and a functional test is added to verify wallet operations succeed with warnings when -nosettings is used and that settings.json remains unchanged.
Changed components
src/node/interfaces.cpp ChainImpl::updateRwSettingsrc/interfaces/chain.h updateRwSetting documentationBitcoin-Qt GUI wallet creationWallet RPC createwallet/loadwallet/unloadwallettest/functional/wallet_startup.pyInspect captured patch +34 / −1
### src/interfaces/chain.h
@@ -358,6 +358,9 @@ class Chain
//! support for writing null values to settings.json.
//! Depending on the action returned by the update function, this will either
//! update the setting in memory or write the updated settings to disk.
+ //! Returns false if the update function returned no action, or if the
+ //! settings could not be written to disk, including when settings are
+ //! disabled with -nosettings. In-memory changes are kept either way.
virtual bool updateRwSetting(const std::string& name, const SettingsUpdate& update_function) = 0;
//! Replace a setting in <datadir>/settings.json with a new value.
### src/node/interfaces.cpp
@@ -843,7 +843,7 @@ class ChainImpl : public Chain
});
if (!action) return false;
// Now dump value to disk if requested
- return *action != interfaces::SettingsAction::WRITE || args().WriteSettingsFile();
+ return *action != interfaces::SettingsAction::WRITE || (args().GetSettingsPath() && args().WriteSettingsFile());
}
bool overwriteRwSetting(const std::string& name, common::SettingsValue value, interfaces::SettingsAction action) override
{
### test/functional/wallet_startup.py
@@ -64,6 +64,35 @@ def test_load_unwritable_wallet(self, node):
# Reset directory permissions for cleanup
dir_path.chmod(original_dir_perms)
+ def test_disabled_settings(self, node):
+ self.log.info("Test wallet startup preferences with dynamic settings disabled")
+ load_message = "Wallet load on startup setting could not be updated, so wallet may not be loaded next node startup."
+
+ settings_path = node.chain_path / "settings.json"
+ settings_before = settings_path.read_bytes()
+ self.restart_node(0, extra_args=["-nosettings"])
+ assert_equal(node.listwallets(), [''])
+
+ assert_equal(node.createwallet(wallet_name="no_settings", load_on_startup=True), {"name": "no_settings", "warnings": [load_message]})
+ assert_equal(set(node.listwallets()), {'', 'no_settings'})
+
+ # Leaving the startup preference unchanged does not warn, and the wallet remains usable.
+ assert_equal(node.unloadwallet(wallet_name="no_settings"), {})
+ assert_equal(node.loadwallet(filename="no_settings"), {"name": "no_settings"})
+ assert_equal(node.get_wallet_rpc("no_settings").getwalletinfo()["walletname"], "no_settings")
+
+ assert_equal(node.loadwallet(filename="w2", load_on_startup=True), {"name": "w2", "warnings": [load_message]})
+ assert_equal(set(node.listwallets()), {'', 'no_settings', 'w2'})
+
+ assert_equal(node.unloadwallet(wallet_name="no_settings", load_on_startup=False), {"warnings": ["Wallet load on startup setting could not be updated, so wallet may still be loaded next node startup."]})
+ assert_equal(set(node.listwallets()), {'', 'w2'})
+ self.stop_node(0)
+ assert_equal(settings_path.read_bytes(), settings_before)
+
+ # Re-enabling settings restores the original startup preferences.
+ self.start_node(0)
+ assert_equal(set(node.listwallets()), {'w2', 'w3'})
+
def run_test(self):
self.log.info('Should start without any wallets')
assert_equal(self.nodes[0].listwallets(), [])
@@ -94,6 +123,7 @@ def run_test(self):
assert_equal(set(self.nodes[0].listwallets()), set(('w2', 'w3')))
self.test_load_unwritable_wallet(self.nodes[0])
+ self.test_disabled_settings(self.nodes[0])
if __name__ == '__main__':
WalletStartupTest(__file__).main()Why this scored 44/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.