wallet: handle non-writable db directories
What changed, and why it matters
This Bitcoin Core commit fixes a bug where the program would crash if a wallet was loaded from a directory that cannot be written to. It also gives a clearer error message when someone tries to create a wallet in such a directory. The fix checks whether the directory is writable before opening the wallet database, and reports a helpful error instead of crashing or showing a generic failure.
Apply the patch. It is a defensive hardening fix that prevents a reproducible crash condition and improves operational diagnostics. No immediate incident response is indicated unless nodes are known to be running with wallets on read-only filesystems.
Security signals we found
Denial-of-service vector: loading a wallet on a non-writable directory caused a node crash on subsequent writes
Input/environment validation added: explicit writability check before database open
Error-handling improvement: clearer error message for wallet creation failures
Evidence from the diff
The patch adds an IsDirWritable() helper that probes writability by creating a temporary file in the target directory. SQLiteDatabase::Open() now calls this helper after creating directories and before opening the SQLite database. For wallet load paths, this prevents a later crash on any database write (e.g., during block generation). For wallet creation paths, it replaces the generic ‘unable to open database file’ SQLite error with an explicit ‘directory is not writable’ message.
Changed components
src/util/fs_helpers.cppsrc/util/fs_helpers.hsrc/wallet/sqlite.cppBitcoin Core wallet database loading/creationInspect captured patch +38 / −1
diff --git a/src/util/fs_helpers.cpp b/src/util/fs_helpers.cpp
index a41bf65a..097fbef9 100644
--- a/src/util/fs_helpers.cpp
+++ b/src/util/fs_helpers.cpp
@@ -6,8 +6,9 @@
#include <bitcoin-build-config.h> // IWYU pragma: keep
#include <util/fs_helpers.h>
-
+#include <random.h>
#include <sync.h>
+#include <tinyformat.h>
#include <util/byte_units.h> // IWYU pragma: keep
#include <util/fs.h>
#include <util/log.h>
@@ -18,6 +19,7 @@
#include <map>
#include <memory>
#include <optional>
+#include <stdexcept>
#include <string>
#include <system_error>
#include <utility>
@@ -306,6 +308,29 @@ std::optional<fs::perms> InterpretPermString(const std::string& s)
}
}
+bool IsDirWritable(const fs::path& dir_path)
+{
+ // Attempt to create a tmp file in the directory
+ if (!fs::is_directory(dir_path)) throw std::runtime_error(strprintf("Path %s is not a directory", fs::PathToString(dir_path)));
+ FastRandomContext rng;
+ const auto tmp = dir_path / fs::PathFromString(strprintf(".tmp_%d", rng.rand64()));
+
+ const char* mode;
+#ifdef __MINGW64__
+ mode = "w"; // Temporary workaround for https://github.com/bitcoin/bitcoin/issues/30210
+#else
+ mode = "wx";
+#endif
+
+ if (const auto created{fsbridge::fopen(tmp, mode)}) {
+ std::fclose(created);
+ std::error_code ec;
+ fs::remove(tmp, ec); // clean up, ignore errors
+ return true;
+ }
+ return false;
+}
+
#ifdef __APPLE__
FSType GetFilesystemType(const fs::path& path)
{
diff --git a/src/util/fs_helpers.h b/src/util/fs_helpers.h
index face17fd..f4d406f7 100644
--- a/src/util/fs_helpers.h
+++ b/src/util/fs_helpers.h
@@ -94,6 +94,14 @@ std::string PermsToSymbolicString(fs::perms p);
*/
std::optional<fs::perms> InterpretPermString(const std::string& s);
+/** Check if a directory is writable by creating a temporary file on it.
+ *
+ * @param[in] dir_path Path of the directory to test
+ * @return true if a temporary file could be created and removed, false otherwise.
+ * @throw std::runtime_error if dir_path is not a directory.
+ */
+bool IsDirWritable(const fs::path& dir_path);
+
#ifdef WIN32
fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
#endif
diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp
index 3d6583bb..17414521 100644
--- a/src/wallet/sqlite.cpp
+++ b/src/wallet/sqlite.cpp
@@ -257,7 +257,11 @@ void SQLiteDatabase::Open(int additional_flags)
if (m_db == nullptr) {
if (!(flags & SQLITE_OPEN_MEMORY)) {
TryCreateDirectories(m_dir_path);
+ if (!IsDirWritable(m_dir_path)) {
+ throw std::runtime_error(strprintf("SQLiteDatabase: Failed to open database in directory '%s': directory is not writable", fs::PathToString(m_dir_path)));
+ }
}
+
int ret = sqlite3_open_v2(m_file_path.c_str(), &m_db, flags, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to open database: %s\n", sqlite3_errstr(ret)));
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.