foundationdb/flow/WipedString.cpp

259 lines
7.2 KiB
C++

#include "flow/FastAlloc.h"
#include "flow/ScopeExit.h"
#include "flow/UnitTest.h"
#include "flow/WipedString.h"
#include "flow/ObjectSerializer.h"
#include "flow/Net2Packet.h"
#include "flow/serialize.h"
static_assert(detail::is_wipe_enabled<ObjectWriter::SaveContext>);
TEST_CASE("/flow/WipedString/basic") {
auto& rng = *deterministicRandom();
for (auto iter = 0; iter < 100; iter++) {
auto randomString = rng.randomAlphaNumeric(rng.randomInt(1, 1000));
// keeps arena-allocated memory from being really freed, for test purposes
auto kaScope = keepalive_allocator::ActiveScope();
const uint8_t* begin = nullptr;
int size = 0;
{
StringRef rs(randomString);
WipedString ws(rs);
begin = ws.contents().begin();
size = ws.contents().size();
}
for (auto i = 0; i < size; i++) {
ASSERT_EQ(begin[i], 0);
}
}
return Void();
}
namespace unit_tests {
void fillRandom(int& i) {
i = deterministicRandom()->randomInt(0, 10000);
}
void fillRandom(int64_t& i) {
i = deterministicRandom()->randomInt64(0, 1e15);
}
void fillRandom(double& d) {
auto i64 = deterministicRandom()->randomInt64(0, 1e15);
static_assert(sizeof(i64) == sizeof(double));
d = *reinterpret_cast<double*>(&i64);
}
void fillRandom(VectorRef<int>& vi, Arena& arena, int minLen = 1, int maxLen = 100) {
vi.resize(arena, deterministicRandom()->randomInt(minLen, maxLen + 1));
for (auto i = 0; i < vi.size(); i++)
fillRandom(vi[i]);
}
void fillRandom(StringRef& s, Arena& arena, int minLen = 1, int maxLen = 100) {
const auto len = deterministicRandom()->randomInt(minLen, maxLen + 1);
s = StringRef(arena, deterministicRandom()->randomAlphaNumeric(len));
}
void fillRandom(VectorRef<StringRef>& vs,
Arena& arena,
int minLen = 1,
int maxLen = 100,
int minElementLen = 1,
int maxElementLen = 100) {
vs.resize(arena, deterministicRandom()->randomInt(minLen, maxLen + 1));
for (auto i = 0; i < vs.size(); i++)
fillRandom(vs[i], arena, minElementLen, maxElementLen);
}
void fillRandom(WipedString& ws, Arena& arena, int minLen = 1, int maxLen = 1000) {
StringRef s;
fillRandom(s, arena, minLen, maxLen);
ws = WipedString(s);
}
void fillRandom(Optional<WipedString>& ows, Arena& arena, int minLen = 1, int maxLen = 1000) {
WipedString ws;
fillRandom(ws, arena, minLen, maxLen);
ows = ws;
}
void fillRandom(std::vector<WipedString>& vws,
Arena& arena,
int minLen = 1,
int maxLen = 100,
int minElementLen = 10,
int maxElementLen = 1000) {
auto vs = VectorRef<StringRef>();
fillRandom(vs, arena, minLen, maxLen);
for (auto s : vs) {
vws.push_back(WipedString(s));
}
}
// WipedString embedded in the middle of struct, with variable-length objects
struct WS_A {
constexpr static FileIdentifier file_identifier = 1557618;
Arena a;
int64_t i;
double d;
VectorRef<int> vi;
WipedString ws;
StringRef s;
static WS_A makeRandom() {
WS_A o;
fillRandom(o.i);
fillRandom(o.d);
fillRandom(o.vi, o.a, 0, 1000);
fillRandom(o.ws, o.a, 100, 1000);
fillRandom(o.s, o.a, 0, 1000);
return o;
}
static WS_A makeMax() {
WS_A o;
fillRandom(o.i);
fillRandom(o.d);
fillRandom(o.vi, o.a, 9000, 10000);
fillRandom(o.ws, o.a, 9000, 10000);
fillRandom(o.s, o.a, 9000, 10000);
return o;
}
template <class Ar>
void serialize(Ar& ar) {
serializer(ar, i, d, vi, ws, s, a);
}
};
// optional WipedString
struct WS_B {
constexpr static FileIdentifier file_identifier = 7621124;
Arena a;
Optional<WipedString> ows;
int64_t i;
StringRef s;
VectorRef<int> vi;
VectorRef<StringRef> vs;
static WS_B makeRandom() {
WS_B o;
fillRandom(o.ows, o.a, 100, 10000);
fillRandom(o.i);
fillRandom(o.s, o.a, 0, 10000);
fillRandom(o.vi, o.a, 0, 10000);
fillRandom(o.vs, o.a, 1, 10, 100, 1000);
return o;
}
static WS_B makeMax() {
WS_B o;
fillRandom(o.ows, o.a, 9000, 10000);
fillRandom(o.i);
fillRandom(o.s, o.a, 9000, 10000);
fillRandom(o.vi, o.a, 9000, 10000);
fillRandom(o.vs, o.a, 50, 200, 100, 1000);
return o;
}
template <class Ar>
void serialize(Ar& ar) {
serializer(ar, ows, i, s, vi, vs, a);
}
};
// vector of WipedStrings
struct WS_C {
constexpr static FileIdentifier file_identifier = 151112;
Arena a;
int64_t i;
StringRef s;
VectorRef<int> vi;
std::vector<WipedString> vws;
VectorRef<StringRef> vs;
static WS_C makeRandom() {
WS_C o;
fillRandom(o.i);
fillRandom(o.s, o.a, 100, 1000);
fillRandom(o.vi, o.a, 100, 1000);
fillRandom(o.vws, o.a, 10, 100, 100, 1000);
fillRandom(o.vs, o.a, 10, 100, 100, 200);
return o;
}
static WS_C makeMax() {
WS_C o;
fillRandom(o.i);
fillRandom(o.s, o.a, 9000, 10000);
fillRandom(o.vi, o.a, 1000, 10000);
fillRandom(o.vws, o.a, 10, 20, 1000, 10000);
fillRandom(o.vs, o.a, 10, 100, 100, 200);
return o;
}
template <class Ar>
void serialize(Ar& ar) {
serializer(ar, i, s, vi, vws, vs, a);
}
};
template <class GenerateObjectFunc>
void testWipeAfterPacketSerialize(GenerateObjectFunc&& fn) {
// Note that kaScope is not created before object creation.
// This is because ArenaBlock wiping is not a target of this test function
auto obj = fn();
{
// Emulate network packet writing with keepalive allocator active
auto pq = UnsentPacketQueue();
auto kaScope = keepalive_allocator::ActiveScope();
auto pb = pq.getWriteBuffer();
auto pw = PacketWriter(pb, nullptr /* ReliablePacket* */, AssumeVersion(g_network->protocolVersion()));
// Below call serializes the object, marking sensitive areas for wiping in the process,
// because the serialized objects all have WipedString in it.
SerializeSource<decltype(obj)>(obj).serializePacketWriter(pw);
pq.setWriteBuffer(pw.finish());
auto const& wipedSet = keepalive_allocator::getWipedAreaSet();
ASSERT_GT(wipedSet.size(), 0);
for (auto [begin, size] : wipedSet) {
for (auto i = 0; i < size; i++) {
// This weakly verifies that memory is filled with serialized string.
ASSERT(std::isalnum(begin[i]));
}
}
// This should normally deallocate the packet buffers after wiping the sensitive region.
// With keepalive_allocator active, however, the memory is kept alive for post-free inspection.
pq.discardAll();
for (auto [begin, size] : wipedSet) {
for (auto i = 0; i < size; i++)
ASSERT_EQ(begin[i], 0);
}
}
}
} // namespace unit_tests
TEST_CASE("/flow/WipedString/serialize/modest") {
for (auto i = 0; i < 100; i++) {
unit_tests::testWipeAfterPacketSerialize([]() { return unit_tests::WS_A::makeRandom(); });
unit_tests::testWipeAfterPacketSerialize([]() { return unit_tests::WS_B::makeRandom(); });
unit_tests::testWipeAfterPacketSerialize([]() { return unit_tests::WS_C::makeRandom(); });
}
return Void();
}
TEST_CASE("/flow/WipedString/serialize/maximal") {
// Test with larger test objects fewer times to test wiping memory with larger allocation/serialization context
for (auto i = 0; i < 10; i++) {
unit_tests::testWipeAfterPacketSerialize([]() { return unit_tests::WS_A::makeMax(); });
unit_tests::testWipeAfterPacketSerialize([]() { return unit_tests::WS_B::makeMax(); });
unit_tests::testWipeAfterPacketSerialize([]() { return unit_tests::WS_C::makeMax(); });
}
return Void();
}
void forceLinkWipedStringTests() {}