Skip to content

Commit 4fac576

Browse files
committed
Merge pull request #6650
42cb388 Add chainstate obfuscation to avoid spurious antivirus detection (James O'Beirne)
2 parents b7d78fd + 42cb388 commit 4fac576

File tree

7 files changed

+338
-22
lines changed

7 files changed

+338
-22
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ BITCOIN_TESTS =\
5353
test/hash_tests.cpp \
5454
test/key_tests.cpp \
5555
test/limitedmap_tests.cpp \
56+
test/leveldbwrapper_tests.cpp \
5657
test/main_tests.cpp \
5758
test/mempool_tests.cpp \
5859
test/miner_tests.cpp \
@@ -73,6 +74,7 @@ BITCOIN_TESTS =\
7374
test/sighash_tests.cpp \
7475
test/sigopcount_tests.cpp \
7576
test/skiplist_tests.cpp \
77+
test/streams_tests.cpp \
7678
test/test_bitcoin.cpp \
7779
test/test_bitcoin.h \
7880
test/timedata_tests.cpp \

src/leveldbwrapper.cpp

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
#include "leveldbwrapper.h"
66

77
#include "util.h"
8+
#include "random.h"
89

910
#include <boost/filesystem.hpp>
1011

1112
#include <leveldb/cache.h>
1213
#include <leveldb/env.h>
1314
#include <leveldb/filter_policy.h>
1415
#include <memenv.h>
16+
#include <stdint.h>
1517

1618
void HandleError(const leveldb::Status& status) throw(leveldb_error)
1719
{
@@ -43,7 +45,7 @@ static leveldb::Options GetOptions(size_t nCacheSize)
4345
return options;
4446
}
4547

46-
CLevelDBWrapper::CLevelDBWrapper(const boost::filesystem::path& path, size_t nCacheSize, bool fMemory, bool fWipe)
48+
CLevelDBWrapper::CLevelDBWrapper(const boost::filesystem::path& path, size_t nCacheSize, bool fMemory, bool fWipe, bool obfuscate)
4749
{
4850
penv = NULL;
4951
readoptions.verify_checksums = true;
@@ -67,6 +69,25 @@ CLevelDBWrapper::CLevelDBWrapper(const boost::filesystem::path& path, size_t nCa
6769
leveldb::Status status = leveldb::DB::Open(options, path.string(), &pdb);
6870
HandleError(status);
6971
LogPrintf("Opened LevelDB successfully\n");
72+
73+
// The base-case obfuscation key, which is a noop.
74+
obfuscate_key = std::vector<unsigned char>(OBFUSCATE_KEY_NUM_BYTES, '\000');
75+
76+
bool key_exists = Read(OBFUSCATE_KEY_KEY, obfuscate_key);
77+
78+
if (!key_exists && obfuscate && IsEmpty()) {
79+
// Initialize non-degenerate obfuscation if it won't upset
80+
// existing, non-obfuscated data.
81+
std::vector<unsigned char> new_key = CreateObfuscateKey();
82+
83+
// Write `new_key` so we don't obfuscate the key with itself
84+
Write(OBFUSCATE_KEY_KEY, new_key);
85+
obfuscate_key = new_key;
86+
87+
LogPrintf("Wrote new obfuscate key for %s: %s\n", path.string(), GetObfuscateKeyHex());
88+
}
89+
90+
LogPrintf("Using obfuscation key for %s: %s\n", path.string(), GetObfuscateKeyHex());
7091
}
7192

7293
CLevelDBWrapper::~CLevelDBWrapper()
@@ -87,3 +108,40 @@ bool CLevelDBWrapper::WriteBatch(CLevelDBBatch& batch, bool fSync) throw(leveldb
87108
HandleError(status);
88109
return true;
89110
}
111+
112+
// Prefixed with null character to avoid collisions with other keys
113+
//
114+
// We must use a string constructor which specifies length so that we copy
115+
// past the null-terminator.
116+
const std::string CLevelDBWrapper::OBFUSCATE_KEY_KEY("\000obfuscate_key", 14);
117+
118+
const unsigned int CLevelDBWrapper::OBFUSCATE_KEY_NUM_BYTES = 8;
119+
120+
/**
121+
* Returns a string (consisting of 8 random bytes) suitable for use as an
122+
* obfuscating XOR key.
123+
*/
124+
std::vector<unsigned char> CLevelDBWrapper::CreateObfuscateKey() const
125+
{
126+
unsigned char buff[OBFUSCATE_KEY_NUM_BYTES];
127+
GetRandBytes(buff, OBFUSCATE_KEY_NUM_BYTES);
128+
return std::vector<unsigned char>(&buff[0], &buff[OBFUSCATE_KEY_NUM_BYTES]);
129+
130+
}
131+
132+
bool CLevelDBWrapper::IsEmpty()
133+
{
134+
boost::scoped_ptr<leveldb::Iterator> it(NewIterator());
135+
it->SeekToFirst();
136+
return !(it->Valid());
137+
}
138+
139+
const std::vector<unsigned char>& CLevelDBWrapper::GetObfuscateKey() const
140+
{
141+
return obfuscate_key;
142+
}
143+
144+
std::string CLevelDBWrapper::GetObfuscateKeyHex() const
145+
{
146+
return HexStr(obfuscate_key);
147+
}

src/leveldbwrapper.h

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "serialize.h"
1010
#include "streams.h"
1111
#include "util.h"
12+
#include "utilstrencodings.h"
1213
#include "version.h"
1314

1415
#include <boost/filesystem/path.hpp>
@@ -31,8 +32,14 @@ class CLevelDBBatch
3132

3233
private:
3334
leveldb::WriteBatch batch;
35+
const std::vector<unsigned char> obfuscate_key;
3436

3537
public:
38+
/**
39+
* @param[in] obfuscate_key If passed, XOR data with this key.
40+
*/
41+
CLevelDBBatch(const std::vector<unsigned char>& obfuscate_key) : obfuscate_key(obfuscate_key) { };
42+
3643
template <typename K, typename V>
3744
void Write(const K& key, const V& value)
3845
{
@@ -44,6 +51,7 @@ class CLevelDBBatch
4451
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
4552
ssValue.reserve(ssValue.GetSerializeSize(value));
4653
ssValue << value;
54+
ssValue.Xor(obfuscate_key);
4755
leveldb::Slice slValue(&ssValue[0], ssValue.size());
4856

4957
batch.Put(slKey, slValue);
@@ -85,8 +93,27 @@ class CLevelDBWrapper
8593
//! the database itself
8694
leveldb::DB* pdb;
8795

96+
//! a key used for optional XOR-obfuscation of the database
97+
std::vector<unsigned char> obfuscate_key;
98+
99+
//! the key under which the obfuscation key is stored
100+
static const std::string OBFUSCATE_KEY_KEY;
101+
102+
//! the length of the obfuscate key in number of bytes
103+
static const unsigned int OBFUSCATE_KEY_NUM_BYTES;
104+
105+
std::vector<unsigned char> CreateObfuscateKey() const;
106+
88107
public:
89-
CLevelDBWrapper(const boost::filesystem::path& path, size_t nCacheSize, bool fMemory = false, bool fWipe = false);
108+
/**
109+
* @param[in] path Location in the filesystem where leveldb data will be stored.
110+
* @param[in] nCacheSize Configures various leveldb cache settings.
111+
* @param[in] fMemory If true, use leveldb's memory environment.
112+
* @param[in] fWipe If true, remove all existing data.
113+
* @param[in] obfuscate If true, store data obfuscated via simple XOR. If false, XOR
114+
* with a zero'd byte array.
115+
*/
116+
CLevelDBWrapper(const boost::filesystem::path& path, size_t nCacheSize, bool fMemory = false, bool fWipe = false, bool obfuscate = false);
90117
~CLevelDBWrapper();
91118

92119
template <typename K, typename V>
@@ -107,6 +134,7 @@ class CLevelDBWrapper
107134
}
108135
try {
109136
CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION);
137+
ssValue.Xor(obfuscate_key);
110138
ssValue >> value;
111139
} catch (const std::exception&) {
112140
return false;
@@ -117,7 +145,7 @@ class CLevelDBWrapper
117145
template <typename K, typename V>
118146
bool Write(const K& key, const V& value, bool fSync = false) throw(leveldb_error)
119147
{
120-
CLevelDBBatch batch;
148+
CLevelDBBatch batch(obfuscate_key);
121149
batch.Write(key, value);
122150
return WriteBatch(batch, fSync);
123151
}
@@ -144,7 +172,7 @@ class CLevelDBWrapper
144172
template <typename K>
145173
bool Erase(const K& key, bool fSync = false) throw(leveldb_error)
146174
{
147-
CLevelDBBatch batch;
175+
CLevelDBBatch batch(obfuscate_key);
148176
batch.Erase(key);
149177
return WriteBatch(batch, fSync);
150178
}
@@ -159,7 +187,7 @@ class CLevelDBWrapper
159187

160188
bool Sync() throw(leveldb_error)
161189
{
162-
CLevelDBBatch batch;
190+
CLevelDBBatch batch(obfuscate_key);
163191
return WriteBatch(batch, true);
164192
}
165193

@@ -168,6 +196,23 @@ class CLevelDBWrapper
168196
{
169197
return pdb->NewIterator(iteroptions);
170198
}
199+
200+
/**
201+
* Return true if the database managed by this class contains no entries.
202+
*/
203+
bool IsEmpty();
204+
205+
/**
206+
* Accessor for obfuscate_key.
207+
*/
208+
const std::vector<unsigned char>& GetObfuscateKey() const;
209+
210+
/**
211+
* Return the obfuscate_key as a hex-formatted string.
212+
*/
213+
std::string GetObfuscateKeyHex() const;
214+
171215
};
172216

173217
#endif // BITCOIN_LEVELDBWRAPPER_H
218+

src/streams.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,29 @@ class CDataStream
296296
data.insert(data.end(), begin(), end());
297297
clear();
298298
}
299+
300+
/**
301+
* XOR the contents of this stream with a certain key.
302+
*
303+
* @param[in] key The key used to XOR the data in this stream.
304+
*/
305+
void Xor(const std::vector<unsigned char>& key)
306+
{
307+
if (key.size() == 0) {
308+
return;
309+
}
310+
311+
for (size_type i = 0, j = 0; i != size(); i++) {
312+
vch[i] ^= key[j++];
313+
314+
// This potentially acts on very many bytes of data, so it's
315+
// important that we calculate `j`, i.e. the `key` index in this
316+
// way instead of doing a %, which would effectively be a division
317+
// for each byte Xor'd -- much slower than need be.
318+
if (j == key.size())
319+
j = 0;
320+
}
321+
}
299322
};
300323

