util: Check write failures before renaming settings.json
What changed, and why it matters
This commit fixes a bug in how Bitcoin Core saves its settings file. Previously, if the disk was full or a write failed, the program could replace the user's valid settings file with an empty or corrupted one. Now it checks that the new file was written successfully before overwriting the old one. It also updates an error message to mention power loss, full disk, or storage errors as possible causes of a damaged settings file. This is a reliability and data-loss fix, not an attack that a remote hacker can exploit.
No urgent action required. This is a defensive patch that improves resilience against disk-full and write-failure conditions. Users and node operators should keep systems updated normally; operators running nodes on constrained storage should monitor disk space and logs for the new error messages.
Security signals we found
Data integrity / data-loss prevention
Filesystem/disk-full failure handling
Atomic file replacement safety check
Settings file corruption mitigation
Evidence from the diff
In src/common/settings.cpp, WriteSettings() now checks file.fail() after writing JSON to the temporary settings file and again after closing it, returning false and adding an error message before RenameOver() is called. This prevents RenameOver() from replacing settings.json with a partially written or zero-byte file when write() or close() fails (e.g., due to ENOSPC, I/O errors, or fsync failure). ReadSettings()’s parse-failure message was broadened to include power loss, full disk, and storage errors. Unit and functional tests were updated to match the new message text.
Changed components
src/common/settings.cppsrc/test/settings_tests.cpptest/functional/feature_settings.pyBitcoin Core settings persistence (settings.json)Inspect captured patch +11 / −3
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index e9b92979..7d511b57 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -86,7 +86,7 @@ bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& va
SettingsValue in;
if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) {
- errors.emplace_back(strprintf("Settings file %s does not contain valid JSON. This is probably caused by disk corruption or a crash, "
+ errors.emplace_back(strprintf("Settings file %s does not contain valid JSON. This may be caused by a crash, power loss, full disk, or storage error, "
"and can be fixed by removing the file, which will reset settings to default values.",
fs::PathToString(path)));
return false;
@@ -139,7 +139,15 @@ bool WriteSettings(const fs::path& path,
return false;
}
file << out.write(/* prettyIndent= */ 4, /* indentLevel= */ 1) << std::endl;
+ if (file.fail()) {
+ errors.emplace_back(strprintf("Error: Unable to write settings file %s", fs::PathToString(path)));
+ return false;
+ }
file.close();
+ if (file.fail()) {
+ errors.emplace_back(strprintf("Error: Unable to close settings file %s", fs::PathToString(path)));
+ return false;
+ }
return true;
}
diff --git a/src/test/settings_tests.cpp b/src/test/settings_tests.cpp
index 54d3b058..0d8e3b1b 100644
--- a/src/test/settings_tests.cpp
+++ b/src/test/settings_tests.cpp
@@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(ReadWrite)
// Check invalid json not allowed
WriteText(path, R"(invalid json)");
BOOST_CHECK(!common::ReadSettings(path, values, errors));
- std::vector<std::string> fail_parse = {strprintf("Settings file %s does not contain valid JSON. This is probably caused by disk corruption or a crash, "
+ std::vector<std::string> fail_parse = {strprintf("Settings file %s does not contain valid JSON. This may be caused by a crash, power loss, full disk, or storage error, "
"and can be fixed by removing the file, which will reset settings to default values.",
fs::PathToString(path))};
BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), fail_parse.begin(), fail_parse.end());
diff --git a/test/functional/feature_settings.py b/test/functional/feature_settings.py
index 9a4138b9..0585e056 100755
--- a/test/functional/feature_settings.py
+++ b/test/functional/feature_settings.py
@@ -73,7 +73,7 @@ class SettingsTest(BitcoinTestFramework):
# Test invalid json
with settings.open("w") as fp:
fp.write("invalid json")
- node.assert_start_raises_init_error(expected_msg='does not contain valid JSON. This is probably caused by disk corruption or a crash', match=ErrorMatch.PARTIAL_REGEX)
+ node.assert_start_raises_init_error(expected_msg='does not contain valid JSON. This may be caused by a crash, power loss, full disk, or storage error', match=ErrorMatch.PARTIAL_REGEX)
# Test invalid json object
with settings.open("w") as fp:
Why this scored 43/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.