blob: 8650236d1376e85e5c3c1141d27615fc033c7e33 [file] [log] [blame]
// Copyright 2020 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 SecureString.
#include "brillo/secure_string.h"
#include <array>
#include <cstring>
#include <limits>
#include <string>
#include <vector>
#include <base/timer/lap_timer.h>
#include <gtest/gtest.h>
namespace brillo {
static constexpr char str1[] = "abc";
static constexpr char str2[] = "def";
static constexpr char str3[] = "abc";
static_assert(str1 != str3, "The strings should have different addresses");
TEST(SecureClearBytes, SecureClearBytes) {
std::vector<uint8_t> input = {0xFF, 0xFF, 0xFF};
SecureClearBytes(input.data(), input.size());
EXPECT_EQ(input, std::vector<uint8_t>({0x00, 0x00, 0x00}));
}
TEST(SecureClearContainer, SecureClearVector) {
std::vector<uint8_t> input = {0xFF, 0xFF, 0xFF};
SecureClearContainer(input);
EXPECT_EQ(input, std::vector<uint8_t>({0x00, 0x00, 0x00}));
}
TEST(SecureClearContainer, SecureClearVectorUint16) {
std::vector<uint16_t> input = {0xFFFF, 0xFFFF, 0xFFFF};
SecureClearContainer(input);
EXPECT_EQ(input, std::vector<uint16_t>({0x0000, 0x0000, 0x0000}));
}
TEST(SecureClearContainer, SecureClearArray) {
std::array<uint8_t, 3> input = {0xFF, 0xFF, 0xFF};
SecureClearContainer(input);
EXPECT_EQ(input, (std::array<uint8_t, 3>{0x00, 0x00, 0x00}));
}
TEST(SecureClearContainer, SecureClearCStyleArray) {
uint8_t input[3] = {0xFF, 0xFF, 0xFF};
constexpr uint8_t kExpected[3] = {0x00, 0x00, 0x00};
SecureClearContainer(input);
for (int i = 0; i < sizeof(kExpected); i++) {
EXPECT_EQ(input[i], kExpected[i]);
}
}
TEST(SecureClearContainer, SecureClearString) {
std::string input = "abc";
EXPECT_EQ(input.size(), 3);
SecureClearContainer(input);
// string has three NULs (plus terminating NUL)
EXPECT_EQ(input, std::string({0, 0, 0}));
}
TEST(SecureClearObject, SecureClearObject) {
struct TestObj {
int a = 0xFF;
int b = 0xFF;
};
struct TestObj input;
SecureClearObject(input);
TestObj kExpected;
memset(&kExpected, 0, sizeof(kExpected));
EXPECT_EQ(memcmp(&kExpected, &input, sizeof(kExpected)), 0);
}
TEST(SecureClearObject, SecureClearScalarObject) {
int scalar = std::numeric_limits<int>::max();
constexpr int kExpected = 0;
SecureClearObject(scalar);
EXPECT_EQ(scalar, kExpected);
}
TEST(SecureMemcmp, Zero_Size) {
EXPECT_EQ(SecureMemcmp(nullptr, nullptr, 0), 0);
// memcmp has the first two arguments marked as non-null:
// https://sourceware.org/git/?p=glibc.git;a=blob;f=string/string.h;h=b0be00c0f703ae7014fa7c424bfa8767edc500ca;hb=HEAD#l64
// so we need to disable the warning in order to pass nullptr arguments to
// it. Otherwise this will fail to compile.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
EXPECT_EQ(memcmp(nullptr, nullptr, 0), 0);
#pragma clang diagnostic pop
}
TEST(SecureMemcmp, Different) {
// The return value for this differs than memcmp, which will return a
// negative value.
EXPECT_EQ(SecureMemcmp(str1, str2, sizeof(str1)), 1);
EXPECT_LT(std::memcmp(str1, str2, sizeof(str1)), 0);
// memcmp will return a positive value.
EXPECT_EQ(SecureMemcmp(str2, str1, sizeof(str1)), 1);
EXPECT_GT(std::memcmp(str2, str1, sizeof(str1)), 0);
}
TEST(SecureMemcmp, Same) {
EXPECT_EQ(SecureMemcmp(str1, str3, sizeof(str1)), 0);
EXPECT_EQ(std::memcmp(str1, str3, sizeof(str1)), 0);
}
// Even when increasing kAbsoluteError to 0.25, this test flakes too much when
// run on the CQ: https://crrev.com/c/2427403. It should pass if you enable and
// run it locally.
TEST(SecureMemcmp, DISABLED_ConstantTime) {
// Compare two large vectors that are completely different (0x00 and 0xFF)
// The "secure" comparison should take roughly the same amount of time for
// both, as opposed to exiting early on the first byte.
// Caveats: different compilation flags (e.g., around optimizations) may
// significantly change the behavior of the "secure" version, so the fact
// that this test passes in the testing environment does not guarantee that
// it behaves the same in a release build (or different architectures /
// compilers).
constexpr double kAbsoluteError = 0.01;
constexpr size_t kBufSizeBytes = 1000;
std::vector<uint8_t> all_zero(kBufSizeBytes, 0x00);
std::vector<uint8_t> all_one(kBufSizeBytes, 0xFF);
int memcmp_ret = 0;
base::LapTimer timer_different_data(
base::LapTimer::TimerMethod::kUseThreadTicks);
do {
memcmp_ret = SecureMemcmp(all_zero.data(), all_one.data(), all_zero.size());
timer_different_data.NextLap();
} while (!timer_different_data.HasTimeLimitExpired());
EXPECT_EQ(memcmp_ret, 1);
base::LapTimer timer_same_data(base::LapTimer::TimerMethod::kUseThreadTicks);
do {
memcmp_ret =
SecureMemcmp(all_zero.data(), all_zero.data(), all_zero.size());
timer_same_data.NextLap();
} while (!timer_same_data.HasTimeLimitExpired());
EXPECT_EQ(memcmp_ret, 0);
double lap_ratio = static_cast<double>(timer_same_data.NumLaps()) /
static_cast<double>(timer_different_data.NumLaps());
EXPECT_NEAR(lap_ratio, 1.0, kAbsoluteError);
}
} // namespace brillo