mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-05 14:06:27 -05:00
Merge bitcoin/bitcoin#28052: blockstorage: XOR blocksdir *.dat files
fa895c7283
mingw: Document mode wbx workaround (MarcoFalke)fa359255fe
Add -blocksxor boolean option (MarcoFalke)fa7f7ac040
Return XOR AutoFile from BlockManager::Open*File() (MarcoFalke) Pull request description: Currently the *.dat files in the blocksdir store the data received from remote peers as-is. This may be problematic when a program other than Bitcoin Core tries to interpret them by accident. For example, an anti-virus program or other program may scan them and move them into quarantine, or delete them, or corrupt them. This may cause Bitcoin Core to fail a reorg, or fail to reply to block requests (via P2P, RPC, REST, ...). Fix this, similar to https://github.com/bitcoin/bitcoin/pull/6650, by rolling a random XOR pattern over the dat files when writing or reading them. Obviously this can only protect against programs that accidentally and unintentionally are trying to mess with the dat files. Any program that intentionally wants to mess with the dat files can still trivially do so. The XOR pattern is only applied when the blocksdir is freshly created, and there is an option to disable it (on creation), so that people can disable it, if needed. ACKs for top commit: achow101: ACKfa895c7283
TheCharlatan: Re-ACKfa895c7283
hodlinator: ACKfa895c7283
Tree-SHA512: c92a6a717da83bc33a9b8671a779eeefde2c63b192362ba1d71e6535ee31d08e2802b74acc908345197de9daac6930e4771595ee25b09acd5a67f7ea34854720
This commit is contained in:
commit
949b673472
10 changed files with 97 additions and 16 deletions
|
@ -47,8 +47,9 @@ Subdirectory | File(s) | Description
|
||||||
-------------------|-----------------------|------------
|
-------------------|-----------------------|------------
|
||||||
`blocks/` | | Blocks directory; can be specified by `-blocksdir` option (except for `blocks/index/`)
|
`blocks/` | | Blocks directory; can be specified by `-blocksdir` option (except for `blocks/index/`)
|
||||||
`blocks/index/` | LevelDB database | Block index; `-blocksdir` option does not affect this path
|
`blocks/index/` | LevelDB database | Block index; `-blocksdir` option does not affect this path
|
||||||
`blocks/` | `blkNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Actual Bitcoin blocks (in network format, dumped in raw on disk, 128 MiB per file)
|
`blocks/` | `blkNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Actual Bitcoin blocks (dumped in network format, 128 MiB per file)
|
||||||
`blocks/` | `revNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Block undo data (custom format)
|
`blocks/` | `revNNNNN.dat`<sup>[\[2\]](#note2)</sup> | Block undo data (custom format)
|
||||||
|
`blocks/` | `xor.dat` | Rolling XOR pattern for block and undo data files
|
||||||
`chainstate/` | LevelDB database | Blockchain state (a compact representation of all currently unspent transaction outputs (UTXOs) and metadata about the transactions they are from)
|
`chainstate/` | LevelDB database | Blockchain state (a compact representation of all currently unspent transaction outputs (UTXOs) and metadata about the transactions they are from)
|
||||||
`indexes/txindex/` | LevelDB database | Transaction index; *optional*, used if `-txindex=1`
|
`indexes/txindex/` | LevelDB database | Transaction index; *optional*, used if `-txindex=1`
|
||||||
`indexes/blockfilter/basic/db/` | LevelDB database | Blockfilter index LevelDB database for the basic filtertype; *optional*, used if `-blockfilterindex=basic`
|
`indexes/blockfilter/basic/db/` | LevelDB database | Blockfilter index LevelDB database for the basic filtertype; *optional*, used if `-blockfilterindex=basic`
|
||||||
|
|
6
doc/release-notes-28052.md
Normal file
6
doc/release-notes-28052.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Blockstorage
|
||||||
|
============
|
||||||
|
|
||||||
|
Block files are now XOR'd by default with a key stored in the blocksdir.
|
||||||
|
Previous releases of Bitcoin Core or previous external software will not be able to read the blocksdir with a non-zero XOR-key.
|
||||||
|
Refer to the `-blocksxor` help for more details.
|
13
src/init.cpp
13
src/init.cpp
|
@ -469,6 +469,13 @@ void SetupServerArgs(ArgsManager& argsman)
|
||||||
#endif
|
#endif
|
||||||
argsman.AddArg("-assumevalid=<hex>", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s, signet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex(), signetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-assumevalid=<hex>", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s, signet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex(), signetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-blocksdir=<dir>", "Specify directory to hold blocks subdirectory for *.dat files (default: <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-blocksdir=<dir>", "Specify directory to hold blocks subdirectory for *.dat files (default: <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
|
argsman.AddArg("-blocksxor",
|
||||||
|
strprintf("Whether an XOR-key applies to blocksdir *.dat files. "
|
||||||
|
"The created XOR-key will be zeros for an existing blocksdir or when `-blocksxor=0` is "
|
||||||
|
"set, and random for a freshly initialized blocksdir. "
|
||||||
|
"(default: %u)",
|
||||||
|
kernel::DEFAULT_XOR_BLOCKSDIR),
|
||||||
|
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
argsman.AddArg("-fastprune", "Use smaller block files and lower minimum prune height for testing purposes", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
|
argsman.AddArg("-fastprune", "Use smaller block files and lower minimum prune height for testing purposes", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
|
||||||
#if HAVE_SYSTEM
|
#if HAVE_SYSTEM
|
||||||
argsman.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
argsman.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||||
|
@ -1534,7 +1541,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
|
||||||
}
|
}
|
||||||
LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), mempool_opts.max_size_bytes * (1.0 / 1024 / 1024));
|
LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), mempool_opts.max_size_bytes * (1.0 / 1024 / 1024));
|
||||||
|
|
||||||
node.chainman = std::make_unique<ChainstateManager>(*Assert(node.shutdown), chainman_opts, blockman_opts);
|
try {
|
||||||
|
node.chainman = std::make_unique<ChainstateManager>(*Assert(node.shutdown), chainman_opts, blockman_opts);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
return InitError(strprintf(Untranslated("Failed to initialize ChainstateManager: %s"), e.what()));
|
||||||
|
}
|
||||||
ChainstateManager& chainman = *node.chainman;
|
ChainstateManager& chainman = *node.chainman;
|
||||||
|
|
||||||
// This is defined and set here instead of inline in validation.h to avoid a hard
|
// This is defined and set here instead of inline in validation.h to avoid a hard
|
||||||
|
|
|
@ -14,12 +14,15 @@ class CChainParams;
|
||||||
|
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
|
|
||||||
|
static constexpr bool DEFAULT_XOR_BLOCKSDIR{true};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An options struct for `BlockManager`, more ergonomically referred to as
|
* An options struct for `BlockManager`, more ergonomically referred to as
|
||||||
* `BlockManager::Options` due to the using-declaration in `BlockManager`.
|
* `BlockManager::Options` due to the using-declaration in `BlockManager`.
|
||||||
*/
|
*/
|
||||||
struct BlockManagerOpts {
|
struct BlockManagerOpts {
|
||||||
const CChainParams& chainparams;
|
const CChainParams& chainparams;
|
||||||
|
bool use_xor{DEFAULT_XOR_BLOCKSDIR};
|
||||||
uint64_t prune_target{0};
|
uint64_t prune_target{0};
|
||||||
bool fast_prune{false};
|
bool fast_prune{false};
|
||||||
const fs::path blocks_dir;
|
const fs::path blocks_dir;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
namespace node {
|
namespace node {
|
||||||
util::Result<void> ApplyArgsManOptions(const ArgsManager& args, BlockManager::Options& opts)
|
util::Result<void> ApplyArgsManOptions(const ArgsManager& args, BlockManager::Options& opts)
|
||||||
{
|
{
|
||||||
|
if (auto value{args.GetBoolArg("-blocksxor")}) opts.use_xor = *value;
|
||||||
// block pruning; get the amount of disk space (in MiB) to allot for block & undo files
|
// block pruning; get the amount of disk space (in MiB) to allot for block & undo files
|
||||||
int64_t nPruneArg{args.GetIntArg("-prune", opts.prune_target)};
|
int64_t nPruneArg{args.GetIntArg("-prune", opts.prune_target)};
|
||||||
if (nPruneArg < 0) {
|
if (nPruneArg < 0) {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include <pow.h>
|
#include <pow.h>
|
||||||
#include <primitives/block.h>
|
#include <primitives/block.h>
|
||||||
#include <primitives/transaction.h>
|
#include <primitives/transaction.h>
|
||||||
|
#include <random.h>
|
||||||
#include <reverse_iterator.h>
|
#include <reverse_iterator.h>
|
||||||
#include <serialize.h>
|
#include <serialize.h>
|
||||||
#include <signet.h>
|
#include <signet.h>
|
||||||
|
@ -818,13 +819,13 @@ void BlockManager::UnlinkPrunedFiles(const std::set<int>& setFilesToPrune) const
|
||||||
|
|
||||||
AutoFile BlockManager::OpenBlockFile(const FlatFilePos& pos, bool fReadOnly) const
|
AutoFile BlockManager::OpenBlockFile(const FlatFilePos& pos, bool fReadOnly) const
|
||||||
{
|
{
|
||||||
return AutoFile{m_block_file_seq.Open(pos, fReadOnly)};
|
return AutoFile{m_block_file_seq.Open(pos, fReadOnly), m_xor_key};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Open an undo file (rev?????.dat) */
|
/** Open an undo file (rev?????.dat) */
|
||||||
AutoFile BlockManager::OpenUndoFile(const FlatFilePos& pos, bool fReadOnly) const
|
AutoFile BlockManager::OpenUndoFile(const FlatFilePos& pos, bool fReadOnly) const
|
||||||
{
|
{
|
||||||
return AutoFile{m_undo_file_seq.Open(pos, fReadOnly)};
|
return AutoFile{m_undo_file_seq.Open(pos, fReadOnly), m_xor_key};
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::path BlockManager::GetBlockPosFilename(const FlatFilePos& pos) const
|
fs::path BlockManager::GetBlockPosFilename(const FlatFilePos& pos) const
|
||||||
|
@ -1144,6 +1145,54 @@ FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight)
|
||||||
return blockPos;
|
return blockPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static auto InitBlocksdirXorKey(const BlockManager::Options& opts)
|
||||||
|
{
|
||||||
|
// Bytes are serialized without length indicator, so this is also the exact
|
||||||
|
// size of the XOR-key file.
|
||||||
|
std::array<std::byte, 8> xor_key{};
|
||||||
|
|
||||||
|
if (opts.use_xor && fs::is_empty(opts.blocks_dir)) {
|
||||||
|
// Only use random fresh key when the boolean option is set and on the
|
||||||
|
// very first start of the program.
|
||||||
|
FastRandomContext{}.fillrand(xor_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path xor_key_path{opts.blocks_dir / "xor.dat"};
|
||||||
|
if (fs::exists(xor_key_path)) {
|
||||||
|
// A pre-existing xor key file has priority.
|
||||||
|
AutoFile xor_key_file{fsbridge::fopen(xor_key_path, "rb")};
|
||||||
|
xor_key_file >> xor_key;
|
||||||
|
} else {
|
||||||
|
// Create initial or missing xor key file
|
||||||
|
AutoFile xor_key_file{fsbridge::fopen(xor_key_path,
|
||||||
|
#ifdef __MINGW64__
|
||||||
|
"wb" // Temporary workaround for https://github.com/bitcoin/bitcoin/issues/30210
|
||||||
|
#else
|
||||||
|
"wbx"
|
||||||
|
#endif
|
||||||
|
)};
|
||||||
|
xor_key_file << xor_key;
|
||||||
|
}
|
||||||
|
// If the user disabled the key, it must be zero.
|
||||||
|
if (!opts.use_xor && xor_key != decltype(xor_key){}) {
|
||||||
|
throw std::runtime_error{
|
||||||
|
strprintf("The blocksdir XOR-key can not be disabled when a random key was already stored! "
|
||||||
|
"Stored key: '%s', stored path: '%s'.",
|
||||||
|
HexStr(xor_key), fs::PathToString(xor_key_path)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
LogInfo("Using obfuscation key for blocksdir *.dat files (%s): '%s'\n", fs::PathToString(opts.blocks_dir), HexStr(xor_key));
|
||||||
|
return std::vector<std::byte>{xor_key.begin(), xor_key.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockManager::BlockManager(const util::SignalInterrupt& interrupt, Options opts)
|
||||||
|
: m_prune_mode{opts.prune_target > 0},
|
||||||
|
m_xor_key{InitBlocksdirXorKey(opts)},
|
||||||
|
m_opts{std::move(opts)},
|
||||||
|
m_block_file_seq{FlatFileSeq{m_opts.blocks_dir, "blk", m_opts.fast_prune ? 0x4000 /* 16kB */ : BLOCKFILE_CHUNK_SIZE}},
|
||||||
|
m_undo_file_seq{FlatFileSeq{m_opts.blocks_dir, "rev", UNDOFILE_CHUNK_SIZE}},
|
||||||
|
m_interrupt{interrupt} {}
|
||||||
|
|
||||||
class ImportingNow
|
class ImportingNow
|
||||||
{
|
{
|
||||||
std::atomic<bool>& m_importing;
|
std::atomic<bool>& m_importing;
|
||||||
|
|
|
@ -240,6 +240,8 @@ private:
|
||||||
|
|
||||||
const bool m_prune_mode;
|
const bool m_prune_mode;
|
||||||
|
|
||||||
|
const std::vector<std::byte> m_xor_key;
|
||||||
|
|
||||||
/** Dirty block index entries. */
|
/** Dirty block index entries. */
|
||||||
std::set<CBlockIndex*> m_dirty_blockindex;
|
std::set<CBlockIndex*> m_dirty_blockindex;
|
||||||
|
|
||||||
|
@ -264,12 +266,7 @@ private:
|
||||||
public:
|
public:
|
||||||
using Options = kernel::BlockManagerOpts;
|
using Options = kernel::BlockManagerOpts;
|
||||||
|
|
||||||
explicit BlockManager(const util::SignalInterrupt& interrupt, Options opts)
|
explicit BlockManager(const util::SignalInterrupt& interrupt, Options opts);
|
||||||
: m_prune_mode{opts.prune_target > 0},
|
|
||||||
m_opts{std::move(opts)},
|
|
||||||
m_block_file_seq{FlatFileSeq{m_opts.blocks_dir, "blk", m_opts.fast_prune ? 0x4000 /* 16kB */ : BLOCKFILE_CHUNK_SIZE}},
|
|
||||||
m_undo_file_seq{FlatFileSeq{m_opts.blocks_dir, "rev", UNDOFILE_CHUNK_SIZE}},
|
|
||||||
m_interrupt{interrupt} {}
|
|
||||||
|
|
||||||
const util::SignalInterrupt& m_interrupt;
|
const util::SignalInterrupt& m_interrupt;
|
||||||
std::atomic<bool> m_importing{false};
|
std::atomic<bool> m_importing{false};
|
||||||
|
|
|
@ -30,8 +30,7 @@ BOOST_AUTO_TEST_CASE(xor_file)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
#ifdef __MINGW64__
|
#ifdef __MINGW64__
|
||||||
// Our usage of mingw-w64 and the msvcrt runtime does not support
|
// Temporary workaround for https://github.com/bitcoin/bitcoin/issues/30210
|
||||||
// the x modifier for the _wfopen().
|
|
||||||
const char* mode = "wb";
|
const char* mode = "wb";
|
||||||
#else
|
#else
|
||||||
const char* mode = "wbx";
|
const char* mode = "wbx";
|
||||||
|
|
|
@ -26,6 +26,10 @@ class LoadblockTest(BitcoinTestFramework):
|
||||||
self.setup_clean_chain = True
|
self.setup_clean_chain = True
|
||||||
self.num_nodes = 2
|
self.num_nodes = 2
|
||||||
self.supports_cli = False
|
self.supports_cli = False
|
||||||
|
self.extra_args = [
|
||||||
|
["-blocksxor=0"], # TODO: The linearize scripts should be adjusted to apply any XOR
|
||||||
|
[],
|
||||||
|
]
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
self.nodes[1].setnetworkactive(state=False)
|
self.nodes[1].setnetworkactive(state=False)
|
||||||
|
|
|
@ -39,9 +39,19 @@ class ReindexTest(BitcoinTestFramework):
|
||||||
# we're generating them rather than getting them from peers), so to
|
# we're generating them rather than getting them from peers), so to
|
||||||
# test out-of-order handling, swap blocks 1 and 2 on disk.
|
# test out-of-order handling, swap blocks 1 and 2 on disk.
|
||||||
blk0 = self.nodes[0].blocks_path / "blk00000.dat"
|
blk0 = self.nodes[0].blocks_path / "blk00000.dat"
|
||||||
|
with open(self.nodes[0].blocks_path / "xor.dat", "rb") as xor_f:
|
||||||
|
NUM_XOR_BYTES = 8 # From InitBlocksdirXorKey::xor_key.size()
|
||||||
|
xor_dat = xor_f.read(NUM_XOR_BYTES)
|
||||||
|
|
||||||
|
def util_xor(data, key, *, offset):
|
||||||
|
data = bytearray(data)
|
||||||
|
for i in range(len(data)):
|
||||||
|
data[i] ^= key[(i + offset) % len(key)]
|
||||||
|
return bytes(data)
|
||||||
|
|
||||||
with open(blk0, 'r+b') as bf:
|
with open(blk0, 'r+b') as bf:
|
||||||
# Read at least the first few blocks (including genesis)
|
# Read at least the first few blocks (including genesis)
|
||||||
b = bf.read(2000)
|
b = util_xor(bf.read(2000), xor_dat, offset=0)
|
||||||
|
|
||||||
# Find the offsets of blocks 2, 3, and 4 (the first 3 blocks beyond genesis)
|
# Find the offsets of blocks 2, 3, and 4 (the first 3 blocks beyond genesis)
|
||||||
# by searching for the regtest marker bytes (see pchMessageStart).
|
# by searching for the regtest marker bytes (see pchMessageStart).
|
||||||
|
@ -55,12 +65,12 @@ class ReindexTest(BitcoinTestFramework):
|
||||||
b4_start = find_block(b, b3_start)
|
b4_start = find_block(b, b3_start)
|
||||||
|
|
||||||
# Blocks 2 and 3 should be the same size.
|
# Blocks 2 and 3 should be the same size.
|
||||||
assert_equal(b3_start-b2_start, b4_start-b3_start)
|
assert_equal(b3_start - b2_start, b4_start - b3_start)
|
||||||
|
|
||||||
# Swap the second and third blocks (don't disturb the genesis block).
|
# Swap the second and third blocks (don't disturb the genesis block).
|
||||||
bf.seek(b2_start)
|
bf.seek(b2_start)
|
||||||
bf.write(b[b3_start:b4_start])
|
bf.write(util_xor(b[b3_start:b4_start], xor_dat, offset=b2_start))
|
||||||
bf.write(b[b2_start:b3_start])
|
bf.write(util_xor(b[b2_start:b3_start], xor_dat, offset=b3_start))
|
||||||
|
|
||||||
# The reindexing code should detect and accommodate out of order blocks.
|
# The reindexing code should detect and accommodate out of order blocks.
|
||||||
with self.nodes[0].assert_debug_log([
|
with self.nodes[0].assert_debug_log([
|
||||||
|
|
Loading…
Add table
Reference in a new issue