// Copyright (c) 2016-2024 The Hush developers // Distributed under the GPLv3 software license, see the accompanying // file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html #include #include #include #include #include #include #include #include #include "chainparams.h" #include "init.h" #include "main.h" #include "noui.h" #include "util.h" #include "validationinterface.h" extern std::atomic fRequestShutdown; // A block file opened for reading must not be writable through its fd: a stray write through a reused fd number // (the wolfSSL CloseSocket macro let the wallet's BDB fd be closed behind its back) used to land in it. TEST(FdDefense, ReadOnlyBlockFileIsNotWritable) { SelectParams(CBaseChainParams::MAIN); const boost::filesystem::path dir = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path("fddefense-%%%%%%%%"); const bool hadDatadir = mapArgs.count("-datadir") > 0; const std::string oldDatadir = hadDatadir ? mapArgs["-datadir"] : ""; boost::filesystem::create_directories(dir); mapArgs["-datadir"] = dir.string(); ClearDatadirCache(); const CDiskBlockPos pos(0, 0); const boost::filesystem::path blk = GetBlockPosFilename(pos, "blk"); boost::filesystem::create_directories(blk.parent_path()); { FILE* f = fopen(blk.string().c_str(), "wb"); ASSERT_TRUE(f != nullptr); fputs("block bytes", f); fclose(f); } FILE* ro = OpenBlockFile(pos, true); ASSERT_TRUE(ro != nullptr); char c = 0; EXPECT_EQ(1u, fread(&c, 1, 1, ro)) << "still readable"; errno = 0; EXPECT_EQ(-1, pwrite(fileno(ro), "X", 1, 0)) << "a write through a read-only block file's fd must fail"; EXPECT_EQ(EBADF, errno); fclose(ro); FILE* rw = OpenBlockFile(pos, false); ASSERT_TRUE(rw != nullptr); EXPECT_EQ(1, pwrite(fileno(rw), "b", 1, 0)) << "the writable open still writes"; fclose(rw); if (hadDatadir) mapArgs["-datadir"] = oldDatadir; else mapArgs.erase("-datadir"); ClearDatadirCache(); boost::filesystem::remove_all(dir); } // A wallet notification that throws (a wallet DB failure) requests a clean shutdown instead of escaping the // notifier thread into std::terminate; a successful one doesn't; thread interruption still propagates. TEST(FdDefense, WalletNotifyFailureShutsDownCleanly) { noui_connect(); // the daemon connects these UI signals at startup; the error path shows a message box fRequestShutdown = false; EXPECT_TRUE(NotifyWalletsOrShutdown([] {})); EXPECT_FALSE(ShutdownRequested()); EXPECT_FALSE(NotifyWalletsOrShutdown([] { throw std::runtime_error("CWalletDB::ListAccountCreditDebit(): error scanning DB"); })); EXPECT_TRUE(ShutdownRequested()); fRequestShutdown = false; EXPECT_FALSE(NotifyWalletsOrShutdown([] { throw 42; })); EXPECT_TRUE(ShutdownRequested()); fRequestShutdown = false; EXPECT_THROW(NotifyWalletsOrShutdown([] { throw boost::thread_interrupted(); }), boost::thread_interrupted); EXPECT_FALSE(ShutdownRequested()); }