blob: 365363c0f73ec4353cb08f1e7a8fc5a7de65c1b4 [file] [log] [blame] [edit]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Unit tests for SecureBlob.
#include "brillo/asan.h"
#include "brillo/secure_allocator.h"
#include "brillo/secure_blob.h"
#include <sys/resource.h>
#include <algorithm>
#include <iterator>
#include <limits>
#include <numeric>
#include <utility>
#include <base/check_op.h>
#include <base/logging.h>
#include <gtest/gtest.h>
namespace brillo {
using std::string;
// Tests BlobToString() and BlobFromString().
TEST(BlobTest, StringConversions) {
const char kTestBytes[] = {'\0', '\x1', 'a', std::numeric_limits<char>::min(),
std::numeric_limits<char>::max()};
const Blob blob(std::begin(kTestBytes), std::end(kTestBytes));
const string obtained_string = BlobToString(blob);
EXPECT_EQ(string(std::begin(kTestBytes), std::end(kTestBytes)),
obtained_string);
const Blob obtained_blob = BlobFromString(obtained_string);
EXPECT_EQ(blob, obtained_blob);
}
// Tests CombineBlobs().
TEST(BlobTest, CombineBlobs) {
const Blob kEmpty;
const Blob kBlob1 = {1};
const Blob kBlob2 = {2};
const Blob kBlob3 = {3};
const Blob kBlob12 = {1, 2};
const Blob kBlob123 = {1, 2, 3};
EXPECT_EQ(kBlob123, CombineBlobs({kBlob12, kBlob3}));
EXPECT_EQ(kBlob123, CombineBlobs({kBlob1, kBlob2, kBlob3}));
EXPECT_EQ(kBlob12, CombineBlobs({kBlob12}));
EXPECT_EQ(kBlob12, CombineBlobs({kEmpty, kBlob1, kEmpty, kBlob2, kEmpty}));
EXPECT_EQ(kEmpty, CombineBlobs({}));
}
class SecureBlobTest : public ::testing::Test {
public:
SecureBlobTest() {}
SecureBlobTest(const SecureBlobTest&) = delete;
SecureBlobTest& operator=(const SecureBlobTest&) = delete;
virtual ~SecureBlobTest() {}
static bool FindBlobInBlob(const brillo::SecureBlob& haystack,
const brillo::SecureBlob& needle) {
auto pos = std::search(haystack.begin(), haystack.end(), needle.begin(),
needle.end());
return (pos != haystack.end());
}
static int FindBlobIndexInBlob(const brillo::SecureBlob& haystack,
const brillo::SecureBlob& needle) {
auto pos = std::search(haystack.begin(), haystack.end(), needle.begin(),
needle.end());
if (pos == haystack.end()) {
return -1;
}
return std::distance(haystack.begin(), pos);
}
};
TEST_F(SecureBlobTest, AllocationSizeTest) {
// Checks that allocating a SecureBlob of a specified size works.
SecureBlob blob(32);
EXPECT_EQ(32, blob.size());
}
TEST_F(SecureBlobTest, ConstructorCountValueTest) {
// Checks that constructing a SecureBlob with |count| copies of |value| works.
SecureBlob blob(32, 'a');
for (size_t i = 0; i < blob.size(); i++) {
EXPECT_EQ('a', blob[i]);
}
}
TEST_F(SecureBlobTest, ConstructorAmbiguousTest) {
// This test will become important once SecureBlob stops inheriting from Blob.
SecureBlob blob(32, 0);
for (size_t i = 0; i < blob.size(); i++) {
EXPECT_EQ(0, blob[i]);
}
}
TEST_F(SecureBlobTest, ConstructorIteratorTest) {
// Checks that constructing a SecureBlob with an iterator works.
unsigned char from_data[32];
std::iota(std::begin(from_data), std::end(from_data), 0);
SecureBlob blob(std::begin(from_data), std::end(from_data));
EXPECT_EQ(sizeof(from_data), blob.size());
for (unsigned int i = 0; i < sizeof(from_data); i++) {
EXPECT_EQ(from_data[i], blob[i]);
}
}
TEST_F(SecureBlobTest, BlobConstructorTest) {
// Check that constructing a SecureBlob from a Blob works.
const std::vector<uint8_t> bytes = {0, 1, 255};
const Blob blob(bytes);
const SecureBlob secure_blob(blob);
EXPECT_EQ(bytes,
std::vector<uint8_t>(secure_blob.begin(), secure_blob.end()));
}
TEST_F(SecureBlobTest, IteratorTest) {
// Checks that SecureBlob::begin(), SecureBlob::end() work.
unsigned char from_data[32];
std::iota(std::begin(from_data), std::end(from_data), 0);
SecureBlob blob(std::begin(from_data), std::end(from_data));
EXPECT_EQ(sizeof(from_data), blob.size());
size_t i = 0;
for (auto it = blob.begin(); it != blob.end(); ++it) {
EXPECT_EQ(from_data[i], *it);
++i;
}
}
TEST_F(SecureBlobTest, AssignTest) {
// Checks that .assign() works.
unsigned char from_data[32];
std::iota(std::begin(from_data), std::end(from_data), 0);
SecureBlob blob;
blob.assign(std::begin(from_data), std::end(from_data));
EXPECT_EQ(sizeof(from_data), blob.size());
size_t i = 0;
for (auto it = blob.begin(); it != blob.end(); ++it) {
EXPECT_EQ(from_data[i], *it);
++i;
}
SecureBlob blob2;
blob2.assign(blob.begin(), blob.end());
EXPECT_EQ(blob, blob2);
}
// Disable ResizeTest with Address Sanitizer.
// https://crbug.com/806013
#ifndef BRILLO_ASAN_BUILD
TEST_F(SecureBlobTest, ResizeTest) {
// Check that resizing a SecureBlob wipes the excess memory. The test assumes
// that resize() down by one will not re-allocate the memory, so the last byte
// will still be part of the SecureBlob's allocation.
size_t length = 1024;
SecureBlob blob(length);
void* original_data = blob.data();
for (size_t i = 0; i < length; i++) {
blob[i] = i;
}
blob.resize(length - 1);
EXPECT_EQ(original_data, blob.data());
EXPECT_EQ(length - 1, blob.size());
EXPECT_EQ(0, blob.data()[length - 1]);
}
#endif
TEST_F(SecureBlobTest, CombineTest) {
SecureBlob blob1(32);
SecureBlob blob2(32);
std::iota(blob1.begin(), blob1.end(), 0);
std::iota(blob2.begin(), blob2.end(), 32);
SecureBlob combined_blob = SecureBlob::Combine(blob1, blob2);
EXPECT_EQ(combined_blob.size(), (blob1.size() + blob2.size()));
EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(combined_blob, blob1));
EXPECT_TRUE(SecureBlobTest::FindBlobInBlob(combined_blob, blob2));
int blob1_index = SecureBlobTest::FindBlobIndexInBlob(combined_blob, blob1);
int blob2_index = SecureBlobTest::FindBlobIndexInBlob(combined_blob, blob2);
EXPECT_EQ(blob1_index, 0);
EXPECT_EQ(blob2_index, 32);
}
TEST_F(SecureBlobTest, BlobToStringTest) {
std::string test_string("Test String");
SecureBlob blob = SecureBlob(test_string.begin(), test_string.end());
EXPECT_EQ(blob.size(), test_string.length());
std::string result_string = blob.to_string();
EXPECT_EQ(test_string.compare(result_string), 0);
}
TEST_F(SecureBlobTest, HexStringToSecureBlob) {
std::string hex_string("112233445566778899aabbccddeeff0f");
SecureBlob blob;
SecureBlob::HexStringToSecureBlob(hex_string, &blob);
EXPECT_EQ(blob.size(), 16u);
EXPECT_EQ(blob[0], 0x11);
EXPECT_EQ(blob[1], 0x22);
EXPECT_EQ(blob[2], 0x33);
EXPECT_EQ(blob[3], 0x44);
EXPECT_EQ(blob[4], 0x55);
EXPECT_EQ(blob[5], 0x66);
EXPECT_EQ(blob[6], 0x77);
EXPECT_EQ(blob[7], 0x88);
EXPECT_EQ(blob[8], 0x99);
EXPECT_EQ(blob[9], 0xaa);
EXPECT_EQ(blob[10], 0xbb);
EXPECT_EQ(blob[11], 0xcc);
EXPECT_EQ(blob[12], 0xdd);
EXPECT_EQ(blob[13], 0xee);
EXPECT_EQ(blob[14], 0xff);
EXPECT_EQ(blob[15], 0x0f);
}
// Override clear_contents() to check whether memory has been cleared.
template <typename T>
class TestSecureAllocator : public SecureAllocator<T> {
public:
using typename SecureAllocator<T>::pointer;
using typename SecureAllocator<T>::size_type;
using typename SecureAllocator<T>::value_type;
TestSecureAllocator() {
erased_count = 0;
}
int GetErasedCount() { return erased_count; }
protected:
static int erased_count;
void clear_contents(pointer p, size_type n) override {
SecureAllocator<T>::clear_contents(p, n);
unsigned char* v = reinterpret_cast<unsigned char*>(p);
for (int i = 0; i < n; i++) {
EXPECT_EQ(v[i], 0);
erased_count++;
}
}
};
template <typename T>
int TestSecureAllocator<T>::erased_count;
TEST(SecureAllocator, ErasureOnDeallocation) {
// Make sure that the contents are cleared on deallocation.
TestSecureAllocator<char> e;
char* test_string_addr = e.allocate(15);
snprintf(test_string_addr, sizeof(test_string_addr), "Test String");
// Deallocate memory; the mock class should check for cleared data.
e.deallocate(test_string_addr, 15);
// The deallocation should have traversed the complete page.
EXPECT_EQ(e.GetErasedCount(), 4096);
}
TEST(SecureAllocator, MultiPageCorrectness) {
// Make sure that the contents are cleared on deallocation.
TestSecureAllocator<uint64_t> e;
// Allocate 4100*8 bytes.
uint64_t* test_array = e.allocate(4100);
// Check if the space was correctly allocated for long long.
for (int i = 0; i < 4100; i++)
test_array[i] = 0xF0F0F0F0F0F0F0F0;
// Deallocate memory; the mock class should check for cleared data.
e.deallocate(test_array, 4100);
// 36864 bytes is the next highest size that is a multiple of the page size.
EXPECT_EQ(e.GetErasedCount(), 36864);
}
TEST(SecureAllocator, SecureVectorIsClearedOnDestruction) {
using TestSecureVector = std::vector<uint8_t, TestSecureAllocator<uint8_t>>;
TestSecureAllocator<uint8_t> allocator;
{
TestSecureVector vector = {{1, 2, 3, 4}};
EXPECT_EQ(vector.capacity(), 4);
}
// The allocator operates on page size blocks, so even though the vector's
// capacity is 4, the actual amount of memory allocated is a page.
// On destruction, each vector element is destroyed through
// SecureAllocator::destroy, which clears the memory. Then the underlying
// buffer (page in this case) is deallocated through
// SecureAllocator::deallocate, which clears the memory.
EXPECT_EQ(allocator.GetErasedCount(), 4 + 4096);
}
TEST(SecureAllocator, IsAlwaysEqualTrait) {
EXPECT_EQ(SecureAllocator<uint8_t>::is_always_equal::value, true);
}
TEST(SecureAllocator, OperatorEqual) {
SecureAllocator<uint8_t> allocator1;
SecureAllocator<uint8_t> allocator2;
EXPECT_TRUE(allocator1 == allocator2);
}
TEST(SecureAllocator, OperatorNotEqual) {
SecureAllocator<uint8_t> allocator1;
SecureAllocator<uint8_t> allocator2;
// SecureAllocators should always be equal.
EXPECT_FALSE(allocator1 != allocator2);
}
class SecureAllocatorMemlockTest : public ::testing::Test {
public:
static constexpr size_t kPageSize = 4096;
SecureAllocatorMemlockTest() {
struct rlimit limit;
EXPECT_EQ(getrlimit(RLIMIT_MEMLOCK, &limit), 0);
orig_limit_ = limit;
// Using a page size value, since the allocator only allocates in
// page-size chunks.
limit.rlim_cur = kPageSize;
EXPECT_LT(limit.rlim_cur, limit.rlim_max);
EXPECT_EQ(setrlimit(RLIMIT_MEMLOCK, &limit), 0);
EXPECT_EQ(getrlimit(RLIMIT_MEMLOCK, &limit), 0);
EXPECT_EQ(limit.rlim_cur, kPageSize);
}
~SecureAllocatorMemlockTest() override {
// Restore original limits.
EXPECT_EQ(setrlimit(RLIMIT_MEMLOCK, &orig_limit_), 0);
}
private:
struct rlimit orig_limit_;
};
TEST_F(SecureAllocatorMemlockTest, MemlockLimitValid) {
SecureAllocator<uint8_t> allocator;
EXPECT_NE(allocator.allocate(kPageSize), nullptr);
}
// Test fails on ASAN build.
#if defined(BRILLO_ASAN_BUILD)
#define MAYBE_MemlockLimitDeathTest DISABLED_MemlockLimitDeathTest
#else
#define MAYBE_MemlockLimitDeathTest MemlockLimitDeathTest
#endif
TEST_F(SecureAllocatorMemlockTest, MAYBE_MemlockLimitDeathTest) {
EXPECT_DEATH(
{
SecureAllocator<uint8_t> allocator;
ASSERT_NE(allocator.allocate(kPageSize), nullptr);
allocator.allocate(1);
},
"It is likely that SecureAllocator has exceeded the RLIMIT_MEMLOCK "
"limit");
}
TEST(SecureVector, CopyAssignment) {
SecureVector vector1 = {1, 2, 3, 4};
SecureVector vector2 = {5, 6, 7, 8};
EXPECT_NE(vector1, vector2);
vector2 = vector1;
EXPECT_EQ(vector1, vector2);
}
TEST(SecureVector, MoveAssignment) {
SecureVector vector1 = {1, 2, 3, 4};
SecureVector vector2 = {5, 6, 7, 8};
EXPECT_NE(vector1, vector2);
vector2 = std::move(vector1);
EXPECT_EQ(vector2, SecureVector({1, 2, 3, 4}));
}
// DeathTests fork a new process and check how it proceeds. Take advantage
// of this and check if the value of SecureString is passed on to
// forked children.
#if GTEST_IS_THREADSAFE
// Check if the contents of the container are zeroed out.
void CheckPropagationOnFork(const brillo::SecureBlob& forked_blob,
const Blob& reference) {
LOG(INFO) << forked_blob.to_string();
for (int i = 0; i < forked_blob.size(); i++) {
CHECK_NE(reference[i], forked_blob[i]);
CHECK_EQ(forked_blob[i], 0);
}
exit(0);
}
TEST(SecureAllocatorDeathTest, ErasureOnFork) {
Blob reference = BlobFromString("Test String");
SecureBlob erasable_blob(reference.begin(), reference.end());
EXPECT_EXIT(CheckPropagationOnFork(erasable_blob, reference),
::testing::ExitedWithCode(0), "");
// In the original process, check the SecureBlob to see if it has not
// changed.
for (int i = 0; i < erasable_blob.size(); i++)
EXPECT_EQ(erasable_blob[i], reference[i]);
}
#endif // GTEST_IS_THREADSAFE
} // namespace brillo