301324

src/test/leveldbwrapper_tests.cpp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright (c) 2012-2013 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "leveldbwrapper.h"
6+
#include "uint256.h"
7+
#include "random.h"
8+
#include "test/test_bitcoin.h"
9+
10+
#include <boost/assign/std/vector.hpp> // for 'operator+=()'
11+
#include <boost/assert.hpp>
12+
#include <boost/test/unit_test.hpp>
13+
14+
using namespace std;
15+
using namespace boost::assign; // bring 'operator+=()' into scope
16+
using namespace boost::filesystem;
17+
18+
// Test if a string consists entirely of null characters
19+
bool is_null_key(const vector<unsigned char>& key) {
20+
bool isnull = true;
21+
22+
for (unsigned int i = 0; i < key.size(); i++)
23+
isnull &= (key[i] == '\x00');
24+
25+
return isnull;
26+
}
27+
28+
BOOST_FIXTURE_TEST_SUITE(leveldbwrapper_tests, BasicTestingSetup)
29+
30+
BOOST_AUTO_TEST_CASE(leveldbwrapper)
31+
{
32+
// Perform tests both obfuscated and non-obfuscated.
33+
for (int i = 0; i < 2; i++) {
34+
bool obfuscate = (bool)i;
35+
path ph = temp_directory_path() / unique_path();
36+
CLevelDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);
37+
char key = 'k';
38+
uint256 in = GetRandHash();
39+
uint256 res;
40+
41+
// Ensure that we're doing real obfuscation when obfuscate=true
42+
BOOST_CHECK(obfuscate != is_null_key(dbw.GetObfuscateKey()));
43+
44+
BOOST_CHECK(dbw.Write(key, in));
45+
BOOST_CHECK(dbw.Read(key, res));
46+
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
47+
}
48+
}
49+
50+
// Test that we do not obfuscation if there is existing data.
51+
BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate)
52+
{
53+
// We're going to share this path between two wrappers
54+
path ph = temp_directory_path() / unique_path();
55+
create_directories(ph);
56+
57+
// Set up a non-obfuscated wrapper to write some initial data.
58+
CLevelDBWrapper* dbw = new CLevelDBWrapper(ph, (1 << 10), false, false, false);
59+
char key = 'k';
60+
uint256 in = GetRandHash();
61+
uint256 res;
62+
63+
BOOST_CHECK(dbw->Write(key, in));
64+
BOOST_CHECK(dbw->Read(key, res));
65+
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
66+
67+
// Call the destructor to free leveldb LOCK
68+
delete dbw;
69+
70+
// Now, set up another wrapper that wants to obfuscate the same directory
71+
CLevelDBWrapper odbw(ph, (1 << 10), false, false, true);
72+
73+
// Check that the key/val we wrote with unobfuscated wrapper exists and
74+
// is readable.
75+
uint256 res2;
76+
BOOST_CHECK(odbw.Read(key, res2));
77+
BOOST_CHECK_EQUAL(res2.ToString(), in.ToString());
78+
79+
BOOST_CHECK(!odbw.IsEmpty()); // There should be existing data
80+
BOOST_CHECK(is_null_key(odbw.GetObfuscateKey())); // The key should be an empty string
81+
82+
uint256 in2 = GetRandHash();
83+
uint256 res3;
84+
85+
// Check that we can write successfully
86+
BOOST_CHECK(odbw.Write(key, in2));
87+
BOOST_CHECK(odbw.Read(key, res3));
88+
BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString());
89+
}
90+
91+
// Ensure that we start obfuscating during a reindex.
92+
BOOST_AUTO_TEST_CASE(existing_data_reindex)
93+
{
94+
// We're going to share this path between two wrappers
95+
path ph = temp_directory_path() / unique_path();
96+
create_directories(ph);
97+
98+
// Set up a non-obfuscated wrapper to write some initial data.
99+
CLevelDBWrapper* dbw = new CLevelDBWrapper(ph, (1 << 10), false, false, false);
100+
char key = 'k';
101+
uint256 in = GetRandHash();
102+
uint256 res;
103+
104+
BOOST_CHECK(dbw->Write(key, in));
105+
BOOST_CHECK(dbw->Read(key, res));
106+
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
107+
108+
// Call the destructor to free leveldb LOCK
109+
delete dbw;
110+
111+
// Simulate a -reindex by wiping the existing data store
112+
CLevelDBWrapper odbw(ph, (1 << 10), false, true, true);
113+
114+
// Check that the key/val we wrote with unobfuscated wrapper doesn't exist
115+
uint256 res2;
116+
BOOST_CHECK(!odbw.Read(key, res2));
117+
BOOST_CHECK(!is_null_key(odbw.GetObfuscateKey()));
118+
119+
uint256 in2 = GetRandHash();
120+
uint256 res3;
121+
122+
// Check that we can write successfully
123+
BOOST_CHECK(odbw.Write(key, in2));
124+
BOOST_CHECK(odbw.Read(key, res3));
125+
BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString());
126+
}
127+
128+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)