chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,25 @@
add_library(
test_support_lib
STATIC
abi_test.cc
file_test.cc
malloc.cc
test_util.cc
wycheproof_util.cc
)
if (LIBUNWIND_FOUND)
target_compile_options(test_support_lib PRIVATE ${LIBUNWIND_CFLAGS_OTHER})
target_include_directories(test_support_lib PRIVATE ${LIBUNWIND_INCLUDE_DIRS})
target_link_libraries(test_support_lib ${LIBUNWIND_LDFLAGS})
endif()
if(WIN32)
target_link_libraries(test_support_lib dbghelp)
endif()
target_link_libraries(test_support_lib boringssl_gtest crypto)
target_add_awslc_include_paths(TARGET test_support_lib SCOPE PRIVATE)
add_library(boringssl_gtest_main STATIC gtest_main.cc)
target_link_libraries(boringssl_gtest_main boringssl_gtest crypto test_support_lib)

View File

@@ -0,0 +1,785 @@
// Copyright (c) 2018, Google Inc.
// SPDX-License-Identifier: ISC
#include "abi_test.h"
#include <stdarg.h>
#include <stdio.h>
#include <algorithm>
#include <array>
#include <openssl/mem.h>
#include <openssl/rand.h>
#include <openssl/span.h>
#if defined(OPENSSL_X86_64) && defined(SUPPORTS_ABI_TEST)
#if defined(OPENSSL_LINUX) && defined(BORINGSSL_HAVE_LIBUNWIND)
#define SUPPORTS_UNWIND_TEST
#define UNW_LOCAL_ONLY
#include <errno.h>
#include <fcntl.h>
#include <libunwind.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#elif defined(OPENSSL_WINDOWS)
#define SUPPORTS_UNWIND_TEST
OPENSSL_MSVC_PRAGMA(warning(push, 3))
#include <windows.h>
OPENSSL_MSVC_PRAGMA(warning(disable : 4091))
#include <dbghelp.h>
OPENSSL_MSVC_PRAGMA(warning(pop))
#endif
#endif // X86_64 && SUPPORTS_ABI_TEST
// FIPS mode breaks unwind tests. See https://crbug.com/boringssl/289.
#if defined(BORINGSSL_FIPS)
#undef SUPPORTS_UNWIND_TEST
#endif
namespace abi_test {
namespace internal {
static bool g_unwind_tests_enabled = false;
std::string FixVAArgsString(const char *str) {
std::string ret = str;
size_t idx = ret.find(',');
if (idx == std::string::npos) {
return ret + "()";
}
size_t idx2 = idx + 1;
while (idx2 < ret.size() && ret[idx2] == ' ') {
idx2++;
}
while (idx > 0 && ret[idx - 1] == ' ') {
idx--;
}
return ret.substr(0, idx) + "(" + ret.substr(idx2) + ")";
}
#if defined(SUPPORTS_ABI_TEST)
// ForEachMismatch calls |func| for each register where |a| and |b| differ.
template <typename Func>
static void ForEachMismatch(const CallerState &a, const CallerState &b,
const Func &func) {
#define CALLER_STATE_REGISTER(type, name) \
if (a.name != b.name) { \
func(#name); \
}
LOOP_CALLER_STATE_REGISTERS()
#undef CALLER_STATE_REGISTER
}
#endif // SUPPORTS_ABI_TEST
#if defined(SUPPORTS_UNWIND_TEST)
// We test unwind metadata by running the function under test with the trap flag
// set. This results in |SIGTRAP| and |EXCEPTION_SINGLE_STEP| on Linux and
// Windows, respectively. We hande these and verify libunwind or the Windows
// unwind APIs unwind successfully.
// IsAncestorStackFrame returns true if |a_sp| is an ancestor stack frame of
// |b_sp|.
static bool IsAncestorStackFrame(crypto_word_t a_sp, crypto_word_t b_sp) {
#if defined(OPENSSL_X86_64)
// The stack grows down, so ancestor stack frames have higher addresses.
return a_sp > b_sp;
#else
#error "unknown architecture"
#endif
}
// Implement some string formatting utilties. Ideally we would use |snprintf|,
// but this is called in a signal handler and |snprintf| is not async-signal-
// safe.
#if !defined(OPENSSL_WINDOWS)
static std::array<char, DECIMAL_SIZE(crypto_word_t) + 1> WordToDecimal(
crypto_word_t v) {
std::array<char, DECIMAL_SIZE(crypto_word_t) + 1> ret;
size_t len = 0;
do {
ret[len++] = '0' + v % 10;
v /= 10;
} while (v != 0);
for (size_t i = 0; i < len / 2; i++) {
std::swap(ret[i], ret[len - 1 - i]);
}
ret[len] = '\0';
return ret;
}
#endif // !OPENSSL_WINDOWS
static std::array<char, sizeof(crypto_word_t) * 2 + 1> WordToHex(
crypto_word_t v) {
static const char kHex[] = "0123456789abcdef";
std::array<char, sizeof(crypto_word_t) * 2 + 1> ret;
for (size_t i = sizeof(crypto_word_t) - 1; i < sizeof(crypto_word_t); i--) {
uint8_t b = v & 0xff;
v >>= 8;
ret[i * 2] = kHex[b >> 4];
ret[i * 2 + 1] = kHex[b & 0xf];
}
ret[sizeof(crypto_word_t) * 2] = '\0';
return ret;
}
static void StrCatSignalSafeImpl(bssl::Span<char> out) {}
template <typename... Args>
static void StrCatSignalSafeImpl(bssl::Span<char> out, const char *str,
Args... args) {
OPENSSL_strlcat(out.data(), str, out.size());
StrCatSignalSafeImpl(out, args...);
}
template <typename... Args>
static void StrCatSignalSafe(bssl::Span<char> out, Args... args) {
if (out.empty()) {
return;
}
out[0] = '\0';
StrCatSignalSafeImpl(out, args...);
}
template <typename... Args>
[[noreturn]] static void FatalError(Args... args) {
// We cannot use |snprintf| here because it is not async-signal-safe.
char buf[512];
StrCatSignalSafe(buf, args..., "\n");
#if defined(OPENSSL_WINDOWS)
HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
if (stderr_handle != INVALID_HANDLE_VALUE) {
DWORD unused;
WriteFile(stderr_handle, buf, strlen(buf), &unused, nullptr);
}
#else
OPENSSL_UNUSED ssize_t unused_ret =
write(STDERR_FILENO, buf, strlen(buf));
#endif
abort();
}
class UnwindStatus {
public:
UnwindStatus() : err_(nullptr) {}
explicit UnwindStatus(const char *err) : err_(err) {}
bool ok() const { return err_ == nullptr; }
const char *Error() const { return err_; }
private:
const char *err_;
};
template<typename T>
class UnwindStatusOr {
public:
UnwindStatusOr(UnwindStatus status) : status_(status) {
assert(!status_.ok());
}
UnwindStatusOr(const T &value) : status_(UnwindStatus()), value_(value) {}
bool ok() const { return status_.ok(); }
const char *Error() const { return status_.Error(); }
const T &ValueOrDie(const char *msg = "Unexpected error") const {
if (!ok()) {
FatalError(msg, ": ", Error());
}
return value_;
}
private:
UnwindStatus status_;
T value_;
};
// UnwindCursor abstracts between libunwind and Windows unwind APIs. It is
// async-signal-safe.
#if defined(OPENSSL_WINDOWS)
class UnwindCursor {
public:
explicit UnwindCursor(const CONTEXT &ctx) : ctx_(ctx) {
starting_ip_ = ctx_.Rip;
}
crypto_word_t starting_ip() const { return starting_ip_; }
// Step unwinds the cursor by one frame. On success, it returns whether there
// were more frames to unwind.
UnwindStatusOr<bool> Step() {
bool is_top = is_top_;
is_top_ = false;
DWORD64 image_base;
RUNTIME_FUNCTION *entry =
RtlLookupFunctionEntry(ctx_.Rip, &image_base, nullptr);
if (entry == nullptr) {
// This is a leaf function. Leaf functions do not touch stack or
// callee-saved registers, so they may be unwound by simulating a ret.
if (!is_top) {
return UnwindStatus("leaf function found below the top frame");
}
memcpy(&ctx_.Rip, reinterpret_cast<const void *>(ctx_.Rsp),
sizeof(ctx_.Rip));
ctx_.Rsp += 8;
return true;
}
// This is a frame function. Call into the Windows unwinder.
void *handler_data;
DWORD64 establisher_frame;
RtlVirtualUnwind(UNW_FLAG_NHANDLER, image_base, ctx_.Rip, entry, &ctx_,
&handler_data, &establisher_frame, nullptr);
return ctx_.Rip != 0;
}
// GetIP returns the instruction pointer at the current frame.
UnwindStatusOr<crypto_word_t> GetIP() { return ctx_.Rip; }
// GetSP returns the stack pointer at the current frame.
UnwindStatusOr<crypto_word_t> GetSP() { return ctx_.Rsp; }
// GetCallerState returns the callee-saved registers at the current frame.
UnwindStatusOr<CallerState> GetCallerState() {
CallerState state;
state.rbx = ctx_.Rbx;
state.rbp = ctx_.Rbp;
state.rdi = ctx_.Rdi;
state.rsi = ctx_.Rsi;
state.r12 = ctx_.R12;
state.r13 = ctx_.R13;
state.r14 = ctx_.R14;
state.r15 = ctx_.R15;
memcpy(&state.xmm6, &ctx_.Xmm6, sizeof(Reg128));
memcpy(&state.xmm7, &ctx_.Xmm7, sizeof(Reg128));
memcpy(&state.xmm8, &ctx_.Xmm8, sizeof(Reg128));
memcpy(&state.xmm9, &ctx_.Xmm9, sizeof(Reg128));
memcpy(&state.xmm10, &ctx_.Xmm10, sizeof(Reg128));
memcpy(&state.xmm11, &ctx_.Xmm11, sizeof(Reg128));
memcpy(&state.xmm12, &ctx_.Xmm12, sizeof(Reg128));
memcpy(&state.xmm13, &ctx_.Xmm13, sizeof(Reg128));
memcpy(&state.xmm14, &ctx_.Xmm14, sizeof(Reg128));
memcpy(&state.xmm15, &ctx_.Xmm15, sizeof(Reg128));
return state;
}
// ToString returns a human-readable representation of the address the cursor
// started at.
const char *ToString() {
StrCatSignalSafe(starting_ip_buf_, "0x", WordToHex(starting_ip_).data());
return starting_ip_buf_;
}
private:
CONTEXT ctx_;
crypto_word_t starting_ip_;
char starting_ip_buf_[64];
bool is_top_ = true;
};
#else // !OPENSSL_WINDOWS
class UnwindCursor {
public:
explicit UnwindCursor(unw_context_t *ctx) : ctx_(ctx) {
int ret = InitAtSignalFrame(&cursor_);
if (ret < 0) {
FatalError("Error getting unwind context: ", unw_strerror(ret));
}
starting_ip_ = GetIP().ValueOrDie("Error getting instruction pointer");
}
// Step unwinds the cursor by one frame. On success, it returns whether there
// were more frames to unwind.
UnwindStatusOr<bool> Step() {
int ret = unw_step(&cursor_);
if (ret < 0) {
return UNWError(ret);
}
return ret != 0;
}
// GetIP returns the instruction pointer at the current frame.
UnwindStatusOr<crypto_word_t> GetIP() {
crypto_word_t ip;
int ret = GetReg(&ip, UNW_REG_IP);
if (ret < 0) {
return UNWError(ret);
}
return ip;
}
// GetSP returns the stack pointer at the current frame.
UnwindStatusOr<crypto_word_t> GetSP() {
crypto_word_t sp;
int ret = GetReg(&sp, UNW_REG_SP);
if (ret < 0) {
return UNWError(ret);
}
return sp;
}
// GetCallerState returns the callee-saved registers at the current frame.
UnwindStatusOr<CallerState> GetCallerState() {
CallerState state;
int ret = 0;
#if defined(OPENSSL_X86_64)
ret = ret < 0 ? ret : GetReg(&state.rbx, UNW_X86_64_RBX);
ret = ret < 0 ? ret : GetReg(&state.rbp, UNW_X86_64_RBP);
ret = ret < 0 ? ret : GetReg(&state.r12, UNW_X86_64_R12);
ret = ret < 0 ? ret : GetReg(&state.r13, UNW_X86_64_R13);
ret = ret < 0 ? ret : GetReg(&state.r14, UNW_X86_64_R14);
ret = ret < 0 ? ret : GetReg(&state.r15, UNW_X86_64_R15);
#else
#error "unknown architecture"
#endif
if (ret < 0) {
return UNWError(ret);
}
return state;
}
// ToString returns a human-readable representation of the address the cursor
// started at, using debug information if available.
const char *ToString() {
// Use a new cursor. |cursor_| has already been unwound, and
// |unw_get_proc_name| is slow so we do not sample it unconditionally in the
// constructor.
unw_cursor_t cursor;
unw_word_t off;
if (InitAtSignalFrame(&cursor) != 0 ||
unw_get_proc_name(&cursor, starting_ip_buf_, sizeof(starting_ip_buf_),
&off) != 0) {
StrCatSignalSafe(starting_ip_buf_, "0x", WordToHex(starting_ip_).data());
return starting_ip_buf_;
}
size_t len = strlen(starting_ip_buf_);
// Print the offset in decimal, to match gdb's disassembly output and ease
// debugging.
StrCatSignalSafe(bssl::Span<char>(starting_ip_buf_).subspan(len), "+",
WordToDecimal(off).data(), " (0x",
WordToHex(starting_ip_).data(), ")");
return starting_ip_buf_;
}
private:
static UnwindStatus UNWError(int ret) {
assert(ret < 0);
const char *msg = unw_strerror(ret);
return UnwindStatus(msg == nullptr ? "unknown error" : msg);
}
int InitAtSignalFrame(unw_cursor_t *cursor) {
// Work around a bug in libunwind which breaks rax and rdx recovery. This
// breaks functions which temporarily use rax as the CFA register. See
// https://git.savannah.gnu.org/gitweb/?p=libunwind.git;a=commit;h=819bf51bbd2da462c2ec3401e8ac9153b6e725e3
OPENSSL_memset(cursor, 0, sizeof(*cursor));
int ret = unw_init_local(cursor, ctx_);
if (ret < 0) {
return ret;
}
for (;;) {
ret = unw_is_signal_frame(cursor);
if (ret < 0) {
return ret;
}
if (ret != 0) {
return 0; // Found the signal frame.
}
ret = unw_step(cursor);
if (ret < 0) {
return ret;
}
}
}
int GetReg(crypto_word_t *out, unw_regnum_t reg) {
unw_word_t val;
int ret = unw_get_reg(&cursor_, reg, &val);
if (ret >= 0) {
static_assert(sizeof(crypto_word_t) == sizeof(unw_word_t),
"crypto_word_t and unw_word_t are inconsistent");
*out = val;
}
return ret;
}
unw_context_t *ctx_;
unw_cursor_t cursor_;
crypto_word_t starting_ip_;
char starting_ip_buf_[64];
};
#endif // OPENSSL_WINDOWS
// g_in_trampoline is true if we are in an instrumented |abi_test_trampoline|
// call, in the region that triggers |SIGTRAP|.
static bool g_in_trampoline = false;
// g_unwind_function_done, if |g_in_trampoline| is true, is whether the function
// under test has returned. It is undefined otherwise.
static bool g_unwind_function_done;
// g_trampoline_state, during an unwind-enabled ABI test, is the state the
// function under test must preserve. It is undefined otherwise.
static CallerState g_trampoline_state;
// g_trampoline_sp, if |g_in_trampoline| is true, is the stack pointer of the
// trampoline frame. It is undefined otherwise.
static crypto_word_t g_trampoline_sp;
// kMaxUnwindErrors is the maximum number of unwind errors reported per
// function. If a function's unwind tables are wrong, we are otherwise likely to
// repeat the same error at multiple addresses.
static constexpr size_t kMaxUnwindErrors = 10;
// Errors are saved in a signal handler. We use a static buffer to avoid
// allocation.
static size_t g_num_unwind_errors = 0;
struct UnwindError {
#if defined(OPENSSL_WINDOWS)
crypto_word_t ip;
#endif
char str[512];
};
static UnwindError g_unwind_errors[kMaxUnwindErrors];
template <typename... Args>
static void AddUnwindError(UnwindCursor *cursor, Args... args) {
if (g_num_unwind_errors >= kMaxUnwindErrors) {
return;
}
#if defined(OPENSSL_WINDOWS)
// Windows symbol functions should not be called when handling an
// exception. Stash the instruction pointer, to be symbolized later.
g_unwind_errors[g_num_unwind_errors].ip = cursor->starting_ip();
StrCatSignalSafe(g_unwind_errors[g_num_unwind_errors].str, args...);
#else
StrCatSignalSafe(g_unwind_errors[g_num_unwind_errors].str,
"unwinding at ", cursor->ToString(), ": ", args...);
#endif
g_num_unwind_errors++;
}
static void CheckUnwind(UnwindCursor *cursor) {
const crypto_word_t kStartAddress =
reinterpret_cast<crypto_word_t>(&abi_test_unwind_start);
const crypto_word_t kReturnAddress =
reinterpret_cast<crypto_word_t>(&abi_test_unwind_return);
const crypto_word_t kStopAddress =
reinterpret_cast<crypto_word_t>(&abi_test_unwind_stop);
crypto_word_t sp = cursor->GetSP().ValueOrDie("Error getting stack pointer");
crypto_word_t ip =
cursor->GetIP().ValueOrDie("Error getting instruction pointer");
if (!g_in_trampoline) {
if (ip != kStartAddress) {
FatalError("Unexpected SIGTRAP at ", cursor->ToString());
}
// Save the current state and begin.
g_in_trampoline = true;
g_unwind_function_done = false;
g_trampoline_sp = sp;
} else {
if (sp == g_trampoline_sp || g_unwind_function_done) {
// |g_unwind_function_done| should imply |sp| is |g_trampoline_sp|, but
// clearing the trap flag in x86 briefly displaces the stack pointer.
//
// Also note we check both |ip| and |sp| below, in case the function under
// test is also |abi_test_trampoline|.
if (ip == kReturnAddress && sp == g_trampoline_sp) {
g_unwind_function_done = true;
}
if (ip == kStopAddress && sp == g_trampoline_sp) {
// |SIGTRAP| is fatal again.
g_in_trampoline = false;
}
} else if (IsAncestorStackFrame(sp, g_trampoline_sp)) {
// This should never happen. We went past |g_trampoline_sp| without
// stopping at |kStopAddress|.
AddUnwindError(cursor, "stack frame is before caller");
g_in_trampoline = false;
} else if (g_num_unwind_errors < kMaxUnwindErrors) {
for (;;) {
UnwindStatusOr<bool> step_ret = cursor->Step();
if (!step_ret.ok()) {
AddUnwindError(cursor, "error unwinding: ", step_ret.Error());
break;
}
// |Step| returns whether there was a frame to unwind.
if (!step_ret.ValueOrDie()) {
AddUnwindError(cursor, "could not unwind to starting frame");
break;
}
UnwindStatusOr<crypto_word_t> cur_sp = cursor->GetSP();
if (!cur_sp.ok()) {
AddUnwindError(cursor,
"error recovering stack pointer: ", cur_sp.Error());
break;
}
if (IsAncestorStackFrame(cur_sp.ValueOrDie(), g_trampoline_sp)) {
AddUnwindError(cursor, "unwound past starting frame");
break;
}
if (cur_sp.ValueOrDie() == g_trampoline_sp) {
// We found the parent frame. Check the return address.
UnwindStatusOr<crypto_word_t> cur_ip = cursor->GetIP();
if (!cur_ip.ok()) {
AddUnwindError(cursor,
"error recovering return address: ", cur_ip.Error());
} else if (cur_ip.ValueOrDie() != kReturnAddress) {
AddUnwindError(cursor, "wrong return address");
}
// Check the remaining registers.
UnwindStatusOr<CallerState> state = cursor->GetCallerState();
if (!state.ok()) {
AddUnwindError(cursor,
"error recovering registers: ", state.Error());
} else {
ForEachMismatch(state.ValueOrDie(), g_trampoline_state,
[&](const char *reg) {
AddUnwindError(cursor, reg, " was not recovered");
});
}
break;
}
}
}
}
}
// ReadUnwindResult adds the results of the most recent unwind test to |out|.
static void ReadUnwindResult(Result *out) {
for (size_t i = 0; i < g_num_unwind_errors; i++) {
#if defined(OPENSSL_WINDOWS)
const crypto_word_t ip = g_unwind_errors[i].ip;
char buf[256];
DWORD64 displacement;
struct {
SYMBOL_INFO info;
char name_buf[128];
} symbol;
memset(&symbol, 0, sizeof(symbol));
symbol.info.SizeOfStruct = sizeof(symbol.info);
symbol.info.MaxNameLen = sizeof(symbol.name_buf);
if (SymFromAddr(GetCurrentProcess(), ip, &displacement, &symbol.info)) {
snprintf(buf, sizeof(buf), "unwinding at %s+%llu (0x%s): %s",
symbol.info.Name, displacement, WordToHex(ip).data(),
g_unwind_errors[i].str);
} else {
snprintf(buf, sizeof(buf), "unwinding at 0x%s: %s",
WordToHex(ip).data(), g_unwind_errors[i].str);
}
out->errors.emplace_back(buf);
#else
out->errors.emplace_back(g_unwind_errors[i].str);
#endif
}
if (g_num_unwind_errors == kMaxUnwindErrors) {
out->errors.emplace_back("(additional errors omitted)");
}
g_num_unwind_errors = 0;
}
#if defined(OPENSSL_WINDOWS)
static DWORD g_main_thread;
static long ExceptionHandler(EXCEPTION_POINTERS *info) {
if (info->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP ||
GetCurrentThreadId() != g_main_thread) {
return EXCEPTION_CONTINUE_SEARCH;
}
UnwindCursor cursor(*info->ContextRecord);
CheckUnwind(&cursor);
if (g_in_trampoline) {
// Windows clears the trap flag, so we must restore it.
info->ContextRecord->EFlags |= 0x100;
}
return EXCEPTION_CONTINUE_EXECUTION;
}
static void EnableUnwindTestsImpl() {
if (IsDebuggerPresent()) {
// Unwind tests drive logic via |EXCEPTION_SINGLE_STEP|, which conflicts with
// debuggers.
fprintf(stderr, "Debugger detected. Disabling unwind tests.\n");
return;
}
g_main_thread = GetCurrentThreadId();
SymSetOptions(SYMOPT_DEFERRED_LOADS);
if (!SymInitialize(GetCurrentProcess(), nullptr, TRUE)) {
fprintf(stderr, "Could not initialize symbols.\n");
}
if (AddVectoredExceptionHandler(0, ExceptionHandler) == nullptr) {
fprintf(stderr, "Error installing exception handler.\n");
abort();
}
g_unwind_tests_enabled = true;
}
#else // !OPENSSL_WINDOWS
// HandleEINTR runs |func| and returns the result, retrying the operation on
// |EINTR|.
template <typename Func>
static auto HandleEINTR(const Func &func) -> decltype(func()) {
decltype(func()) ret;
do {
ret = func();
} while (ret < 0 && errno == EINTR);
return ret;
}
static bool ReadFileToString(std::string *out, const char *path) {
out->clear();
int fd = HandleEINTR([&] { return open(path, O_RDONLY); });
if (fd < 0) {
return false;
}
for (;;) {
char buf[1024];
ssize_t ret = HandleEINTR([&] { return read(fd, buf, sizeof(buf)); });
if (ret < 0) {
close(fd);
return false;
}
if (ret == 0) {
close(fd);
return true;
}
out->append(buf, static_cast<size_t>(ret));
}
}
static bool IsBeingDebugged() {
std::string status;
if (!ReadFileToString(&status, "/proc/self/status")) {
perror("error reading /proc/self/status");
return false;
}
std::string key = "\nTracerPid:\t";
size_t idx = status.find(key);
if (idx == std::string::npos) {
return false;
}
idx += key.size();
return idx < status.size() && status[idx] != '0';
}
static pthread_t g_main_thread;
static void TrapHandler(int sig) {
// Note this is a signal handler, so only async-signal-safe functions may be
// used here. See signal-safety(7). libunwind promises local unwind is
// async-signal-safe.
// |pthread_equal| is not listed as async-signal-safe, but this is clearly an
// oversight.
if (!pthread_equal(g_main_thread, pthread_self())) {
FatalError("SIGTRAP on background thread");
}
unw_context_t ctx;
int ret = unw_getcontext(&ctx);
if (ret < 0) {
FatalError("Error getting unwind context: ", unw_strerror(ret));
}
UnwindCursor cursor(&ctx);
CheckUnwind(&cursor);
}
static void EnableUnwindTestsImpl() {
if (IsBeingDebugged()) {
// Unwind tests drive logic via |SIGTRAP|, which conflicts with debuggers.
fprintf(stderr, "Debugger detected. Disabling unwind tests.\n");
return;
}
g_main_thread = pthread_self();
struct sigaction trap_action;
OPENSSL_memset(&trap_action, 0, sizeof(trap_action));
sigemptyset(&trap_action.sa_mask);
trap_action.sa_handler = TrapHandler;
if (sigaction(SIGTRAP, &trap_action, NULL) != 0) {
perror("sigaction");
abort();
}
g_unwind_tests_enabled = true;
}
#endif // OPENSSL_WINDOWS
#else // !SUPPORTS_UNWIND_TEST
#if defined(SUPPORTS_ABI_TEST)
static void ReadUnwindResult(Result *) {}
#endif
static void EnableUnwindTestsImpl() {}
#endif // SUPPORTS_UNWIND_TEST
#if defined(SUPPORTS_ABI_TEST)
crypto_word_t RunTrampoline(Result *out, crypto_word_t func,
const crypto_word_t *argv, size_t argc,
bool unwind) {
CallerState state;
RAND_bytes(reinterpret_cast<uint8_t *>(&state), sizeof(state));
unwind &= g_unwind_tests_enabled;
#if defined(SUPPORTS_UNWIND_TEST)
if (unwind) {
// Save the caller state for the unwind tester to check for.
g_trampoline_state = state;
}
#endif
CallerState state2 = state;
crypto_word_t ret = abi_test_trampoline(func, &state2, argv, argc, unwind);
#if defined(OPENSSL_X86_64) || defined(OPENSSL_X86)
// Query and clear the direction flag early, so negative tests do not
// interfere with |malloc|.
bool direction_flag = abi_test_get_and_clear_direction_flag();
#endif // OPENSSL_X86_64 || OPENSSL_X86
*out = Result();
ForEachMismatch(state, state2, [&](const char *reg) {
out->errors.push_back(std::string(reg) + " was not restored after return");
});
#if defined(OPENSSL_X86_64) || defined(OPENSSL_X86)
// Linux and Windows ABIs for x86 require the direction flag be cleared on
// return. (Some OpenSSL assembly preserves it, which is stronger, but we only
// require what is specified by the ABI so |CHECK_ABI| works with C compiler
// output.)
if (direction_flag) {
out->errors.emplace_back("Direction flag set after return");
}
#endif // OPENSSL_X86_64 || OPENSSL_X86
if (unwind) {
ReadUnwindResult(out);
}
return ret;
}
#endif // SUPPORTS_ABI_TEST
} // namespace internal
void EnableUnwindTests() { internal::EnableUnwindTestsImpl(); }
bool UnwindTestsEnabled() { return internal::g_unwind_tests_enabled; }
} // namespace abi_test

View File

@@ -0,0 +1,548 @@
// Copyright (c) 2018, Google Inc.
// SPDX-License-Identifier: ISC
#ifndef OPENSSL_HEADER_ABI_TEST_H
#define OPENSSL_HEADER_ABI_TEST_H
#include <gtest/gtest.h>
#include <string>
#include <type_traits>
#include <vector>
#include <openssl/base.h>
#include "../internal.h"
// abi_test provides routines for verifying that functions satisfy platform ABI
// requirements.
namespace abi_test {
// Result stores the result of an ABI test.
struct Result {
bool ok() const { return errors.empty(); }
std::vector<std::string> errors;
};
namespace internal {
// DeductionGuard wraps |T| in a template, so that template argument deduction
// does not apply to it. This may be used to force C++ to deduce template
// arguments from another parameter.
template <typename T>
struct DeductionGuard {
using Type = T;
};
// Reg128 contains storage space for a 128-bit register.
struct alignas(16) Reg128 {
bool operator==(const Reg128 &x) const { return x.lo == lo && x.hi == hi; }
bool operator!=(const Reg128 &x) const { return !((*this) == x); }
uint64_t lo, hi;
};
// LOOP_CALLER_STATE_REGISTERS is a macro that iterates over all registers the
// callee is expected to save for the caller, with the exception of the stack
// pointer. The stack pointer is tested implicitly by the function successfully
// returning at all.
#if defined(OPENSSL_X86_64)
// References:
// SysV64: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
// Win64: https://docs.microsoft.com/en-us/cpp/build/x64-software-conventions?view=vs-2017#register-usage
#if defined(OPENSSL_WINDOWS)
#define LOOP_CALLER_STATE_REGISTERS() \
CALLER_STATE_REGISTER(uint64_t, rbx) \
CALLER_STATE_REGISTER(uint64_t, rbp) \
CALLER_STATE_REGISTER(uint64_t, rdi) \
CALLER_STATE_REGISTER(uint64_t, rsi) \
CALLER_STATE_REGISTER(uint64_t, r12) \
CALLER_STATE_REGISTER(uint64_t, r13) \
CALLER_STATE_REGISTER(uint64_t, r14) \
CALLER_STATE_REGISTER(uint64_t, r15) \
CALLER_STATE_REGISTER(Reg128, xmm6) \
CALLER_STATE_REGISTER(Reg128, xmm7) \
CALLER_STATE_REGISTER(Reg128, xmm8) \
CALLER_STATE_REGISTER(Reg128, xmm9) \
CALLER_STATE_REGISTER(Reg128, xmm10) \
CALLER_STATE_REGISTER(Reg128, xmm11) \
CALLER_STATE_REGISTER(Reg128, xmm12) \
CALLER_STATE_REGISTER(Reg128, xmm13) \
CALLER_STATE_REGISTER(Reg128, xmm14) \
CALLER_STATE_REGISTER(Reg128, xmm15)
#else
#define LOOP_CALLER_STATE_REGISTERS() \
CALLER_STATE_REGISTER(uint64_t, rbx) \
CALLER_STATE_REGISTER(uint64_t, rbp) \
CALLER_STATE_REGISTER(uint64_t, r12) \
CALLER_STATE_REGISTER(uint64_t, r13) \
CALLER_STATE_REGISTER(uint64_t, r14) \
CALLER_STATE_REGISTER(uint64_t, r15)
#endif // OPENSSL_WINDOWS
#elif defined(OPENSSL_X86)
// References:
// SysV32: https://uclibc.org/docs/psABI-i386.pdf and
// Win32: https://docs.microsoft.com/en-us/cpp/cpp/argument-passing-and-naming-conventions?view=vs-2017
#define LOOP_CALLER_STATE_REGISTERS() \
CALLER_STATE_REGISTER(uint32_t, esi) \
CALLER_STATE_REGISTER(uint32_t, edi) \
CALLER_STATE_REGISTER(uint32_t, ebx) \
CALLER_STATE_REGISTER(uint32_t, ebp)
#elif defined(OPENSSL_ARM)
// References:
// AAPCS: https://developer.arm.com/docs/ihi0042/latest
// iOS32: https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv6FunctionCallingConventions.html
// Linux: http://sourcery.mentor.com/sgpp/lite/arm/portal/kbattach142/arm_gnu_linux_%20abi.pdf
//
// ARM specifies a common calling convention, except r9 is left to the platform.
// Linux treats r9 as callee-saved, while iOS 3+ treats it as caller-saved. Most
// of our assembly treats it as callee-saved to be uniform, but we match the
// platform to avoid false positives when testing compiler-generated output.
#define LOOP_CALLER_STATE_REGISTERS_PRE_R9() \
CALLER_STATE_REGISTER(uint64_t, d8) \
CALLER_STATE_REGISTER(uint64_t, d9) \
CALLER_STATE_REGISTER(uint64_t, d10) \
CALLER_STATE_REGISTER(uint64_t, d11) \
CALLER_STATE_REGISTER(uint64_t, d12) \
CALLER_STATE_REGISTER(uint64_t, d13) \
CALLER_STATE_REGISTER(uint64_t, d14) \
CALLER_STATE_REGISTER(uint64_t, d15) \
CALLER_STATE_REGISTER(uint32_t, r4) \
CALLER_STATE_REGISTER(uint32_t, r5) \
CALLER_STATE_REGISTER(uint32_t, r6) \
CALLER_STATE_REGISTER(uint32_t, r7) \
CALLER_STATE_REGISTER(uint32_t, r8)
#define LOOP_CALLER_STATE_REGISTERS_POST_R9() \
CALLER_STATE_REGISTER(uint32_t, r10) \
CALLER_STATE_REGISTER(uint32_t, r11)
#if defined(OPENSSL_APPLE)
#define LOOP_CALLER_STATE_REGISTERS() \
LOOP_CALLER_STATE_REGISTERS_PRE_R9() \
LOOP_CALLER_STATE_REGISTERS_POST_R9()
#else // !OPENSSL_APPLE
#define LOOP_CALLER_STATE_REGISTERS() \
LOOP_CALLER_STATE_REGISTERS_PRE_R9() \
CALLER_STATE_REGISTER(uint32_t, r9) \
LOOP_CALLER_STATE_REGISTERS_POST_R9()
#endif // OPENSSL_APPLE
#elif defined(OPENSSL_AARCH64)
// References:
// AAPCS64: https://developer.arm.com/docs/ihi0055/latest
// iOS64: https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html
//
// In aarch64, r18 (accessed as w18 or x18 in a 64-bit context) is the platform
// register. iOS says user code may not touch it. We found no clear reference
// for Linux. The iOS behavior implies portable assembly cannot use it, and
// aarch64 has many registers. Thus this framework ignores register's existence.
// We test r18 violations in arm-xlate.pl.
#define LOOP_CALLER_STATE_REGISTERS() \
/* Per AAPCS64, section 5.1.2, only the bottom 64 bits of v8-v15 */ \
/* are preserved. These are accessed as dN. */ \
CALLER_STATE_REGISTER(uint64_t, d8) \
CALLER_STATE_REGISTER(uint64_t, d9) \
CALLER_STATE_REGISTER(uint64_t, d10) \
CALLER_STATE_REGISTER(uint64_t, d11) \
CALLER_STATE_REGISTER(uint64_t, d12) \
CALLER_STATE_REGISTER(uint64_t, d13) \
CALLER_STATE_REGISTER(uint64_t, d14) \
CALLER_STATE_REGISTER(uint64_t, d15) \
/* For consistency with dN, use the 64-bit name xN, rather than */ \
/* the generic rN. */ \
CALLER_STATE_REGISTER(uint64_t, x19) \
CALLER_STATE_REGISTER(uint64_t, x20) \
CALLER_STATE_REGISTER(uint64_t, x21) \
CALLER_STATE_REGISTER(uint64_t, x22) \
CALLER_STATE_REGISTER(uint64_t, x23) \
CALLER_STATE_REGISTER(uint64_t, x24) \
CALLER_STATE_REGISTER(uint64_t, x25) \
CALLER_STATE_REGISTER(uint64_t, x26) \
CALLER_STATE_REGISTER(uint64_t, x27) \
CALLER_STATE_REGISTER(uint64_t, x28) \
CALLER_STATE_REGISTER(uint64_t, x29)
#elif defined(OPENSSL_PPC64LE)
// CRReg only compares the CR2-CR4 bits of a CR register.
struct CRReg {
uint32_t masked() const { return value & 0x00fff000; }
bool operator==(CRReg r) const { return masked() == r.masked(); }
bool operator!=(CRReg r) const { return masked() != r.masked(); }
uint32_t value;
};
// References:
// ELFv2: http://openpowerfoundation.org/wp-content/uploads/resources/leabi/leabi-20170510.pdf
//
// Note vector and floating-point registers on POWER have two different names.
// Originally, there were 32 floating-point registers and 32 vector registers,
// labelled f0-f31 and v0-v31 respectively. Later, VSX (Vector Scalar Extension)
// unified them into 64 registers vs0-vs63. f0-f31 map to the lower halves of
// vs0-vs31. v0-v31 map to vs32-vs63. The ABI was defined in terms of pre-VSX
// names, so we use those names here. In particular, f14-f31 are
// callee-saved, but the upper halves of vs14-vs31 are not.
#define LOOP_CALLER_STATE_REGISTERS() \
CALLER_STATE_REGISTER(Reg128, v20) \
CALLER_STATE_REGISTER(Reg128, v21) \
CALLER_STATE_REGISTER(Reg128, v22) \
CALLER_STATE_REGISTER(Reg128, v23) \
CALLER_STATE_REGISTER(Reg128, v24) \
CALLER_STATE_REGISTER(Reg128, v25) \
CALLER_STATE_REGISTER(Reg128, v26) \
CALLER_STATE_REGISTER(Reg128, v27) \
CALLER_STATE_REGISTER(Reg128, v28) \
CALLER_STATE_REGISTER(Reg128, v29) \
CALLER_STATE_REGISTER(Reg128, v30) \
CALLER_STATE_REGISTER(Reg128, v31) \
CALLER_STATE_REGISTER(uint64_t, r14) \
CALLER_STATE_REGISTER(uint64_t, r15) \
CALLER_STATE_REGISTER(uint64_t, r16) \
CALLER_STATE_REGISTER(uint64_t, r17) \
CALLER_STATE_REGISTER(uint64_t, r18) \
CALLER_STATE_REGISTER(uint64_t, r19) \
CALLER_STATE_REGISTER(uint64_t, r20) \
CALLER_STATE_REGISTER(uint64_t, r21) \
CALLER_STATE_REGISTER(uint64_t, r22) \
CALLER_STATE_REGISTER(uint64_t, r23) \
CALLER_STATE_REGISTER(uint64_t, r24) \
CALLER_STATE_REGISTER(uint64_t, r25) \
CALLER_STATE_REGISTER(uint64_t, r26) \
CALLER_STATE_REGISTER(uint64_t, r27) \
CALLER_STATE_REGISTER(uint64_t, r28) \
CALLER_STATE_REGISTER(uint64_t, r29) \
CALLER_STATE_REGISTER(uint64_t, r30) \
CALLER_STATE_REGISTER(uint64_t, r31) \
CALLER_STATE_REGISTER(uint64_t, f14) \
CALLER_STATE_REGISTER(uint64_t, f15) \
CALLER_STATE_REGISTER(uint64_t, f16) \
CALLER_STATE_REGISTER(uint64_t, f17) \
CALLER_STATE_REGISTER(uint64_t, f18) \
CALLER_STATE_REGISTER(uint64_t, f19) \
CALLER_STATE_REGISTER(uint64_t, f20) \
CALLER_STATE_REGISTER(uint64_t, f21) \
CALLER_STATE_REGISTER(uint64_t, f22) \
CALLER_STATE_REGISTER(uint64_t, f23) \
CALLER_STATE_REGISTER(uint64_t, f24) \
CALLER_STATE_REGISTER(uint64_t, f25) \
CALLER_STATE_REGISTER(uint64_t, f26) \
CALLER_STATE_REGISTER(uint64_t, f27) \
CALLER_STATE_REGISTER(uint64_t, f28) \
CALLER_STATE_REGISTER(uint64_t, f29) \
CALLER_STATE_REGISTER(uint64_t, f30) \
CALLER_STATE_REGISTER(uint64_t, f31) \
CALLER_STATE_REGISTER(CRReg, cr)
#endif // X86_64 || X86 || ARM || AARCH64 || PPC64LE
// Enable ABI testing if all of the following are true.
//
// - We have CallerState and trampoline support for the architecture.
//
// - Assembly is enabled.
//
// - This is not a shared library build. Assembly functions are not reachable
// from tests in shared library builds.
#if defined(LOOP_CALLER_STATE_REGISTERS) && !defined(OPENSSL_NO_ASM) && \
!defined(BORINGSSL_SHARED_LIBRARY)
#define SUPPORTS_ABI_TEST
// CallerState contains all caller state that the callee is expected to
// preserve.
struct CallerState {
#define CALLER_STATE_REGISTER(type, name) type name;
LOOP_CALLER_STATE_REGISTERS()
#undef CALLER_STATE_REGISTER
};
// RunTrampoline runs |func| on |argv|, recording ABI errors in |out|. It does
// not perform any type-checking. If |unwind| is true and unwind tests have been
// enabled, |func| is single-stepped under an unwind test.
crypto_word_t RunTrampoline(Result *out, crypto_word_t func,
const crypto_word_t *argv, size_t argc,
bool unwind);
template <typename T>
inline crypto_word_t ToWord(T t) {
// ABIs typically pass floats and structs differently from integers and
// pointers. We only need to support the latter.
static_assert(std::is_integral<T>::value || std::is_pointer<T>::value,
"parameter types must be integral or pointer types");
// We only support types which fit in registers.
static_assert(sizeof(T) <= sizeof(crypto_word_t),
"parameter types must be at most word-sized");
// ABIs are complex around arguments that are smaller than native words.
// Parameters passed in memory are sometimes packed and sometimes padded to a
// word. When parameters are padded in memory or passed in a larger register,
// the unused bits may be undefined or sign- or zero-extended.
//
// We could simply cast to |crypto_word_t| everywhere but, on platforms where
// padding is undefined, we perturb the bits to test the function accounts for
// for this.
#if defined(OPENSSL_32_BIT)
// We never pass parameters smaller than int, so require word-sized parameters
// on 32-bit architectures for simplicity.
static_assert(sizeof(T) == 4, "parameter types must be word-sized");
return (crypto_word_t)t;
#elif defined(OPENSSL_PPC64LE)
// ELFv2, section 2.2.2.3 says the parameter save area sign- or zero-extends
// parameters passed in memory. Section 2.2.3 is unclear on how to handle
// register parameters, but section 2.2.2.3 additionally says that the memory
// copy of a parameter is identical to the register one.
return (crypto_word_t)t;
#elif defined(OPENSSL_X86_64) || defined(OPENSSL_AARCH64)
// AAPCS64, section 5.4.2, clauses C.7 and C.14 says any remaining bits in
// aarch are unspecified. iOS64 contradicts this and says the callee extends
// arguments up to 32 bits, and only the upper 32 bits are unspecified.
//
// On x86_64, Win64 leaves all unused bits unspecified. SysV also leaves
// unused bits in stack parameters unspecified, but it behaves like iOS64 for
// register parameters. This was determined via experimentation.
//
// We limit to 32-bit and 64-bit parameters, the subset where the above all
// align, and then test that functions tolerate arbitrary unused bits.
//
// TODO(davidben): Find authoritative citations for x86_64. For x86_64, I
// observed the behavior of Clang, GCC, and MSVC. ABI rules here may be
// inferred from two kinds of experiments:
//
// 1. When passing a value to a small-argument-taking function, does the
// compiler ensure unused bits are cleared, sign-extended, etc.? Tests for
// register parameters are confounded by x86_64's implicit clearing of
// registers' upper halves, but passing some_u64 >> 1 usually clears this.
//
// 2. When compiling a small-argument-taking function, does the compiler make
// assumptions about unused bits of arguments?
//
// MSVC was observed to tolerate and produce arbitrary values for unused bits,
// which is conclusive. GCC and Clang, targeting Linux, were similarly
// conclusive on stack parameters. Clang was also conclusive for register
// parameters. Callers only extended parameters up to 32 bits, and callees
// took advantage of the 32-bit extension. GCC only exhibited the callee
// behavior.
static_assert(sizeof(T) >= 4, "parameters must be at least 32 bits wide");
crypto_word_t ret;
// Filling extra bits with 0xaa will be vastly out of bounds for code
// expecting either sign- or zero-extension. (0xaa is 0b10101010.)
OPENSSL_memset(&ret, 0xaa, sizeof(ret));
OPENSSL_memcpy(&ret, &t, sizeof(t));
return ret;
#else
#error "unknown architecture"
#endif
}
// CheckImpl runs |func| on |args|, recording ABI errors in |out|. If |unwind|
// is true and unwind tests have been enabled, |func| is single-stepped under an
// unwind test.
//
// It returns the value as a |crypto_word_t| to work around problems when |R| is
// void. |args| is wrapped in a |DeductionGuard| so |func| determines the
// template arguments. Otherwise, |args| may deduce |Args| incorrectly. For
// instance, if |func| takes const int *, and the caller passes an int *, the
// compiler will complain the deduced types do not match.
template <typename R, typename... Args>
inline crypto_word_t CheckImpl(Result *out, bool unwind, R (*func)(Args...),
typename DeductionGuard<Args>::Type... args) {
// We only support up to 8 arguments, so all arguments on aarch64 and ppc64le
// are passed in registers. This is simpler and avoids the iOS discrepancy
// around packing small arguments on the stack. (See the iOS64 reference.)
static_assert(sizeof...(args) <= 8,
"too many arguments for abi_test_trampoline");
// Allocate one extra entry so MSVC does not complain about zero-size arrays.
crypto_word_t argv[sizeof...(args) + 1] = {
ToWord(args)...,
};
return RunTrampoline(out, reinterpret_cast<crypto_word_t>(func), argv,
sizeof...(args), unwind);
}
#else
// To simplify callers when ABI testing support is unavoidable, provide a backup
// CheckImpl implementation. It must be specialized for void returns because we
// call |func| directly.
template <typename R, typename... Args>
inline typename std::enable_if<!std::is_void<R>::value, crypto_word_t>::type
CheckImpl(Result *out, bool /* unwind */, R (*func)(Args...),
typename DeductionGuard<Args>::Type... args) {
*out = Result();
return func(args...);
}
template <typename... Args>
inline crypto_word_t CheckImpl(Result *out, bool /* unwind */,
void (*func)(Args...),
typename DeductionGuard<Args>::Type... args) {
*out = Result();
func(args...);
return 0;
}
#endif // SUPPORTS_ABI_TEST
// FixVAArgsString takes a string like "f, 1, 2" and returns a string like
// "f(1, 2)".
//
// This is needed because the |CHECK_ABI| macro below cannot be defined as
// CHECK_ABI(func, ...). The C specification requires that variadic macros bind
// at least one variadic argument. Clang, GCC, and MSVC all ignore this, but
// there are issues with trailing commas and different behaviors across
// compilers.
std::string FixVAArgsString(const char *str);
// CheckGTest behaves like |CheckImpl|, but it returns the correct type and
// raises GTest assertions on failure. If |unwind| is true and unwind tests are
// enabled, |func| is single-stepped under an unwind test.
template <typename R, typename... Args>
inline R CheckGTest(const char *va_args_str, const char *file, int line,
bool unwind, R (*func)(Args...),
typename DeductionGuard<Args>::Type... args) {
Result result;
crypto_word_t ret = CheckImpl(&result, unwind, func, args...);
if (!result.ok()) {
testing::Message msg;
msg << "ABI failures in " << FixVAArgsString(va_args_str) << ":\n";
for (const auto &error : result.errors) {
msg << " " << error << "\n";
}
ADD_FAILURE_AT(file, line) << msg;
}
return (R)ret;
}
} // namespace internal
// Check runs |func| on |args| and returns the result. If ABI-testing is
// supported in this build configuration, it writes any ABI failures to |out|.
// Otherwise, it runs the function transparently.
template <typename R, typename... Args>
inline R Check(Result *out, R (*func)(Args...),
typename internal::DeductionGuard<Args>::Type... args) {
return (R)internal::CheckImpl(out, false, func, args...);
}
// EnableUnwindTests enables unwind tests, if supported. If not supported, it
// does nothing.
void EnableUnwindTests();
// UnwindTestsEnabled returns true if unwind tests are enabled and false
// otherwise.
bool UnwindTestsEnabled();
} // namespace abi_test
// CHECK_ABI calls the first argument on the remaining arguments and returns the
// result. If ABI-testing is supported in this build configuration, it adds a
// non-fatal GTest failure if the call did not satisfy ABI requirements.
//
// |CHECK_ABI| does return the value and thus may replace any function call,
// provided it takes only simple parameters. However, it is recommended to test
// ABI separately from functional tests of assembly. Fully instrumenting a
// function for ABI checking requires single-stepping the function, which is
// inefficient.
//
// Functional testing requires coverage of input values, while ABI testing only
// requires branch coverage. Most of our assembly is constant-time, so usually
// only a few instrumented calls are necessary.
//
// TODO(https://crbug.com/boringssl/259): Most of Windows assembly currently
// fails SEH testing. For now, |CHECK_ABI| behaves like |CHECK_ABI_NO_UNWIND|
// on Windows. Functions which work with unwind testing on Windows should use
// |CHECK_ABI_SEH|.
#if defined(OPENSSL_WINDOWS)
#define CHECK_ABI(...) CHECK_ABI_NO_UNWIND(__VA_ARGS__)
#else
#define CHECK_ABI(...) CHECK_ABI_SEH(__VA_ARGS__)
#endif
// CHECK_ABI_SEH behaves like |CHECK_ABI| but enables unwind testing on Windows.
#define CHECK_ABI_SEH(...) \
abi_test::internal::CheckGTest(#__VA_ARGS__, __FILE__, __LINE__, true, \
__VA_ARGS__)
// CHECK_ABI_NO_UNWIND behaves like |CHECK_ABI| but disables unwind testing.
#define CHECK_ABI_NO_UNWIND(...) \
abi_test::internal::CheckGTest(#__VA_ARGS__, __FILE__, __LINE__, false, \
__VA_ARGS__)
// Internal functions.
#if defined(SUPPORTS_ABI_TEST)
struct Uncallable {
Uncallable() = delete;
};
extern "C" {
// abi_test_trampoline loads callee-saved registers from |state|, calls |func|
// with |argv|, then saves the callee-saved registers into |state|. It returns
// the result of |func|. If |unwind| is non-zero, this function triggers unwind
// instrumentation.
//
// We give |func| type |crypto_word_t| to avoid tripping MSVC's warning 4191.
crypto_word_t abi_test_trampoline(crypto_word_t func,
abi_test::internal::CallerState *state,
const crypto_word_t *argv, size_t argc,
crypto_word_t unwind);
#if defined(OPENSSL_X86_64)
// abi_test_unwind_start points at the instruction that starts unwind testing in
// |abi_test_trampoline|. This is the value of the instruction pointer at the
// first |SIGTRAP| during unwind testing.
//
// This symbol is not a function and should not be called.
void abi_test_unwind_start(Uncallable);
// abi_test_unwind_return points at the instruction immediately after the call in
// |abi_test_trampoline|. When unwinding the function under test, this is the
// expected address in the |abi_test_trampoline| frame. After this address, the
// unwind tester should ignore |SIGTRAP| until |abi_test_unwind_stop|.
//
// This symbol is not a function and should not be called.
void abi_test_unwind_return(Uncallable);
// abi_test_unwind_stop is the value of the instruction pointer at the final
// |SIGTRAP| during unwind testing.
//
// This symbol is not a function and should not be called.
void abi_test_unwind_stop(Uncallable);
// abi_test_bad_unwind_wrong_register preserves the ABI, but annotates the wrong
// register in unwind metadata.
void abi_test_bad_unwind_wrong_register(void);
// abi_test_bad_unwind_temporary preserves the ABI, but temporarily corrupts the
// storage space for a saved register, breaking unwind.
void abi_test_bad_unwind_temporary(void);
#if defined(OPENSSL_WINDOWS)
// abi_test_bad_unwind_epilog preserves the ABI, and correctly annotates the
// prolog, but the epilog does not match Win64's rules, breaking unwind during
// the epilog.
void abi_test_bad_unwind_epilog(void);
#endif
#endif // OPENSSL_X86_64
#if defined(OPENSSL_X86_64) || defined(OPENSSL_X86)
// abi_test_get_and_clear_direction_flag clears the direction flag. If the flag
// was previously set, it returns one. Otherwise, it returns zero.
int abi_test_get_and_clear_direction_flag(void);
// abi_test_set_direction_flag sets the direction flag. This does not conform to
// ABI requirements and must only be called within a |CHECK_ABI| guard to avoid
// errors later in the program.
int abi_test_set_direction_flag(void);
#endif // OPENSSL_X86_64 || OPENSSL_X86
} // extern "C"
#endif // SUPPORTS_ABI_TEST
#endif // OPENSSL_HEADER_ABI_TEST_H

View File

@@ -0,0 +1,170 @@
#!/usr/bin/env perl
# Copyright (c) 2019, Google Inc.
#
# SPDX-License-Identifier: ISC
# This file defines helper functions for crypto/test/abi_test.h on 32-bit
# ARM. See that header for details on how to use this.
#
# For convenience, this file is linked into libcrypto, where consuming builds
# already support architecture-specific sources. The static linker should drop
# this code in non-test binaries. This includes a shared library build of
# libcrypto, provided --gc-sections (ELF), -dead_strip (iOS), or equivalent is
# used.
#
# References:
#
# AAPCS: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042f/IHI0042F_aapcs.pdf
# iOS ARMv6: https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv6FunctionCallingConventions.html
# iOS ARMv7: https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv7FunctionCallingConventions.html
# Linux: http://sourcery.mentor.com/sgpp/lite/arm/portal/kbattach142/arm_gnu_linux_%20abi.pdf
use strict;
my $flavour = shift;
my $output = shift;
$0 =~ m/(.*[\/\\])[^\/\\]+$/;
my $dir = $1;
my $xlate;
( $xlate="${dir}arm-xlate.pl" and -f $xlate ) or
( $xlate="${dir}../../perlasm/arm-xlate.pl" and -f $xlate) or
die "can't locate arm-xlate.pl";
open OUT, "| \"$^X\" \"$xlate\" $flavour \"$output\"";
*STDOUT = *OUT;
my ($func, $state, $argv, $argc) = ("r0", "r1", "r2", "r3");
my $code = <<____;
.syntax unified
.arch armv7-a
.fpu vfp
.text
@ abi_test_trampoline loads callee-saved registers from |state|, calls |func|
@ with |argv|, then saves the callee-saved registers into |state|. It returns
@ the result of |func|. The |unwind| argument is unused.
@ uint32_t abi_test_trampoline(void (*func)(...), CallerState *state,
@ const uint32_t *argv, size_t argc,
@ int unwind);
.type abi_test_trampoline, %function
.globl abi_test_trampoline
.align 4
abi_test_trampoline:
@ Save parameters and all callee-saved registers. For convenience, we
@ save r9 on iOS even though it's volatile.
vstmdb sp!, {d8-d15}
stmdb sp!, {r0-r11,lr}
@ Reserve stack space for six (10-4) stack parameters, plus an extra 4
@ bytes to keep it 8-byte-aligned (see AAPCS, section 5.3).
sub sp, sp, #28
@ Every register in AAPCS is either non-volatile or a parameter (except
@ r9 on iOS), so this code, by the actual call, loses all its scratch
@ registers. First fill in stack parameters while there are registers
@ to spare.
cmp $argc, #4
bls .Lstack_args_done
mov r4, sp @ r4 is the output pointer.
add r5, $argv, $argc, lsl #2 @ Set r5 to the end of argv.
add $argv, $argv, #16 @ Skip four arguments.
.Lstack_args_loop:
ldr r6, [$argv], #4
cmp $argv, r5
str r6, [r4], #4
bne .Lstack_args_loop
.Lstack_args_done:
@ Load registers from |$state|.
vldmia $state!, {d8-d15}
#if defined(__APPLE__)
@ r9 is not volatile on iOS.
ldmia $state!, {r4-r8,r10-r11}
#else
ldmia $state!, {r4-r11}
#endif
@ Load register parameters. This uses up our remaining registers, so we
@ repurpose lr as scratch space.
ldr $argc, [sp, #40] @ Reload argc.
ldr lr, [sp, #36] @ Load argv into lr.
cmp $argc, #3
bhi .Larg_r3
beq .Larg_r2
cmp $argc, #1
bhi .Larg_r1
beq .Larg_r0
b .Largs_done
.Larg_r3:
ldr r3, [lr, #12] @ argv[3]
.Larg_r2:
ldr r2, [lr, #8] @ argv[2]
.Larg_r1:
ldr r1, [lr, #4] @ argv[1]
.Larg_r0:
ldr r0, [lr] @ argv[0]
.Largs_done:
@ With every other register in use, load the function pointer into lr
@ and call the function.
ldr lr, [sp, #28]
blx lr
@ r1-r3 are free for use again. The trampoline only supports
@ single-return functions. Pass r4-r11 to the caller.
ldr $state, [sp, #32]
vstmia $state!, {d8-d15}
#if defined(__APPLE__)
@ r9 is not volatile on iOS.
stmia $state!, {r4-r8,r10-r11}
#else
stmia $state!, {r4-r11}
#endif
@ Unwind the stack and restore registers.
add sp, sp, #44 @ 44 = 28+16
ldmia sp!, {r4-r11,lr} @ Skip r0-r3 (see +16 above).
vldmia sp!, {d8-d15}
bx lr
.size abi_test_trampoline,.-abi_test_trampoline
____
# abi_test_clobber_* zeros the corresponding register. These are used to test
# the ABI-testing framework.
foreach (0..12) {
# This loop skips r13 (sp), r14 (lr, implicitly clobbered by every call), and
# r15 (pc).
$code .= <<____;
.type abi_test_clobber_r$_, %function
.globl abi_test_clobber_r$_
.align 4
abi_test_clobber_r$_:
mov r$_, #0
bx lr
.size abi_test_clobber_r$_,.-abi_test_clobber_r$_
____
}
foreach (0..15) {
my $lo = "s".(2*$_);
my $hi = "s".(2*$_+1);
$code .= <<____;
.type abi_test_clobber_d$_, %function
.globl abi_test_clobber_d$_
.align 4
abi_test_clobber_d$_:
mov r0, #0
vmov $lo, r0
vmov $hi, r0
bx lr
.size abi_test_clobber_d$_,.-abi_test_clobber_d$_
____
}
print $code;
close STDOUT or die "error closing STDOUT: $!";

View File

@@ -0,0 +1,205 @@
#!/usr/bin/env perl
# Copyright (c) 2019, Google Inc.
#
# SPDX-License-Identifier: ISC
# This file defines helper functions for crypto/test/abi_test.h on aarch64. See
# that header for details on how to use this.
#
# For convenience, this file is linked into libcrypto, where consuming builds
# already support architecture-specific sources. The static linker should drop
# this code in non-test binaries. This includes a shared library build of
# libcrypto, provided --gc-sections (ELF), -dead_strip (iOS), or equivalent is
# used.
#
# References:
#
# AAPCS64: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
# iOS ARM64: https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html
use strict;
my $flavour = shift;
my $output = shift;
$0 =~ m/(.*[\/\\])[^\/\\]+$/;
my $dir = $1;
my $xlate;
( $xlate="${dir}arm-xlate.pl" and -f $xlate ) or
( $xlate="${dir}../../perlasm/arm-xlate.pl" and -f $xlate) or
die "can't locate arm-xlate.pl";
open OUT, "| \"$^X\" \"$xlate\" $flavour \"$output\"";
*STDOUT = *OUT;
my ($func, $state, $argv, $argc) = ("x0", "x1", "x2", "x3");
my $code = <<____;
#include <openssl/arm_arch.h>
.text
// abi_test_trampoline loads callee-saved registers from |state|, calls |func|
// with |argv|, then saves the callee-saved registers into |state|. It returns
// the result of |func|. The |unwind| argument is unused.
// uint64_t abi_test_trampoline(void (*func)(...), CallerState *state,
// const uint64_t *argv, size_t argc,
// uint64_t unwind);
.type abi_test_trampoline, %function
.globl abi_test_trampoline
.align 4
abi_test_trampoline:
.Labi_test_trampoline_begin:
AARCH64_SIGN_LINK_REGISTER
// Stack layout (low to high addresses)
// x29,x30 (16 bytes)
// d8-d15 (64 bytes)
// x19-x28 (80 bytes)
// $state (8 bytes)
// padding (8 bytes)
stp x29, x30, [sp, #-176]!
mov x29, sp
// Saved callee-saved registers and |state|.
stp d8, d9, [sp, #16]
stp d10, d11, [sp, #32]
stp d12, d13, [sp, #48]
stp d14, d15, [sp, #64]
stp x19, x20, [sp, #80]
stp x21, x22, [sp, #96]
stp x23, x24, [sp, #112]
stp x25, x26, [sp, #128]
stp x27, x28, [sp, #144]
str $state, [sp, #160]
// Load registers from |state|, with the exception of x29. x29 is the
// frame pointer and also callee-saved, but AAPCS64 allows platforms to
// mandate that x29 always point to a frame. iOS64 does so, which means
// we cannot fill x29 with entropy without violating ABI rules
// ourselves. x29 is tested separately below.
ldp d8, d9, [$state], #16
ldp d10, d11, [$state], #16
ldp d12, d13, [$state], #16
ldp d14, d15, [$state], #16
ldp x19, x20, [$state], #16
ldp x21, x22, [$state], #16
ldp x23, x24, [$state], #16
ldp x25, x26, [$state], #16
ldp x27, x28, [$state], #16
// Move parameters into temporary registers.
mov x9, $func
mov x10, $argv
mov x11, $argc
// Load parameters into registers.
cbz x11, .Largs_done
ldr x0, [x10], #8
subs x11, x11, #1
b.eq .Largs_done
ldr x1, [x10], #8
subs x11, x11, #1
b.eq .Largs_done
ldr x2, [x10], #8
subs x11, x11, #1
b.eq .Largs_done
ldr x3, [x10], #8
subs x11, x11, #1
b.eq .Largs_done
ldr x4, [x10], #8
subs x11, x11, #1
b.eq .Largs_done
ldr x5, [x10], #8
subs x11, x11, #1
b.eq .Largs_done
ldr x6, [x10], #8
subs x11, x11, #1
b.eq .Largs_done
ldr x7, [x10], #8
.Largs_done:
blr x9
// Reload |state| and store registers.
ldr $state, [sp, #160]
stp d8, d9, [$state], #16
stp d10, d11, [$state], #16
stp d12, d13, [$state], #16
stp d14, d15, [$state], #16
stp x19, x20, [$state], #16
stp x21, x22, [$state], #16
stp x23, x24, [$state], #16
stp x25, x26, [$state], #16
stp x27, x28, [$state], #16
// |func| is required to preserve x29, the frame pointer. We cannot load
// random values into x29 (see comment above), so compare it against the
// expected value and zero the field of |state| if corrupted.
mov x9, sp
cmp x29, x9
b.eq .Lx29_ok
str xzr, [$state]
.Lx29_ok:
// Restore callee-saved registers.
ldp d8, d9, [sp, #16]
ldp d10, d11, [sp, #32]
ldp d12, d13, [sp, #48]
ldp d14, d15, [sp, #64]
ldp x19, x20, [sp, #80]
ldp x21, x22, [sp, #96]
ldp x23, x24, [sp, #112]
ldp x25, x26, [sp, #128]
ldp x27, x28, [sp, #144]
ldp x29, x30, [sp], #176
AARCH64_VALIDATE_LINK_REGISTER
ret
.size abi_test_trampoline,.-abi_test_trampoline
____
# abi_test_clobber_* zeros the corresponding register. These are used to test
# the ABI-testing framework.
foreach (0..29) {
# x18 is the platform register and off limits.
next if ($_ == 18);
$code .= <<____;
.type abi_test_clobber_x$_, %function
.globl abi_test_clobber_x$_
.align 4
abi_test_clobber_x$_:
AARCH64_VALID_CALL_TARGET
mov x$_, xzr
ret
.size abi_test_clobber_x$_,.-abi_test_clobber_x$_
____
}
foreach (0..31) {
$code .= <<____;
.type abi_test_clobber_d$_, %function
.globl abi_test_clobber_d$_
.align 4
abi_test_clobber_d$_:
AARCH64_VALID_CALL_TARGET
fmov d$_, xzr
ret
.size abi_test_clobber_d$_,.-abi_test_clobber_d$_
____
}
# abi_test_clobber_v*_upper clobbers only the upper half of v*. AAPCS64 only
# requires the lower half (d*) be preserved.
foreach (8..15) {
$code .= <<____;
.type abi_test_clobber_v${_}_upper, %function
.globl abi_test_clobber_v${_}_upper
.align 4
abi_test_clobber_v${_}_upper:
AARCH64_VALID_CALL_TARGET
fmov v${_}.d[1], xzr
ret
.size abi_test_clobber_v${_}_upper,.-abi_test_clobber_v${_}_upper
____
}
print $code;
close STDOUT or die "error closing STDOUT: $!";

View File

@@ -0,0 +1,251 @@
#!/usr/bin/env perl
# Copyright (c) 2019, Google Inc.
#
# SPDX-License-Identifier: ISC
# This file defines helper functions for crypto/test/abi_test.h on ppc64le. See
# that header for details on how to use this.
#
# For convenience, this file is linked into libcrypto, where consuming builds
# already support architecture-specific sources. The static linker should drop
# this code in non-test binaries. This includes a shared library build of
# libcrypto, provided --gc-sections or equivalent is used.
#
# References:
#
# ELFv2: http://openpowerfoundation.org/wp-content/uploads/resources/leabi/leabi-20170510.pdf
use strict;
my $flavour = shift;
my $output = shift;
$0 =~ m/(.*[\/\\])[^\/\\]+$/;
my $dir = $1;
my $xlate;
( $xlate="${dir}ppc-xlate.pl" and -f $xlate ) or
( $xlate="${dir}../../perlasm/ppc-xlate.pl" and -f $xlate) or
die "can't locate ppc-xlate.pl";
open OUT, "| \"$^X\" \"$xlate\" $flavour \"$output\"";
*STDOUT = *OUT;
unless ($flavour =~ /linux.*64le/) {
die "This file only supports the ELFv2 ABI, used by ppc64le";
}
my $code = "";
sub load_or_store_regs {
# $op is "l" or "st".
my ($op, $base_reg, $base_offset) = @_;
# Vector registers.
foreach (20..31) {
my $offset = $base_offset + ($_ - 20) * 16;
# Vector registers only support indexed register addressing.
$code .= "\tli\tr11, $offset\n";
$code .= "\t${op}vx\tv$_, r11, $base_reg\n";
}
# Save general registers.
foreach (14..31) {
my $offset = $base_offset + 192 + ($_ - 14) * 8;
$code .= "\t${op}d\tr$_, $offset($base_reg)\n";
}
# Save floating point registers.
foreach (14..31) {
my $offset = $base_offset + 336 + ($_ - 14) * 8;
$code .= "\t${op}fd\tf$_, $offset($base_reg)\n";
}
}
sub load_regs {
my ($base_reg, $base_offset) = @_;
load_or_store_regs("l", $base_reg, $base_offset);
}
sub store_regs {
my ($base_reg, $base_offset) = @_;
load_or_store_regs("st", $base_reg, $base_offset);
}
my ($func, $state, $argv, $argc) = ("r3", "r4", "r5", "r6");
$code .= <<____;
.machine "any"
.text
# abi_test_trampoline loads callee-saved registers from |state|, calls |func|
# with |argv|, then saves the callee-saved registers into |state|. It returns
# the result of |func|. The |unwind| argument is unused.
# uint64_t abi_test_trampoline(void (*func)(...), CallerState *state,
# const uint64_t *argv, size_t argc,
# uint64_t unwind);
.globl abi_test_trampoline
.align 5
abi_test_trampoline:
# LR is saved into the caller's stack frame.
mflr r0
std r0, 16(r1)
# Allocate 66*8 = 528 bytes of stack frame. From the top of the stack
# to the bottom, the stack frame is:
#
# 0(r1) - Back chain pointer
# 8(r1) - CR save area
# 16(r1) - LR save area (for |func|)
# 24(r1) - TOC pointer save area
# 32(r1) - Saved copy of |state|
# 40(r1) - Padding
# 48(r1) - Vector register save area (v20-v31, 12 registers)
# 240(r1) - General register save area (r14-r31, 18 registers)
# 384(r1) - Floating point register save area (f14-f31, 18 registers)
#
# Note the layouts of the register save areas and CallerState match.
#
# In the ELFv2 ABI, the parameter save area is optional if the function
# is non-variadic and all parameters fit in registers. We only support
# such functions, so we omit it to test that |func| does not rely on it.
stdu r1, -528(r1)
mfcr r0
std r0, 8(r1) # Save CR
std r2, 24(r1) # Save TOC
std $state, 32(r1) # Save |state|
____
# Save registers to the stack.
store_regs("r1", 48);
# Load registers from the caller.
load_regs($state, 0);
$code .= <<____;
# Load CR from |state|.
ld r0, 480($state)
mtcr r0
# Move parameters into temporary registers so they are not clobbered.
addi r11, $argv, -8 # Adjust for ldu below
mr r12, $func
# Load parameters into registers.
cmpdi $argc, 0
beq .Largs_done
mtctr $argc
ldu r3, 8(r11)
bdz .Largs_done
ldu r4, 8(r11)
bdz .Largs_done
ldu r5, 8(r11)
bdz .Largs_done
ldu r6, 8(r11)
bdz .Largs_done
ldu r7, 8(r11)
bdz .Largs_done
ldu r8, 8(r11)
bdz .Largs_done
ldu r9, 8(r11)
bdz .Largs_done
ldu r10, 8(r11)
.Largs_done:
li r2, 0 # Clear TOC to test |func|'s global entry point
mtctr r12
bctrl
ld r2, 24(r1) # Restore TOC
ld $state, 32(r1) # Reload |state|
____
# Output resulting registers to the caller.
store_regs($state, 0);
# Restore registers from the stack.
load_regs("r1", 48);
$code .= <<____;
mfcr r0
std r0, 480($state) # Output CR to caller
ld r0, 8(r1)
mtcrf 0b00111000, r0 # Restore CR2-CR4
addi r1, r1, 528
ld r0, 16(r1) # Restore LR
mtlr r0
blr
.size abi_test_trampoline,.-abi_test_trampoline
____
# abi_test_clobber_* clobbers the corresponding register. These are used to test
# the ABI-testing framework.
foreach (0..31) {
# r1 is the stack pointer. r13 is the thread pointer.
next if ($_ == 1 || $_ == 13);
$code .= <<____;
.globl abi_test_clobber_r$_
.align 5
abi_test_clobber_r$_:
li r$_, 0
blr
.size abi_test_clobber_r$_,.-abi_test_clobber_r$_
____
}
foreach (0..31) {
$code .= <<____;
.globl abi_test_clobber_f$_
.align 4
abi_test_clobber_f$_:
li r0, 0
# Use the red zone.
std r0, -8(r1)
lfd f$_, -8(r1)
blr
.size abi_test_clobber_f$_,.-abi_test_clobber_f$_
____
}
foreach (0..31) {
$code .= <<____;
.globl abi_test_clobber_v$_
.align 4
abi_test_clobber_v$_:
vxor v$_, v$_, v$_
blr
.size abi_test_clobber_v$_,.-abi_test_clobber_v$_
____
}
foreach (0..7) {
# PPC orders CR fields in big-endian, so the mask is reversed from what one
# would expect.
my $mask = 1 << (7 - $_);
$code .= <<____;
.globl abi_test_clobber_cr$_
.align 4
abi_test_clobber_cr$_:
# Flip the bits on cr$_ rather than setting to zero. With a four-bit
# register, zeroing it will do nothing 1 in 16 times.
mfcr r0
not r0, r0
mtcrf $mask, r0
blr
.size abi_test_clobber_cr$_,.-abi_test_clobber_cr$_
____
}
$code .= <<____;
.globl abi_test_clobber_ctr
.align 4
abi_test_clobber_ctr:
li r0, 0
mtctr r0
blr
.size abi_test_clobber_ctr,.-abi_test_clobber_ctr
.globl abi_test_clobber_lr
.align 4
abi_test_clobber_lr:
mflr r0
mtctr r0
li r0, 0
mtlr r0
bctr
.size abi_test_clobber_lr,.-abi_test_clobber_lr
____
print $code;
close STDOUT or die "error closing STDOUT: $!";

View File

@@ -0,0 +1,113 @@
#!/usr/bin/env perl
# Copyright (c) 2018, Google Inc.
#
# SPDX-License-Identifier: ISC
# This file defines helper functions for crypto/test/abi_test.h on x86. See
# that header for details on how to use this.
#
# For convenience, this file is linked into libcrypto, where consuming builds
# already support architecture-specific sources. The static linker should drop
# this code in non-test binaries. This includes a shared library build of
# libcrypto, provided --gc-sections (ELF), -dead_strip (Mac), or equivalent is
# used.
#
# References:
#
# SysV ABI: https://uclibc.org/docs/psABI-i386.pdf
# Win32 ABI: https://docs.microsoft.com/en-us/cpp/cpp/argument-passing-and-naming-conventions?view=vs-2017
use strict;
$0 =~ m/(.*[\/\\])[^\/\\]+$/;
my $dir = $1;
push(@INC, "${dir}", "${dir}../../perlasm");
require "x86asm.pl";
my $output = $ARGV[1];
open STDOUT, ">$output";
&asm_init($ARGV[0]);
# abi_test_trampoline loads callee-saved registers from |state|, calls |func|
# with |argv|, then saves the callee-saved registers into |state|. It returns
# the result of |func|. |unwind| is ignored.
# uint32_t abi_test_trampoline(void (*func)(...), CallerState *state,
# const uint32_t *argv, size_t argc,
# int unwind);
&function_begin("abi_test_trampoline")
# Load registers from |state|. Note |function_begin| (as opposed to
# |function_begin_B|) automatically saves all callee-saved registers, so we
# may freely clobber them.
&mov("ecx", &wparam(1));
&mov("esi", &DWP(4*0, "ecx"));
&mov("edi", &DWP(4*1, "ecx"));
&mov("ebx", &DWP(4*2, "ecx"));
&mov("ebp", &DWP(4*3, "ecx"));
# Use a fixed stack allocation so |wparam| continues to work. abi_test.h
# supports at most 10 arguments. The SysV ABI requires a 16-byte-aligned
# stack on process entry, so round up to 3 (mod 4).
&stack_push(11);
# Copy parameters to stack.
&mov("eax", &wparam(2));
&xor("ecx", "ecx");
&set_label("loop");
&cmp("ecx", &wparam(3));
&jae(&label("loop_done"));
&mov("edx", &DWP(0, "eax", "ecx", 4));
&mov(&DWP(0, "esp", "ecx", 4), "edx");
&add("ecx", 1);
&jmp(&label("loop"));
&set_label("loop_done");
&call_ptr(&wparam(0));
&stack_pop(11);
# Save registers back into |state|.
&mov("ecx", &wparam(1));
&mov(&DWP(4*0, "ecx"), "esi");
&mov(&DWP(4*1, "ecx"), "edi");
&mov(&DWP(4*2, "ecx"), "ebx");
&mov(&DWP(4*3, "ecx"), "ebp");
&function_end("abi_test_trampoline")
# abi_test_get_and_clear_direction_flag clears the direction flag. If the flag
# was previously set, it returns one. Otherwise, it returns zero.
# int abi_test_get_and_clear_direction_flag(void);
&function_begin_B("abi_test_get_and_clear_direction_flag");
&pushf();
&pop("eax");
&and("eax", 0x400);
&shr("eax", 10);
&cld();
&ret();
&function_end_B("abi_test_get_and_clear_direction_flag");
# abi_test_set_direction_flag sets the direction flag.
# void abi_test_set_direction_flag(void);
&function_begin_B("abi_test_set_direction_flag");
&std();
&ret();
&function_end_B("abi_test_set_direction_flag");
# abi_test_clobber_* zeros the corresponding register. These are used to test
# the ABI-testing framework.
foreach ("eax", "ebx", "ecx", "edx", "edi", "esi", "ebp") {
&function_begin_B("abi_test_clobber_$_");
&xor($_, $_);
&ret();
&function_end_B("abi_test_clobber_$_");
}
foreach (0..7) {
&function_begin_B("abi_test_clobber_xmm$_");
&pxor("xmm$_", "xmm$_");
&ret();
&function_end_B("abi_test_clobber_xmm$_");
}
&asm_finish();
close STDOUT or die "error closing STDOUT: $!";

View File

@@ -0,0 +1,427 @@
#!/usr/bin/env perl
# Copyright (c) 2018, Google Inc.
#
# SPDX-License-Identifier: ISC
# This file defines helper functions for crypto/test/abi_test.h on x86_64. See
# that header for details on how to use this.
#
# For convenience, this file is linked into libcrypto, where consuming builds
# already support architecture-specific sources. The static linker should drop
# this code in non-test binaries. This includes a shared library build of
# libcrypto, provided --gc-sections (ELF), -dead_strip (Mac), or equivalent is
# used.
#
# References:
#
# SysV ABI: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
# Win64 ABI: https://docs.microsoft.com/en-us/cpp/build/x64-software-conventions?view=vs-2017
use strict;
my $flavour = shift;
my $output = shift;
my $win64 = 0;
$win64 = 1 if ($flavour =~ /[nm]asm|mingw64/ || $output =~ /\.asm$/);
$0 =~ m/(.*[\/\\])[^\/\\]+$/;
my $dir = $1;
my $xlate;
( $xlate="${dir}x86_64-xlate.pl" and -f $xlate ) or
( $xlate="${dir}../../perlasm/x86_64-xlate.pl" and -f $xlate) or
die "can't locate x86_64-xlate.pl";
open OUT, "| \"$^X\" \"$xlate\" $flavour \"$output\"";
*STDOUT = *OUT;
# @inp is the registers used for function inputs, in order.
my @inp = $win64 ? ("%rcx", "%rdx", "%r8", "%r9") :
("%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9");
# @caller_state is the list of registers that the callee must preserve for the
# caller. This must match the definition of CallerState in abi_test.h.
my @caller_state = ("%rbx", "%rbp", "%r12", "%r13", "%r14", "%r15");
if ($win64) {
@caller_state = ("%rbx", "%rbp", "%rdi", "%rsi", "%r12", "%r13", "%r14",
"%r15", "%xmm6", "%xmm7", "%xmm8", "%xmm9", "%xmm10",
"%xmm11", "%xmm12", "%xmm13", "%xmm14", "%xmm15");
}
# $caller_state_size is the size of CallerState, in bytes.
my $caller_state_size = 0;
foreach (@caller_state) {
if (/^%r/) {
$caller_state_size += 8;
} elsif (/^%xmm/) {
$caller_state_size += 16;
} else {
die "unknown register $_";
}
}
# load_caller_state returns code which loads a CallerState structure at
# $off($reg) into the respective registers. No other registers are touched, but
# $reg may not be a register in CallerState. $cb is an optional callback to
# add extra lines after each movq or movdqa. $cb is passed the offset, relative
# to $reg, and name of each register.
sub load_caller_state {
my ($off, $reg, $cb) = @_;
my $ret = "";
foreach (@caller_state) {
my $old_off = $off;
if (/^%r/) {
$ret .= "\tmovq\t$off($reg), $_\n";
$off += 8;
} elsif (/^%xmm/) {
$ret .= "\tmovdqa\t$off($reg), $_\n";
$off += 16;
} else {
die "unknown register $_";
}
$ret .= $cb->($old_off, $_) if (defined($cb));
}
return $ret;
}
# store_caller_state behaves like load_caller_state, except that it writes the
# current values of the registers into $off($reg).
sub store_caller_state {
my ($off, $reg, $cb) = @_;
my $ret = "";
foreach (@caller_state) {
my $old_off = $off;
if (/^%r/) {
$ret .= "\tmovq\t$_, $off($reg)\n";
$off += 8;
} elsif (/^%xmm/) {
$ret .= "\tmovdqa\t$_, $off($reg)\n";
$off += 16;
} else {
die "unknown register $_";
}
$ret .= $cb->($old_off, $_) if (defined($cb));
}
return $ret;
}
# $max_params is the maximum number of parameters abi_test_trampoline supports.
my $max_params = 10;
# Windows reserves stack space for the register-based parameters, while SysV
# only reserves space for the overflow ones.
my $stack_params_skip = $win64 ? scalar(@inp) : 0;
my $num_stack_params = $win64 ? $max_params : $max_params - scalar(@inp);
my ($func, $state, $argv, $argc, $unwind) = @inp;
my $code = <<____;
.text
# abi_test_trampoline loads callee-saved registers from |state|, calls |func|
# with |argv|, then saves the callee-saved registers into |state|. It returns
# the result of |func|. If |unwind| is non-zero, this function triggers unwind
# instrumentation.
# uint64_t abi_test_trampoline(void (*func)(...), CallerState *state,
# const uint64_t *argv, size_t argc,
# int unwind);
.type abi_test_trampoline, \@abi-omnipotent
.globl abi_test_trampoline
.align 16
abi_test_trampoline:
.cfi_startproc
.seh_startproc
_CET_ENDBR
# Stack layout:
# 8 bytes - align
# $caller_state_size bytes - saved caller registers
# 8 bytes - scratch space
# 8 bytes - saved copy of \$unwind (SysV-only)
# 8 bytes - saved copy of \$state
# 8 bytes - saved copy of \$func
# 8 bytes - if needed for stack alignment
# 8*$num_stack_params bytes - parameters for \$func
____
my $stack_alloc_size = 8 + $caller_state_size + 8*3 + 8*$num_stack_params;
if (!$win64) {
$stack_alloc_size += 8;
}
# SysV and Windows both require the stack to be 16-byte-aligned. The call
# instruction offsets it by 8, so stack allocations must be 8 mod 16.
if ($stack_alloc_size % 16 != 8) {
$num_stack_params++;
$stack_alloc_size += 8;
}
my $stack_params_offset = 8 * $stack_params_skip;
my $func_offset = 8 * $num_stack_params;
my $state_offset = $func_offset + 8;
# On Win64, unwind is already passed in memory. On SysV, it is passed in as
# register and we must reserve stack space for it.
my ($unwind_offset, $scratch_offset);
if ($win64) {
$unwind_offset = $stack_alloc_size + 5*8;
$scratch_offset = $state_offset + 8;
} else {
$unwind_offset = $state_offset + 8;
$scratch_offset = $unwind_offset + 8;
}
my $caller_state_offset = $scratch_offset + 8;
$code .= <<____;
subq \$$stack_alloc_size, %rsp
.cfi_adjust_cfa_offset $stack_alloc_size
.seh_allocstack $stack_alloc_size
____
$code .= <<____ if (!$win64);
movq $unwind, $unwind_offset(%rsp)
____
# Store our caller's state. This is needed because we modify it ourselves, and
# also to isolate the test infrastruction from the function under test failing
# to save some register.
$code .= store_caller_state($caller_state_offset, "%rsp", sub {
my ($off, $reg) = @_;
$reg = substr($reg, 1);
# SEH records offsets relative to %rsp (when there is no frame pointer), while
# CFI records them relative to the CFA, the value of the parent's stack
# pointer just before the call.
my $cfi_off = $off - $stack_alloc_size - 8;
my $seh_dir = ".seh_savereg";
$seh_dir = ".seh_savexmm128" if ($reg =~ /^xmm/);
return <<____;
.cfi_offset $reg, $cfi_off
$seh_dir \%$reg, $off
____
});
$code .= load_caller_state(0, $state);
$code .= <<____;
# Stash \$func and \$state, so they are available after the call returns.
movq $func, $func_offset(%rsp)
movq $state, $state_offset(%rsp)
# Load parameters. Note this will clobber \$argv and \$argc, so we can
# only use non-parameter volatile registers. There are three, and they
# are the same between SysV and Win64: %rax, %r10, and %r11.
movq $argv, %r10
movq $argc, %r11
____
foreach (@inp) {
$code .= <<____;
dec %r11
js .Largs_done
movq (%r10), $_
addq \$8, %r10
____
}
$code .= <<____;
leaq $stack_params_offset(%rsp), %rax
.Largs_loop:
dec %r11
js .Largs_done
# This block should be:
# movq (%r10), %rtmp
# movq %rtmp, (%rax)
# There are no spare registers available, so we spill into the scratch
# space.
movq %r11, $scratch_offset(%rsp)
movq (%r10), %r11
movq %r11, (%rax)
movq $scratch_offset(%rsp), %r11
addq \$8, %r10
addq \$8, %rax
jmp .Largs_loop
.Largs_done:
movq $func_offset(%rsp), %rax
movq $unwind_offset(%rsp), %r10
testq %r10, %r10
jz .Lno_unwind
# Set the trap flag.
pushfq
orq \$0x100, 0(%rsp)
popfq
# Run an instruction to trigger a breakpoint immediately before the
# call.
nop
.globl abi_test_unwind_start
abi_test_unwind_start:
call *%rax
.globl abi_test_unwind_return
abi_test_unwind_return:
# Clear the trap flag. Note this assumes the trap flag was clear on
# entry. We do not support instrumenting an unwind-instrumented
# |abi_test_trampoline|.
pushfq
andq \$-0x101, 0(%rsp) # -0x101 is ~0x100
popfq
.globl abi_test_unwind_stop
abi_test_unwind_stop:
jmp .Lcall_done
.Lno_unwind:
call *%rax
.Lcall_done:
# Store what \$func did our state, so our caller can check.
movq $state_offset(%rsp), $state
____
$code .= store_caller_state(0, $state);
# Restore our caller's state.
$code .= load_caller_state($caller_state_offset, "%rsp", sub {
my ($off, $reg) = @_;
$reg = substr($reg, 1);
return ".cfi_restore\t$reg\n";
});
$code .= <<____;
addq \$$stack_alloc_size, %rsp
.cfi_adjust_cfa_offset -$stack_alloc_size
# %rax already contains \$func's return value, unmodified.
ret
.cfi_endproc
.seh_endproc
.size abi_test_trampoline,.-abi_test_trampoline
____
# abi_test_clobber_* zeros the corresponding register. These are used to test
# the ABI-testing framework.
foreach ("ax", "bx", "cx", "dx", "di", "si", "bp", 8..15) {
$code .= <<____;
.type abi_test_clobber_r$_, \@abi-omnipotent
.globl abi_test_clobber_r$_
.align 16
abi_test_clobber_r$_:
_CET_ENDBR
xorq %r$_, %r$_
ret
.size abi_test_clobber_r$_,.-abi_test_clobber_r$_
____
}
foreach (0..15) {
$code .= <<____;
.type abi_test_clobber_xmm$_, \@abi-omnipotent
.globl abi_test_clobber_xmm$_
.align 16
abi_test_clobber_xmm$_:
_CET_ENDBR
pxor %xmm$_, %xmm$_
ret
.size abi_test_clobber_xmm$_,.-abi_test_clobber_xmm$_
____
}
$code .= <<____;
# abi_test_bad_unwind_wrong_register preserves the ABI, but annotates the wrong
# register in unwind metadata.
# void abi_test_bad_unwind_wrong_register(void);
.type abi_test_bad_unwind_wrong_register, \@abi-omnipotent
.globl abi_test_bad_unwind_wrong_register
.align 16
abi_test_bad_unwind_wrong_register:
.cfi_startproc
.seh_startproc
_CET_ENDBR
pushq %r12
.cfi_push %r13 # This should be %r13
.seh_pushreg %r13 # This should be %r13
# Windows evaluates epilogs directly in the unwinder, rather than using
# unwind codes. Add a nop so there is one non-epilog point (immediately
# before the nop) where the unwinder can observe the mistake.
nop
popq %r12
.cfi_pop %r12
ret
.seh_endproc
.cfi_endproc
.size abi_test_bad_unwind_wrong_register,.-abi_test_bad_unwind_wrong_register
# abi_test_bad_unwind_temporary preserves the ABI, but temporarily corrupts the
# storage space for a saved register, breaking unwind.
# void abi_test_bad_unwind_temporary(void);
.type abi_test_bad_unwind_temporary, \@abi-omnipotent
.globl abi_test_bad_unwind_temporary
.align 16
abi_test_bad_unwind_temporary:
.cfi_startproc
.seh_startproc
_CET_ENDBR
pushq %r12
.cfi_push %r12
.seh_pushreg %r12
movq %r12, %rax
inc %rax
movq %rax, (%rsp)
# Unwinding from here is incorrect. Although %r12 itself has not been
# changed, the unwind codes say to look in (%rsp) instead.
movq %r12, (%rsp)
# Unwinding is now fixed.
popq %r12
.cfi_pop %r12
ret
.cfi_endproc
.seh_endproc
.size abi_test_bad_unwind_temporary,.-abi_test_bad_unwind_temporary
# abi_test_get_and_clear_direction_flag clears the direction flag. If the flag
# was previously set, it returns one. Otherwise, it returns zero.
# int abi_test_get_and_clear_direction_flag(void);
.type abi_test_set_direction_flag, \@abi-omnipotent
.globl abi_test_get_and_clear_direction_flag
abi_test_get_and_clear_direction_flag:
_CET_ENDBR
pushfq
popq %rax
andq \$0x400, %rax
shrq \$10, %rax
cld
ret
.size abi_test_get_and_clear_direction_flag,.-abi_test_get_and_clear_direction_flag
# abi_test_set_direction_flag sets the direction flag.
# void abi_test_set_direction_flag(void);
.type abi_test_set_direction_flag, \@abi-omnipotent
.globl abi_test_set_direction_flag
abi_test_set_direction_flag:
_CET_ENDBR
std
ret
.size abi_test_set_direction_flag,.-abi_test_set_direction_flag
____
if ($win64) {
$code .= <<____;
# abi_test_bad_unwind_epilog preserves the ABI, and correctly annotates the
# prolog, but the epilog does not match Win64's rules, breaking unwind during
# the epilog.
# void abi_test_bad_unwind_epilog(void);
.type abi_test_bad_unwind_epilog, \@abi-omnipotent
.globl abi_test_bad_unwind_epilog
.align 16
abi_test_bad_unwind_epilog:
.seh_startproc
pushq %r12
.seh_pushreg %r12
nop
# The epilog should begin here, but the nop makes it invalid.
popq %r12
nop
ret
.seh_endproc
.size abi_test_bad_unwind_epilog,.-abi_test_bad_unwind_epilog
____
}
print $code;
close STDOUT or die "error closing STDOUT: $!";

View File

@@ -0,0 +1,456 @@
// Copyright (c) 2015, Google Inc.
// SPDX-License-Identifier: ISC
#include "file_test.h"
#include <algorithm>
#include <utility>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/err.h>
#include <openssl/mem.h>
#include "../internal.h"
#include "./test_util.h"
FileTest::FileTest(std::unique_ptr<FileTest::LineReader> reader,
std::function<void(const std::string &)> comment_callback,
bool is_kas_test)
: reader_(std::move(reader)),
is_kas_test_(is_kas_test),
comment_callback_(std::move(comment_callback)) {}
FileTest::~FileTest() {}
// FindDelimiter returns a pointer to the first '=' or ':' in |str| or nullptr
// if there is none.
static const char *FindDelimiter(const char *str) {
while (*str) {
if (*str == ':' || *str == '=') {
return str;
}
str++;
}
return nullptr;
}
// StripSpace returns a string containing up to |len| characters from |str| with
// leading and trailing whitespace removed.
static std::string StripSpace(const char *str, size_t len) {
// Remove leading space.
while (len > 0 && OPENSSL_isspace(*str)) {
str++;
len--;
}
while (len > 0 && OPENSSL_isspace(str[len - 1])) {
len--;
}
return std::string(str, len);
}
static std::pair<std::string, std::string> ParseKeyValue(const char *str, const size_t len) {
const char *delimiter = FindDelimiter(str);
std::string key, value;
if (delimiter == nullptr) {
key = StripSpace(str, len);
} else {
key = StripSpace(str, delimiter - str);
value = StripSpace(delimiter + 1, str + len - delimiter - 1);
}
return {key, value};
}
FileTest::ReadResult FileTest::ReadNext() {
// If the previous test had unused attributes or instructions, it is an error.
if (!unused_attributes_.empty()) {
for (const std::string &key : unused_attributes_) {
PrintLine("Unused attribute: %s", key.c_str());
}
return kReadError;
}
if (!unused_instructions_.empty()) {
for (const std::string &key : unused_instructions_) {
PrintLine("Unused instruction: %s", key.c_str());
}
return kReadError;
}
ClearTest();
static const size_t kBufLen = 8192 * 4;
std::unique_ptr<char[]> buf(new char[kBufLen]);
bool in_instruction_block = false;
is_at_new_instruction_block_ = false;
while (true) {
// Read the next line.
switch (reader_->ReadLine(buf.get(), kBufLen)) {
case kReadError:
fprintf(stderr, "Error reading from input at line %u.\n", line_ + 1);
return kReadError;
case kReadEOF:
// EOF is a valid terminator for a test.
return start_line_ > 0 ? kReadSuccess : kReadEOF;
case kReadSuccess:
break;
}
line_++;
size_t len = strlen(buf.get());
if (buf[0] == '\n' || buf[0] == '\r' || buf[0] == '\0') {
// Empty lines delimit tests.
if (start_line_ > 0) {
return kReadSuccess;
}
if (in_instruction_block) {
in_instruction_block = false;
// Delimit instruction block from test with a blank line.
current_test_ += "\r\n";
} else if (is_kas_test_) {
// KAS tests have random blank lines scattered around.
current_test_ += "\r\n";
}
} else if (buf[0] == '#') {
if (is_kas_test_ && seen_non_comment_) {
// KAS tests have comments after the initial comment block which need
// to be included in the corresponding place in the output.
current_test_ += std::string(buf.get());
} else if (comment_callback_) {
comment_callback_(buf.get());
}
// Otherwise ignore comments.
} else if (strcmp("[B.4.2 Key Pair Generation by Testing Candidates]\r\n",
buf.get()) == 0) {
// The above instruction-like line is ignored because the FIPS lab's
// request files are hopelessly inconsistent.
} else if (buf[0] == '[') { // Inside an instruction block.
is_at_new_instruction_block_ = true;
seen_non_comment_ = true;
if (start_line_ != 0) {
// Instructions should be separate blocks.
fprintf(stderr, "Line %u is an instruction in a test case.\n", line_);
return kReadError;
}
if (!in_instruction_block) {
ClearInstructions();
in_instruction_block = true;
}
// Parse the line as an instruction ("[key = value]" or "[key]").
// KAS tests contain invalid syntax.
std::string kv = buf.get();
const bool is_broken_kas_instruction =
is_kas_test_ &&
(kv == "[SHA(s) supported (Used for hashing Z): SHA512 \r\n");
if (!is_broken_kas_instruction) {
kv = StripSpace(buf.get(), len);
if (kv[kv.size() - 1] != ']') {
fprintf(stderr, "Line %u, invalid instruction: '%s'\n", line_,
kv.c_str());
return kReadError;
}
} else {
// Just remove the newline for the broken instruction.
kv = kv.substr(0, kv.size() - 2);
}
current_test_ += kv + "\r\n";
kv = std::string(kv.begin() + 1, kv.end() - 1);
for (;;) {
size_t idx = kv.find(',');
if (idx == std::string::npos) {
idx = kv.size();
}
std::string key, value;
std::tie(key, value) = ParseKeyValue(kv.c_str(), idx);
instructions_[key] = value;
if (idx == kv.size())
break;
kv = kv.substr(idx + 1);
}
} else {
// Parsing a test case.
if (in_instruction_block) {
// Some NIST CAVP test files (TDES) have a test case immediately
// following an instruction block, without a separate blank line, some
// of the time.
in_instruction_block = false;
}
current_test_ += std::string(buf.get(), len);
std::string key, value;
std::tie(key, value) = ParseKeyValue(buf.get(), len);
// Duplicate keys are rewritten to have “/2”, “/3”, … suffixes.
std::string mapped_key = key;
// If absent, the value will be zero-initialized.
const size_t num_occurrences = ++attribute_count_[key];
if (num_occurrences > 1) {
mapped_key += "/" + std::to_string(num_occurrences);
}
unused_attributes_.insert(mapped_key);
attributes_[mapped_key] = value;
if (start_line_ == 0) {
// This is the start of a test.
type_ = mapped_key;
parameter_ = value;
start_line_ = line_;
for (const auto &kv : instructions_) {
unused_instructions_.insert(kv.first);
}
}
}
}
}
void FileTest::PrintLine(const char *format, ...) {
va_list args;
va_start(args, format);
fprintf(stderr, "Line %u: ", start_line_);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
va_end(args);
}
const std::string &FileTest::GetType() {
OnKeyUsed(type_);
return type_;
}
const std::string &FileTest::GetParameter() {
OnKeyUsed(type_);
return parameter_;
}
bool FileTest::HasAttribute(const std::string &key) {
OnKeyUsed(key);
return attributes_.count(key) > 0;
}
bool FileTest::GetAttribute(std::string *out_value, const std::string &key) {
OnKeyUsed(key);
auto iter = attributes_.find(key);
if (iter == attributes_.end()) {
PrintLine("Missing attribute '%s'.", key.c_str());
return false;
}
*out_value = iter->second;
return true;
}
const std::string &FileTest::GetAttributeOrDie(const std::string &key) {
if (!HasAttribute(key)) {
abort();
}
return attributes_[key];
}
bool FileTest::HasInstruction(const std::string &key) {
OnInstructionUsed(key);
return instructions_.count(key) > 0;
}
bool FileTest::GetInstruction(std::string *out_value, const std::string &key) {
OnInstructionUsed(key);
auto iter = instructions_.find(key);
if (iter == instructions_.end()) {
PrintLine("Missing instruction '%s'.", key.c_str());
return false;
}
*out_value = iter->second;
return true;
}
void FileTest::IgnoreAllUnusedInstructions() {
unused_instructions_.clear();
}
const std::string &FileTest::GetInstructionOrDie(const std::string &key) {
if (!HasInstruction(key)) {
abort();
}
return instructions_[key];
}
bool FileTest::GetInstructionBytes(std::vector<uint8_t> *out,
const std::string &key) {
std::string value;
return GetInstruction(&value, key) && ConvertToBytes(out, value);
}
const std::string &FileTest::CurrentTestToString() const {
return current_test_;
}
bool FileTest::GetBytes(std::vector<uint8_t> *out, const std::string &key) {
std::string value;
return GetAttribute(&value, key) && ConvertToBytes(out, value);
}
void FileTest::ClearTest() {
start_line_ = 0;
type_.clear();
parameter_.clear();
attribute_count_.clear();
attributes_.clear();
unused_attributes_.clear();
unused_instructions_.clear();
current_test_ = "";
}
void FileTest::ClearInstructions() {
instructions_.clear();
unused_attributes_.clear();
}
void FileTest::OnKeyUsed(const std::string &key) {
unused_attributes_.erase(key);
}
void FileTest::OnInstructionUsed(const std::string &key) {
unused_instructions_.erase(key);
}
bool FileTest::ConvertToBytes(std::vector<uint8_t> *out,
const std::string &value) {
if (value.size() >= 2 && value[0] == '"' && value[value.size() - 1] == '"') {
out->assign(value.begin() + 1, value.end() - 1);
return true;
}
if (!DecodeHex(out, value)) {
PrintLine("Error decoding value: %s", value.c_str());
return false;
}
return true;
}
bool FileTest::IsAtNewInstructionBlock() const {
return is_at_new_instruction_block_;
}
void FileTest::InjectInstruction(const std::string &key,
const std::string &value) {
instructions_[key] = value;
}
class FileLineReader : public FileTest::LineReader {
public:
explicit FileLineReader(const char *path) : file_(fopen(path, "r")) {}
~FileLineReader() override {
if (file_ != nullptr) {
fclose(file_);
}
}
// is_open returns true if the file was successfully opened.
bool is_open() const { return file_ != nullptr; }
FileTest::ReadResult ReadLine(char *out, size_t len) override {
assert(len > 0);
if (file_ == nullptr) {
return FileTest::kReadError;
}
len = std::min(len, size_t{INT_MAX});
if (fgets(out, static_cast<int>(len), file_) == nullptr) {
return feof(file_) ? FileTest::kReadEOF : FileTest::kReadError;
}
if (strlen(out) == len - 1 && out[len - 2] != '\n' && !feof(file_)) {
fprintf(stderr, "Line too long.\n");
return FileTest::kReadError;
}
return FileTest::kReadSuccess;
}
private:
FILE *file_;
FileLineReader(const FileLineReader &) = delete;
FileLineReader &operator=(const FileLineReader &) = delete;
};
int FileTestMain(FileTestFunc run_test, void *arg, const char *path) {
FileTest::Options opts;
opts.callback = run_test;
opts.arg = arg;
opts.path = path;
return FileTestMain(opts);
}
int FileTestMain(const FileTest::Options &opts) {
std::unique_ptr<FileLineReader> reader(
new FileLineReader(opts.path));
if (!reader->is_open()) {
fprintf(stderr, "Could not open file %s: %s.\n", opts.path,
strerror(errno));
return 1;
}
FileTest t(std::move(reader), opts.comment_callback, opts.is_kas_test);
bool failed = false;
while (true) {
FileTest::ReadResult ret = t.ReadNext();
if (ret == FileTest::kReadError) {
return 1;
} else if (ret == FileTest::kReadEOF) {
break;
}
bool result = opts.callback(&t, opts.arg);
if (t.HasAttribute("Error")) {
if (result) {
t.PrintLine("Operation unexpectedly succeeded.");
failed = true;
continue;
}
uint32_t err = ERR_peek_error();
if (ERR_reason_error_string(err) != t.GetAttributeOrDie("Error")) {
t.PrintLine("Unexpected error; wanted '%s', got '%s'.",
t.GetAttributeOrDie("Error").c_str(),
ERR_reason_error_string(err));
failed = true;
ERR_clear_error();
continue;
}
ERR_clear_error();
} else if (!result) {
// In case the test itself doesn't print output, print something so the
// line number is reported.
t.PrintLine("Test failed");
ERR_print_errors_fp(stderr);
failed = true;
continue;
}
}
if (!opts.silent && !failed) {
printf("PASS\n");
}
return failed ? 1 : 0;
}
void FileTest::SkipCurrent() {
ClearTest();
}

View File

@@ -0,0 +1,261 @@
// Copyright (c) 2015, Google Inc.
// SPDX-License-Identifier: ISC
#ifndef OPENSSL_HEADER_CRYPTO_TEST_FILE_TEST_H
#define OPENSSL_HEADER_CRYPTO_TEST_FILE_TEST_H
#include <openssl/base.h>
#include <stdint.h>
OPENSSL_MSVC_PRAGMA(warning(push))
OPENSSL_MSVC_PRAGMA(warning(disable : 4702))
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
OPENSSL_MSVC_PRAGMA(warning(pop))
// File-based test framework.
//
// This module provides a file-based test framework. The file format is based on
// that of OpenSSL upstream's evp_test and BoringSSL's aead_test. NIST CAVP test
// vector files are also supported. Each input file is a sequence of attributes,
// instructions and blank lines.
//
// Each attribute has the form:
//
// Name = Value
//
// Instructions are enclosed in square brackets and may appear without a value:
//
// [Name = Value]
//
// or
//
// [Name]
//
// Commas in instruction lines are treated as separate instructions. Thus this:
//
// [Name1,Name2]
//
// is the same as:
//
// [Name1]
// [Name2]
//
// Either '=' or ':' may be used to delimit the name from the value. Both the
// name and value have leading and trailing spaces stripped.
//
// Each file contains a number of instruction blocks and test cases.
//
// An instruction block is a sequence of instructions followed by a blank line.
// Instructions apply to all test cases following its appearance, until the next
// instruction block. Instructions are unordered.
//
// A test is a sequence of one or more attributes followed by a blank line. For
// tests that process multiple kinds of test cases, the first attribute is
// parsed out as the test's type and parameter. Otherwise, attributes are
// unordered. The first attribute is also included in the set of attributes, so
// tests which do not dispatch may ignore this mechanism.
//
// Additional blank lines and lines beginning with # are ignored.
//
// Functions in this module freely output to |stderr| on failure. Tests should
// also do so, and it is recommended they include the corresponding test's line
// number in any output. |PrintLine| does this automatically.
//
// Each attribute in a test and all instructions applying to it must be
// consumed. When a test completes, if any attributes or insturctions haven't
// been processed, the framework reports an error.
class FileTest;
typedef bool (*FileTestFunc)(FileTest *t, void *arg);
class FileTest {
public:
enum ReadResult {
kReadSuccess,
kReadEOF,
kReadError,
};
class LineReader {
public:
virtual ~LineReader() {}
virtual ReadResult ReadLine(char *out, size_t len) = 0;
};
struct Options {
// path is the path to the input file.
const char *path = nullptr;
// callback is called for each test. It should get the parameters from this
// object and signal any errors by returning false.
FileTestFunc callback = nullptr;
// arg is an opaque pointer that is passed to |callback|.
void *arg = nullptr;
// silent suppressed the "PASS" string that is otherwise printed after
// successful runs.
bool silent = false;
// comment_callback is called after each comment in the input is parsed.
std::function<void(const std::string&)> comment_callback;
// is_kas_test is true if a NIST “KAS” test is being parsed. These tests
// are inconsistent with the other NIST files to such a degree that they
// need their own boolean.
bool is_kas_test = false;
};
explicit FileTest(std::unique_ptr<LineReader> reader,
std::function<void(const std::string &)> comment_callback,
bool is_kas_test);
~FileTest();
// ReadNext reads the next test from the file. It returns |kReadSuccess| if
// successfully reading a test and |kReadEOF| at the end of the file. On
// error or if the previous test had unconsumed attributes, it returns
// |kReadError|.
ReadResult ReadNext();
// PrintLine is a variant of printf which prepends the line number and appends
// a trailing newline.
void PrintLine(const char *format, ...) OPENSSL_PRINTF_FORMAT_FUNC(2, 3);
unsigned start_line() const { return start_line_; }
// GetType returns the name of the first attribute of the current test.
const std::string &GetType();
// GetParameter returns the value of the first attribute of the current test.
const std::string &GetParameter();
// HasAttribute returns true if the current test has an attribute named |key|.
bool HasAttribute(const std::string &key);
// GetAttribute looks up the attribute with key |key|. It sets |*out_value| to
// the value and returns true if it exists and returns false with an error to
// |stderr| otherwise.
bool GetAttribute(std::string *out_value, const std::string &key);
// GetAttributeOrDie looks up the attribute with key |key| and aborts if it is
// missing. It should only be used after a |HasAttribute| call.
const std::string &GetAttributeOrDie(const std::string &key);
// IgnoreAttribute marks the attribute with key |key| as used.
void IgnoreAttribute(const std::string &key) { HasAttribute(key); }
// GetBytes looks up the attribute with key |key| and decodes it as a byte
// string. On success, it writes the result to |*out| and returns
// true. Otherwise it returns false with an error to |stderr|. The value may
// be either a hexadecimal string or a quoted ASCII string. It returns true on
// success and returns false with an error to |stderr| on failure.
bool GetBytes(std::vector<uint8_t> *out, const std::string &key);
// AtNewInstructionBlock returns true if the current test was immediately
// preceded by an instruction block.
bool IsAtNewInstructionBlock() const;
// HasInstruction returns true if the current test has an instruction.
bool HasInstruction(const std::string &key);
// IgnoreInstruction marks the instruction with key |key| as used.
void IgnoreInstruction(const std::string &key) { HasInstruction(key); }
// IgnoreAllUnusedInstructions disables checking for unused instructions.
void IgnoreAllUnusedInstructions();
// GetInstruction looks up the instruction with key |key|. It sets
// |*out_value| to the value (empty string if the instruction has no value)
// and returns true if it exists and returns false with an error to |stderr|
// otherwise.
bool GetInstruction(std::string *out_value, const std::string &key);
// GetInstructionOrDie looks up the instruction with key |key| and aborts if
// it is missing. It should only be used after a |HasInstruction| call.
const std::string &GetInstructionOrDie(const std::string &key);
// GetInstructionBytes behaves like GetBytes, but looks up the corresponding
// instruction.
bool GetInstructionBytes(std::vector<uint8_t> *out, const std::string &key);
// CurrentTestToString returns the file content parsed for the current test.
// If the current test was preceded by an instruction block, the return test
// case is preceded by the instruction block and a single blank line. All
// other blank or comment lines are omitted.
const std::string &CurrentTestToString() const;
// InjectInstruction adds a key value pair to the most recently parsed set of
// instructions.
void InjectInstruction(const std::string &key, const std::string &value);
// SkipCurrent passes the current test case. Unused attributes are ignored.
void SkipCurrent();
private:
void ClearTest();
void ClearInstructions();
void OnKeyUsed(const std::string &key);
void OnInstructionUsed(const std::string &key);
bool ConvertToBytes(std::vector<uint8_t> *out, const std::string &value);
std::unique_ptr<LineReader> reader_;
// line_ is the number of lines read.
unsigned line_ = 0;
// start_line_ is the line number of the first attribute of the test.
unsigned start_line_ = 0;
// type_ is the name of the first attribute of the test.
std::string type_;
// parameter_ is the value of the first attribute.
std::string parameter_;
// attribute_count_ maps unsuffixed attribute names to the number of times
// they have occurred so far.
std::map<std::string, size_t> attribute_count_;
// attributes_ contains all attributes in the test, including the first.
std::map<std::string, std::string> attributes_;
// instructions_ contains all instructions in scope for the test.
std::map<std::string, std::string> instructions_;
// unused_attributes_ is the set of attributes that have not been queried.
std::set<std::string> unused_attributes_;
// unused_instructions_ is the set of instructions that have not been queried.
std::set<std::string> unused_instructions_;
std::string current_test_;
bool is_at_new_instruction_block_ = false;
bool seen_non_comment_ = false;
bool is_kas_test_ = false;
// comment_callback_, if set, is a callback function that is called with the
// contents of each comment as they are parsed.
std::function<void(const std::string&)> comment_callback_;
FileTest(const FileTest &) = delete;
FileTest &operator=(const FileTest &) = delete;
};
// FileTestMain runs a file-based test out of |path| and returns an exit code
// suitable to return out of |main|. |run_test| should return true on pass and
// false on failure. FileTestMain also implements common handling of the 'Error'
// attribute. A test with that attribute is expected to fail. The value of the
// attribute is the reason string of the expected OpenSSL error code.
//
// Tests are guaranteed to run serially and may affect global state if need be.
// It is legal to use "tests" which, for example, import a private key into a
// list of keys. This may be used to initialize a shared set of keys for many
// tests. However, if one test fails, the framework will continue to run
// subsequent tests.
int FileTestMain(FileTestFunc run_test, void *arg, const char *path);
// FileTestMain accepts a larger number of options via a struct.
int FileTestMain(const FileTest::Options &opts);
// FileTestGTest behaves like FileTestMain, but for GTest. |path| must be the
// name of a test file embedded in the test binary.
void FileTestGTest(const char *path, std::function<void(FileTest *)> run_test);
#endif // OPENSSL_HEADER_CRYPTO_TEST_FILE_TEST_H

View File

@@ -0,0 +1,96 @@
// Copyright (c) 2017, Google Inc.
// SPDX-License-Identifier: ISC
#include "file_test.h"
#include <assert.h>
#include <string.h>
#include <memory>
#include <string>
#include <utility>
#include <gtest/gtest.h>
#include <openssl/err.h>
std::string GetTestData(const char *path);
class StringLineReader : public FileTest::LineReader {
public:
explicit StringLineReader(const std::string &data)
: data_(data), offset_(0) {}
FileTest::ReadResult ReadLine(char *out, size_t len) override {
assert(len > 0);
if (offset_ == data_.size()) {
return FileTest::kReadEOF;
}
size_t idx = data_.find('\n', offset_);
if (idx == std::string::npos) {
idx = data_.size();
} else {
idx++; // Include the newline.
}
if (idx - offset_ > len - 1) {
ADD_FAILURE() << "Line too long.";
return FileTest::kReadError;
}
memcpy(out, data_.data() + offset_, idx - offset_);
out[idx - offset_] = '\0';
offset_ = idx;
return FileTest::kReadSuccess;
}
private:
std::string data_;
size_t offset_;
StringLineReader(const StringLineReader &) = delete;
StringLineReader &operator=(const StringLineReader &) = delete;
};
void FileTestGTest(const char *path, std::function<void(FileTest *)> run_test) {
std::unique_ptr<StringLineReader> reader(
new StringLineReader(GetTestData(path)));
FileTest t(std::move(reader), nullptr, false);
while (true) {
switch (t.ReadNext()) {
case FileTest::kReadError:
ADD_FAILURE() << "Error reading test.";
return;
case FileTest::kReadEOF:
return;
case FileTest::kReadSuccess:
break;
}
const testing::TestResult *test_result =
testing::UnitTest::GetInstance()->current_test_info()->result();
int before_part_count = test_result->total_part_count();
SCOPED_TRACE(testing::Message() << path << ", line " << t.start_line());
run_test(&t);
// Check for failures from the most recent test.
bool failed = false;
for (int i = before_part_count; i < test_result->total_part_count(); i++) {
if (test_result->GetTestPartResult(i).failed()) {
failed = true;
break;
}
}
// Clean up the error queue for the next test, reporting it on failure.
if (failed) {
ERR_print_errors_fp(stdout);
} else {
ERR_clear_error();
}
}
}

View File

@@ -0,0 +1,154 @@
// Copyright (c) 2023, Google Inc.
// SPDX-License-Identifier: ISC
#include "file_util.h"
#include <stdlib.h>
#if defined(OPENSSL_WINDOWS)
OPENSSL_MSVC_PRAGMA(warning(push, 3))
#include <windows.h>
OPENSSL_MSVC_PRAGMA(warning(pop))
#else
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#include <openssl/rand.h>
#include "test_util.h"
#if defined(OPENSSL_WINDOWS)
static void PrintLastError(const char *s) {
DWORD error = GetLastError();
char *buffer;
DWORD len = FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, error, 0,
reinterpret_cast<char *>(&buffer), 0, nullptr);
std::string msg = "unknown error";
if (len > 0) {
msg.assign(buffer, len);
while (!msg.empty() && (msg.back() == '\r' || msg.back() == '\n')) {
msg.resize(msg.size() - 1);
}
}
LocalFree(buffer);
fprintf(stderr, "%s: %s (0x%lx)\n", s, msg.c_str(), error);
}
#endif // OPENSSL_WINDOWS
// GetTempDir returns the path to the temporary directory, or the empty string
// on error. On success, the result will include the directory separator.
static std::string GetTempDir() {
#if defined(OPENSSL_WINDOWS)
char buf[MAX_PATH + 1];
DWORD len = GetTempPathA(sizeof(buf), buf);
return std::string(buf, len);
#else
const char *tmpdir = getenv("TMPDIR");
if (tmpdir != nullptr && *tmpdir != '\0') {
std::string ret = tmpdir;
if (ret.back() != '/') {
ret.push_back('/');
}
return ret;
}
#if defined(OPENSSL_ANDROID)
return "/data/local/tmp/";
#else
return "/tmp/";
#endif
#endif
}
bool SkipTempFileTests() {
#if defined(OPENSSL_ANDROID)
// When running in an APK context, /data/local/tmp is unreadable. Android
// versions before https://android-review.googlesource.com/c/1821337 do not
// set TMPDIR to a suitable replacement.
if (getenv("TMPDIR") == nullptr) {
static bool should_skip = [] {
TemporaryFile file;
return !file.Init();
}();
if (should_skip) {
fprintf(stderr, "Skipping tests with temporary files.\n");
return true;
}
}
#endif
return false;
}
TemporaryFile::~TemporaryFile() {
#if defined(OPENSSL_WINDOWS)
if (!path_.empty() && !DeleteFileA(path_.c_str())) {
PrintLastError("Could not delete file");
}
#else
if (!path_.empty() && unlink(path_.c_str()) != 0) {
perror("Could not delete file");
}
#endif
}
bool TemporaryFile::Init(bssl::Span<const uint8_t> content) {
std::string temp_dir = GetTempDir();
if (temp_dir.empty()) {
return false;
}
#if defined(OPENSSL_WINDOWS)
char path[MAX_PATH];
if (GetTempFileNameA(temp_dir.c_str(), "bssl",
/*uUnique=*/0, path) == 0) {
PrintLastError("Could not create temporary");
return false;
}
path_ = path;
#else
std::string path = temp_dir + "bssl_tmp_file.XXXXXX";
// TODO(davidben): Use |path.data()| when we require C++17.
mode_t prev_umask = umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
int fd = mkstemp(&path[0]);
umask(prev_umask);
if (fd < 0) {
perror("Could not create temporary file");
return false;
}
close(fd);
path_ = std::move(path);
#endif
ScopedFILE file = Open("wb");
if (file == nullptr) {
perror("Could not open temporary file");
return false;
}
if (!content.empty() &&
fwrite(content.data(), content.size(), /*nitems=*/1, file.get()) != 1) {
perror("Could not write temporary file");
return false;
}
return true;
}
ScopedFILE TemporaryFile::Open(const char *mode) const {
if (path_.empty()) {
return nullptr;
}
return ScopedFILE(fopen(path_.c_str(), mode));
}
ScopedFD TemporaryFile::OpenFD(int flags) const {
if (path_.empty()) {
return ScopedFD();
}
#if defined(OPENSSL_WINDOWS)
return ScopedFD(_open(path_.c_str(), flags));
#else
return ScopedFD(open(path_.c_str(), flags));
#endif
}

View File

@@ -0,0 +1,114 @@
// Copyright (c) 2023, Google Inc.
// SPDX-License-Identifier: ISC
#ifndef OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H
#define OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H
#include <stdio.h>
#include <memory>
#include <string>
#include <utility>
#include <openssl/span.h>
#if defined(OPENSSL_WINDOWS)
#include <io.h>
#else
#include <unistd.h>
#endif
struct FileDeleter {
void operator()(FILE *f) const {
if (f != nullptr) {
fclose(f);
}
}
};
using ScopedFILE = std::unique_ptr<FILE, FileDeleter>;
class ScopedFD {
public:
ScopedFD() = default;
explicit ScopedFD(int fd) : fd_(fd) {}
~ScopedFD() { reset(); }
ScopedFD(ScopedFD &&other) noexcept { *this = std::move(other); }
ScopedFD &operator=(ScopedFD&& other) {
reset(other.release());
return *this;
}
ScopedFD(const ScopedFD &other) = delete;
ScopedFD &operator=(ScopedFD& other) = delete;
bool is_valid() const { return fd_ >= 0; }
int get() const { return fd_; }
int release() {
int old_fd = fd_;
fd_ = -1;
return old_fd;
}
void reset(int fd = -1) {
if (is_valid()) {
#if defined(OPENSSL_WINDOWS)
_close(fd_);
#else
close(fd_);
#endif
}
fd_ = fd;
}
private:
int fd_ = -1;
};
// SkipTempFileTests returns true and prints a warning if tests involving
// temporary files should be skipped because of platform issues.
bool SkipTempFileTests();
// TemporaryFile manages a temporary file for testing.
class TemporaryFile {
public:
TemporaryFile() = default;
~TemporaryFile();
TemporaryFile(TemporaryFile&& other) noexcept { *this = std::move(other); }
TemporaryFile& operator=(TemporaryFile&&other) {
// Ensure |path_| is empty so it doesn't try to delete the File.
auto old_other_path = other.path_;
other.path_ = {};
path_ = old_other_path;
return *this;
}
TemporaryFile(const TemporaryFile&) = delete;
TemporaryFile& operator=(const TemporaryFile&) = delete;
// Init initializes the temporary file with the specified content. It returns
// true on success and false on error. On error, callers should call
// |IgnoreTempFileErrors| to determine whether to ignore the error.
bool Init(bssl::Span<const uint8_t> content = {});
bool Init(const std::string &content) {
return Init(bssl::MakeConstSpan(
reinterpret_cast<const uint8_t *>(content.data()), content.size()));
}
// Open opens the file as a |FILE| with the specified mode.
ScopedFILE Open(const char *mode) const;
// Open opens the file as a file descriptor with the specified flags.
ScopedFD OpenFD(int flags) const;
// path returns the path to the temporary file.
const std::string &path() const { return path_; }
private:
std::string path_;
};
#endif // OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2016, Google Inc.
// SPDX-License-Identifier: ISC
#include <stdio.h>
#include <string.h>
#include <gtest/gtest.h>
#include <openssl/rand.h>
#include "abi_test.h"
#include "gtest_main.h"
#include "../internal.h"
#include "../ube/vm_ube_detect.h"
int main(int argc, char **argv) {
#if defined(OPENSSL_LINUX) && defined(AWSLC_VM_UBE_TESTING)
if (1 != HAZMAT_init_sysgenid_file()) {
abort();
}
#endif
testing::InitGoogleTest(&argc, argv);
bssl::SetupGoogleTest();
bool unwind_tests = true;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--no_unwind_tests") == 0) {
unwind_tests = false;
}
}
if (unwind_tests) {
abi_test::EnableUnwindTests();
}
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2017, Google Inc.
// SPDX-License-Identifier: ISC
#ifndef OPENSSL_HEADER_CRYPTO_TEST_GTEST_MAIN_H
#define OPENSSL_HEADER_CRYPTO_TEST_GTEST_MAIN_H
#include <stdio.h>
#include <stdlib.h>
#include <gtest/gtest.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#if defined(OPENSSL_WINDOWS)
OPENSSL_MSVC_PRAGMA(warning(push, 3))
#include <winsock2.h>
OPENSSL_MSVC_PRAGMA(warning(pop))
#else
#include <signal.h>
#endif
BSSL_NAMESPACE_BEGIN
class ErrorTestEventListener : public testing::EmptyTestEventListener {
public:
ErrorTestEventListener() {}
~ErrorTestEventListener() override {}
void OnTestEnd(const testing::TestInfo &test_info) override {
if (test_info.result()->Failed()) {
// The test failed. Print any errors left in the error queue.
ERR_print_errors_fp(stdout);
} else {
// The test succeeded, so any failed operations are expected. Clear the
// error queue without printing.
ERR_clear_error();
}
}
};
// SetupGoogleTest should be called by the test runner after
// testing::InitGoogleTest has been called and before RUN_ALL_TESTS.
inline void SetupGoogleTest() {
CRYPTO_library_init();
#if defined(OPENSSL_WINDOWS)
// Initialize Winsock.
WORD wsa_version = MAKEWORD(2, 2);
WSADATA wsa_data;
int wsa_err = WSAStartup(wsa_version, &wsa_data);
if (wsa_err != 0) {
fprintf(stderr, "WSAStartup failed: %d\n", wsa_err);
exit(1);
}
if (wsa_data.wVersion != wsa_version) {
fprintf(stderr, "Didn't get expected version: %x\n", wsa_data.wVersion);
exit(1);
}
#else
// Some tests create pipes. We check return values, so avoid being killed by
// |SIGPIPE|.
signal(SIGPIPE, SIG_IGN);
#endif
testing::UnitTest::GetInstance()->listeners().Append(
new ErrorTestEventListener);
}
BSSL_NAMESPACE_END
#endif // OPENSSL_HEADER_CRYPTO_TEST_GTEST_MAIN_H

View File

@@ -0,0 +1,132 @@
// Copyright (c) 2014, Google Inc.
// SPDX-License-Identifier: ISC
#include <openssl/base.h>
#if defined(__GLIBC__) && !defined(__UCLIBC__)
#define OPENSSL_GLIBC
#endif
// This file isn't built on ARM or Aarch64 because we link statically in those
// builds and trying to override malloc in a static link doesn't work. It also
// requires glibc. It's also disabled on ASan builds as this interferes with
// ASan's malloc interceptor.
//
// TODO(davidben): See if this and ASan's and MSan's interceptors can be made to
// coexist.
#if defined(__linux__) && defined(OPENSSL_GLIBC) && !defined(OPENSSL_ARM) && \
!defined(OPENSSL_AARCH64) && !defined(OPENSSL_ASAN) && \
!defined(OPENSSL_MSAN) && !defined(OPENSSL_TSAN)
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <new>
// This file defines overrides for the standard allocation functions that allow
// a given allocation to be made to fail for testing. If the program is run
// with MALLOC_NUMBER_TO_FAIL set to a base-10 number then that allocation will
// return NULL. If MALLOC_BREAK_ON_FAIL is also defined then the allocation
// will signal SIGTRAP rather than return NULL.
//
// This code is not thread safe.
static uint64_t current_malloc_count = 0;
static uint64_t malloc_number_to_fail = 0;
static bool failure_enabled = false, break_on_fail = false, in_call = false;
extern "C" {
// These are other names for the standard allocation functions.
extern void *__libc_malloc(size_t size);
extern void *__libc_calloc(size_t num_elems, size_t size);
extern void *__libc_realloc(void *ptr, size_t size);
}
static void exit_handler(void) {
if (failure_enabled && current_malloc_count > malloc_number_to_fail) {
_exit(88);
}
}
static void cpp_new_handler() {
// Return to try again. It won't fail a second time.
return;
}
// should_fail_allocation returns true if the current allocation should fail.
static bool should_fail_allocation() {
static bool init = false;
if (in_call) {
return false;
}
in_call = true;
if (!init) {
const char *env = getenv("MALLOC_NUMBER_TO_FAIL");
if (env != NULL && env[0] != 0) {
char *endptr;
malloc_number_to_fail = strtoull(env, &endptr, 10);
if (*endptr == 0) {
failure_enabled = true;
atexit(exit_handler);
std::set_new_handler(cpp_new_handler);
}
}
break_on_fail = (NULL != getenv("MALLOC_BREAK_ON_FAIL"));
init = true;
}
in_call = false;
if (!failure_enabled) {
return false;
}
bool should_fail = (current_malloc_count == malloc_number_to_fail);
current_malloc_count++;
if (should_fail && break_on_fail) {
raise(SIGTRAP);
}
return should_fail;
}
extern "C" {
void *malloc(size_t size) {
if (should_fail_allocation()) {
errno = ENOMEM;
return NULL;
}
return __libc_malloc(size);
}
void *calloc(size_t num_elems, size_t size) {
if (should_fail_allocation()) {
errno = ENOMEM;
return NULL;
}
return __libc_calloc(num_elems, size);
}
void *realloc(void *ptr, size_t size) {
if (should_fail_allocation()) {
errno = ENOMEM;
return NULL;
}
return __libc_realloc(ptr, size);
}
} // extern "C"
#endif // defined(linux) && GLIBC && !ARM && !AARCH64 && !ASAN && !TSAN

View File

@@ -0,0 +1,389 @@
// Copyright (c) 2015, Google Inc.
// SPDX-License-Identifier: ISC
#include "test_util.h"
#include <fstream>
#include <ostream>
#include <inttypes.h>
#include <openssl/bn.h>
#include <openssl/err.h>
#include <thread>
#if !defined(OPENSSL_WINDOWS)
#include <sys/wait.h>
#endif
#include <inttypes.h>
#include <openssl/err.h>
#include "../internal.h"
#include "../ube/fork_ube_detect.h"
#include "openssl/pem.h"
#include "openssl/rand.h"
void hexdump(FILE *fp, const char *msg, const void *in, size_t len) {
const uint8_t *data = reinterpret_cast<const uint8_t *>(in);
fputs(msg, fp);
for (size_t i = 0; i < len; i++) {
fprintf(fp, "%02x", data[i]);
}
fputs("\n", fp);
}
std::ostream &operator<<(std::ostream &os, const Bytes &in) {
if (in.span_.empty()) {
return os << "<empty Bytes>";
}
// Print a byte slice as hex.
os << EncodeHex(in.span_);
return os;
}
bool DecodeHex(std::vector<uint8_t> *out, const std::string &in) {
out->clear();
if (in.size() % 2 != 0) {
return false;
}
out->reserve(in.size() / 2);
for (size_t i = 0; i < in.size(); i += 2) {
uint8_t hi, lo;
if (!OPENSSL_fromxdigit(&hi, in[i]) ||
!OPENSSL_fromxdigit(&lo, in[i + 1])) {
return false;
}
out->push_back((hi << 4) | lo);
}
return true;
}
std::vector<uint8_t> HexToBytes(const char *str) {
std::vector<uint8_t> ret;
if (!DecodeHex(&ret, str)) {
abort();
}
return ret;
}
std::string EncodeHex(bssl::Span<const uint8_t> in) {
static const char kHexDigits[] = "0123456789abcdef";
std::string ret;
ret.reserve(in.size() * 2);
for (uint8_t b : in) {
ret += kHexDigits[b >> 4];
ret += kHexDigits[b & 0xf];
}
return ret;
}
testing::AssertionResult ErrorEquals(uint32_t err, int lib, int reason) {
if (ERR_GET_LIB(err) == lib && ERR_GET_REASON(err) == reason) {
return testing::AssertionSuccess();
}
char buf[128], expected[128];
return testing::AssertionFailure()
<< "Got \"" << ERR_error_string_n(err, buf, sizeof(buf))
<< "\", wanted \""
<< ERR_error_string_n(ERR_PACK(lib, reason), expected,
sizeof(expected))
<< "\"";
}
// CertFromPEM parses the given, NUL-terminated pem block and returns an
// |X509*|.
bssl::UniquePtr<X509> CertFromPEM(const char *pem) {
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem, strlen(pem)));
if (!bio) {
return nullptr;
}
return bssl::UniquePtr<X509>(
PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
}
bssl::UniquePtr<RSA> RSAFromPEM(const char *pem) {
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem, strlen(pem)));
if (!bio) {
return nullptr;
}
return bssl::UniquePtr<RSA>(
PEM_read_bio_RSAPrivateKey(bio.get(), nullptr, nullptr, nullptr));
}
bssl::UniquePtr<X509> MakeTestCert(const char *issuer,
const char *subject, EVP_PKEY *key,
bool is_ca) {
bssl::UniquePtr<X509> cert(X509_new());
if (!cert || //
!X509_set_version(cert.get(), X509_VERSION_3) ||
!X509_NAME_add_entry_by_txt(
X509_get_issuer_name(cert.get()), "CN", MBSTRING_UTF8,
reinterpret_cast<const uint8_t *>(issuer), -1, -1, 0) ||
!X509_NAME_add_entry_by_txt(
X509_get_subject_name(cert.get()), "CN", MBSTRING_UTF8,
reinterpret_cast<const uint8_t *>(subject), -1, -1, 0) ||
!X509_set_pubkey(cert.get(), key) ||
!ASN1_TIME_adj(X509_getm_notBefore(cert.get()), kReferenceTime, -1, 0) ||
!ASN1_TIME_adj(X509_getm_notAfter(cert.get()), kReferenceTime, 1, 0)) {
return nullptr;
}
bssl::UniquePtr<BASIC_CONSTRAINTS> bc(BASIC_CONSTRAINTS_new());
if (!bc) {
return nullptr;
}
bc->ca = is_ca ? ASN1_BOOLEAN_TRUE : ASN1_BOOLEAN_FALSE;
if (!X509_add1_ext_i2d(cert.get(), NID_basic_constraints, bc.get(),
/*crit=*/1, /*flags=*/0)) {
return nullptr;
}
return cert;
}
bssl::UniquePtr<STACK_OF(X509)> CertsToStack(
const std::vector<X509 *> &certs) {
bssl::UniquePtr<STACK_OF(X509)> stack(sk_X509_new_null());
if (!stack) {
return nullptr;
}
for (auto cert : certs) {
if (!bssl::PushToStack(stack.get(), bssl::UpRef(cert))) {
return nullptr;
}
}
return stack;
}
bool PEM_to_DER(const char *pem_str, uint8_t **out_der, long *out_der_len) {
char *name = nullptr;
char *header = nullptr;
// Create BIO from memory
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem_str, strlen(pem_str)));
if (!bio) {
return false;
}
// Read PEM into DER
if (PEM_read_bio(bio.get(), &name, &header, out_der, out_der_len) <= 0) {
OPENSSL_free(name);
OPENSSL_free(header);
OPENSSL_free(*out_der);
*out_der = nullptr;
return false;
}
OPENSSL_free(name);
OPENSSL_free(header);
return true;
}
#if defined(OPENSSL_WINDOWS)
size_t createTempFILEpath(char buffer[PATH_MAX]) {
// On Windows, tmpfile() may attempt to create temp files in the root directory
// of the drive, which requires Admin privileges, resulting in test failure.
char pathname[PATH_MAX];
if(0 == GetTempPathA(PATH_MAX, pathname)) {
return 0;
}
return GetTempFileNameA(pathname, "awslctest", 0, buffer);
}
size_t createTempDirPath(char buffer[PATH_MAX]) {
char temp_path[PATH_MAX];
union {
uint8_t bytes[8];
uint64_t value;
} random_bytes;
// Get the temporary path
if (0 == GetTempPathA(PATH_MAX, temp_path)) {
return 0;
}
if (!RAND_bytes(random_bytes.bytes, sizeof(random_bytes.bytes))) {
return 0;
}
int written = snprintf(buffer, PATH_MAX, "%s\\awslctest_%" PRIX64, temp_path, random_bytes.value);
// Check for truncation of dirname
if (written < 0 || written >= PATH_MAX) {
return 0;
}
if (!CreateDirectoryA(buffer, NULL)) {
return 0;
}
return (size_t)written;
}
FILE* createRawTempFILE() {
char filename[PATH_MAX];
if(createTempFILEpath(filename) == 0) {
return nullptr;
}
return fopen(filename, "w+b");
}
#else
#include <cstdlib>
#include <unistd.h>
size_t createTempFILEpath(char buffer[PATH_MAX]) {
snprintf(buffer, PATH_MAX, "awslcTestTmpFileXXXXXX");
int fd = mkstemp(buffer);
if (fd == -1) {
return 0;
}
close(fd);
return strnlen(buffer, PATH_MAX);
}
size_t createTempDirPath(char buffer[PATH_MAX]) {
snprintf(buffer, PATH_MAX, "/tmp/awslcTestDirXXXXXX");
if (mkdtemp(buffer) == NULL) {
return 0;
}
return strnlen(buffer, PATH_MAX);
}
FILE* createRawTempFILE() {
return tmpfile();
}
#endif
TempFILE createTempFILE() {
return TempFILE(createRawTempFILE());
}
void CustomDataFree(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
int index, long argl, void *argp) {
free(ptr);
}
bool osIsAmazonLinux(void) {
bool res = false;
#if defined(OPENSSL_LINUX)
// Per https://docs.aws.amazon.com/linux/al2023/ug/naming-and-versioning.html.
std::ifstream amazonLinuxSpecificFile("/etc/amazon-linux-release-cpe");
if (amazonLinuxSpecificFile.is_open()) {
// Definitely on Amazon Linux.
amazonLinuxSpecificFile.close();
return true;
}
// /etc/amazon-linux-release-cpe was introduced in AL2023. For earlier, parse
// and read /etc/system-release-cpe.
std::ifstream osRelease("/etc/system-release-cpe");
if (!osRelease.is_open()) {
return false;
}
std::string line;
while (std::getline(osRelease, line)) {
// AL2:
// $ cat /etc/system-release-cpe
// cpe:2.3:o:amazon:amazon_linux:2
//
// AL2023:
// $ cat /etc/system-release-cpe
// cpe:2.3:o:amazon:amazon_linux:2023
if (line.find("amazon") != std::string::npos) {
res = true;
} else if (line.find("amazon_linux") != std::string::npos) {
res = true;
}
}
osRelease.close();
#endif
return res;
}
bool threadTest(const size_t numberOfThreads, std::function<void(bool*)> testFunc) {
bool res = true;
#if defined(OPENSSL_THREADS)
// char to be able to pass-as-reference.
std::vector<char> retValueVec(numberOfThreads, 0);
std::vector<std::thread> threadVec;
for (size_t i = 0; i < numberOfThreads; i++) {
threadVec.emplace_back(testFunc, reinterpret_cast<bool*>(&retValueVec[i]));
}
for (auto& thread : threadVec) {
thread.join();
}
for (size_t i = 0; i < numberOfThreads; i++) {
if (!static_cast<bool>(retValueVec[i])) {
fprintf(stderr, "Thread %lu failed\n", (long unsigned int) i);
res = false;
}
}
#else
testFunc(&res);
#endif
return res;
}
bool forkAndRunTest(std::function<bool()> child_func,
std::function<bool()> parent_func) {
#if defined(OPENSSL_WINDOWS)
// fork() is not supported on Windows. We could potentially add support for
// the CreateProcess API at some point.
return false;
#else
pid_t pid = fork();
if (pid == 0) { // Child
bool success = child_func();
exit(success ? 0 : 1);
} else if (pid > 0) { // Parent
bool parent_success = parent_func();
int status;
waitpid(pid, &status, 0);
return parent_success && WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
// Fork failed
return false;
#endif
}
void maybeDisableSomeForkUbeDetectMechanisms(void) {
if (getenv("AWSLC_IGNORE_FORK_UBE_DETECTION")) {
CRYPTO_fork_detect_ignore_wipeonfork_FOR_TESTING();
CRYPTO_fork_detect_ignore_inheritzero_FOR_TESTING();
}
}
bool runtimeEmulationIsIntelSde(void) {
if (getenv("RUNTIME_EMULATION_SDE")) {
return true;
}
return false;
}
bool addressSanitizerIsEnabled(void) {
#if defined(OPENSSL_ASAN)
return true;
#else
return false;
#endif
}
bssl::UniquePtr<BIGNUM> HexToBIGNUM(const char *hex) {
BIGNUM *bn = nullptr;
BN_hex2bn(&bn, hex);
return bssl::UniquePtr<BIGNUM>(bn);
}

View File

@@ -0,0 +1,166 @@
// Copyright (c) 2015, Google Inc.
// SPDX-License-Identifier: ISC
#ifndef OPENSSL_HEADER_CRYPTO_TEST_TEST_UTIL_H
#define OPENSSL_HEADER_CRYPTO_TEST_TEST_UTIL_H
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <iosfwd>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <openssl/span.h>
#include "../internal.h"
// hexdump writes |msg| to |fp| followed by the hex encoding of |len| bytes
// from |in|.
void hexdump(FILE *fp, const char *msg, const void *in, size_t len);
// Bytes is a wrapper over a byte slice which may be compared for equality. This
// allows it to be used in EXPECT_EQ macros.
struct Bytes {
Bytes(const uint8_t *data_arg, size_t len_arg) : span_(data_arg, len_arg) {}
Bytes(const char *data_arg, size_t len_arg)
: span_(reinterpret_cast<const uint8_t *>(data_arg), len_arg) {}
explicit Bytes(const char *str)
: span_(reinterpret_cast<const uint8_t *>(str), strlen(str)) {}
explicit Bytes(const std::string &str)
: span_(reinterpret_cast<const uint8_t *>(str.data()), str.size()) {}
explicit Bytes(bssl::Span<const uint8_t> span) : span_(span) {}
bssl::Span<const uint8_t> span_;
};
inline bool operator==(const Bytes &a, const Bytes &b) {
return a.span_ == b.span_;
}
inline bool operator!=(const Bytes &a, const Bytes &b) { return !(a == b); }
std::ostream &operator<<(std::ostream &os, const Bytes &in);
// DecodeHex decodes |in| from hexadecimal and writes the output to |out|. It
// returns true on success and false if |in| is not a valid hexadecimal byte
// string.
bool DecodeHex(std::vector<uint8_t> *out, const std::string &in);
// HexToBytes decodes |str| from hexadecimal and returns a new vector of bytes.
// If |str| is invalid it aborts.
std::vector<uint8_t> HexToBytes(const char *str);
// EncodeHex returns |in| encoded in hexadecimal.
std::string EncodeHex(bssl::Span<const uint8_t> in);
// CertFromPEM parses the given, NUL-terminated pem block and returns an
// |X509*|.
bssl::UniquePtr<X509> CertFromPEM(const char *pem);
// CertsToStack converts a vector of |X509*| to an OpenSSL STACK_OF(X509),
// bumping the reference counts for each certificate in question.
bssl::UniquePtr<STACK_OF(X509)> CertsToStack(const std::vector<X509 *> &certs);
// RSAFromPEM parses the given, NUL-terminated pem block and returns an
// |RSA*|.
bssl::UniquePtr<RSA> RSAFromPEM(const char *pem);
// Helper function that:
// 1. Creates a BIO
// 2. Reads the provided |pem_string| into bio
// 3. Reads the PEM into DER encoding
// 4. Returns the DER data and length
bool PEM_to_DER(const char *pem_str, uint8_t **out_der, long *out_der_len);
// kReferenceTime is the reference time used by certs created by |MakeTestCert|.
// It is the unix timestamp for Sep 27th, 2016.
static const int64_t kReferenceTime = 1474934400;
// MakeTestCert creates an X509 certificate for use in testing. It is configured
// to be valid from 1 day prior |kReferenceTime| until 1 day after
// |kReferenceTime|.
bssl::UniquePtr<X509> MakeTestCert(const char *issuer,
const char *subject, EVP_PKEY *key,
bool is_ca);
// unique_ptr will automatically call fclose on the file descriptior when the
// variable goes out of scope, so we need to specify BIO_NOCLOSE close flags
// to avoid a double-free condition.
struct TempFileCloser {
void operator()(FILE *f) const { fclose(f); }
};
using TempFILE = std::unique_ptr<FILE, TempFileCloser>;
#if defined(OPENSSL_WINDOWS)
#include <windows.h>
#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif
#else
#include <limits.h>
#endif
size_t createTempFILEpath(char buffer[PATH_MAX]);
FILE* createRawTempFILE();
TempFILE createTempFILE();
size_t createTempDirPath(char buffer[PATH_MAX]);
// Returns true if operating system is Amazon Linux and false otherwise.
// Determined at run-time and requires read-permissions to /etc.
bool osIsAmazonLinux(void);
// Executes |testFunc| simultaneously in |numberThreads| number of threads. If
// OPENSSL_THREADS is not defined, executes |testFunc| a single time
// non-concurrently.
bool threadTest(const size_t numberOfThreads,
std::function<void(bool*)> testFunc);
bool forkAndRunTest(std::function<bool()> child_func,
std::function<bool()> parent_func);
void maybeDisableSomeForkUbeDetectMechanisms(void);
bool runtimeEmulationIsIntelSde(void);
bool addressSanitizerIsEnabled(void);
// CustomData is for testing new structs that we add support for |ex_data|.
typedef struct {
int custom_data;
} CustomData;
void CustomDataFree(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
int index, long argl, void *argp);
// ErrorEquals asserts that |err| is an error with library |lib| and reason
// |reason|.
testing::AssertionResult ErrorEquals(uint32_t err, int lib, int reason);
// HexToBIGNUM decodes |hex| as a hexadecimal, big-endian, unsigned integer and
// returns it as a |BIGNUM|, or nullptr on error.
bssl::UniquePtr<BIGNUM> HexToBIGNUM(const char *hex);
// ExpectParse does a d2i parse using the corresponding template and function
// pointer.
template <typename T>
void ExpectParse(T *(*d2i)(T **, const uint8_t **, long),
const std::vector<uint8_t> &in, bool expected) {
SCOPED_TRACE(Bytes(in));
const uint8_t *ptr = in.data();
bssl::UniquePtr<T> obj(d2i(nullptr, &ptr, in.size()));
if (expected) {
EXPECT_TRUE(obj);
} else {
EXPECT_FALSE(obj);
uint32_t err = ERR_get_error();
EXPECT_EQ(ERR_LIB_ASN1, ERR_GET_LIB(err));
ERR_clear_error();
}
}
#endif // OPENSSL_HEADER_CRYPTO_TEST_TEST_UTIL_H

View File

@@ -0,0 +1,36 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#ifndef OPENSSL_HEADER_CRYPTO_TEST_UBE_TEST_H
#define OPENSSL_HEADER_CRYPTO_TEST_UBE_TEST_H
#include <gtest/gtest.h>
#include "../ube/internal.h"
class UbeBase {
private:
bool ube_detection_supported_ = false;
public:
void SetUp() {
uint64_t current_generation_number = 0;
if (CRYPTO_get_ube_generation_number(&current_generation_number) == 1) {
ube_detection_supported_ = true;
}
}
void TearDown() {
disable_mocked_ube_detection_FOR_TESTING();
}
bool UbeIsSupported() const {
return ube_detection_supported_;
}
void allowMockedUbe() const {
allow_mocked_ube_detection_FOR_TESTING();
}
};
#endif // OPENSSL_HEADER_CRYPTO_TEST_UBE_TEST_H

View File

@@ -0,0 +1,169 @@
// Copyright (c) 2018, Google Inc.
// SPDX-License-Identifier: ISC
#include "./wycheproof_util.h"
#include <limits.h>
#include <stdlib.h>
#include <algorithm>
#include <openssl/bn.h>
#include <openssl/digest.h>
#include <openssl/ec.h>
#include <openssl/nid.h>
#include "./file_test.h"
bool WycheproofResult::IsValid(
const std::vector<std::string> &acceptable_flags) const {
switch (raw_result) {
case WycheproofRawResult::kValid:
return true;
case WycheproofRawResult::kInvalid:
return false;
case WycheproofRawResult::kAcceptable:
for (const auto &flag : flags) {
if (std::find(acceptable_flags.begin(), acceptable_flags.end(), flag) ==
acceptable_flags.end()) {
return false;
}
}
return true;
}
abort();
}
bool WycheproofResult::HasFlag(const std::string &flag) const {
return std::find(flags.begin(), flags.end(), flag) != flags.end();
}
std::string WycheproofResult::StringifyFlags() {
std::string flags_str;
for (size_t i = 0; i < flags.size(); i++) {
if (i > 0) {
flags_str += ", ";
}
flags_str += flags[i];
}
return flags_str;
}
bool GetWycheproofResult(FileTest *t, WycheproofResult *out) {
std::string result;
if (!t->GetAttribute(&result, "result")) {
return false;
}
if (result == "valid") {
out->raw_result = WycheproofRawResult::kValid;
} else if (result == "invalid") {
out->raw_result = WycheproofRawResult::kInvalid;
} else if (result == "acceptable") {
out->raw_result = WycheproofRawResult::kAcceptable;
} else {
t->PrintLine("Bad result string '%s'", result.c_str());
return false;
}
out->flags.clear();
if (t->HasAttribute("flags")) {
std::string flags = t->GetAttributeOrDie("flags");
size_t idx = 0;
while (idx < flags.size()) {
size_t comma = flags.find(',', idx);
if (comma == std::string::npos) {
comma = flags.size();
}
out->flags.push_back(flags.substr(idx, comma - idx));
idx = comma + 1;
}
}
return true;
}
const EVP_MD *GetWycheproofDigest(FileTest *t, const char *key,
bool instruction) {
std::string name;
bool ok =
instruction ? t->GetInstruction(&name, key) : t->GetAttribute(&name, key);
if (!ok) {
return nullptr;
}
if (name == "SHA-1") {
return EVP_sha1();
}
if (name == "SHA-224") {
return EVP_sha224();
}
if (name == "SHA-256") {
return EVP_sha256();
}
if (name == "SHA-384") {
return EVP_sha384();
}
if (name == "SHA-512") {
return EVP_sha512();
}
t->PrintLine("Unknown digest '%s'", name.c_str());
return nullptr;
}
const EC_GROUP *GetWycheproofCurve(FileTest *t, const char *key,
bool instruction) {
std::string name;
bool ok =
instruction ? t->GetInstruction(&name, key) : t->GetAttribute(&name, key);
if (!ok) {
return nullptr;
}
if (name == "secp224r1") {
return EC_group_p224();
}
if (name == "secp256r1") {
return EC_group_p256();
}
if (name == "secp384r1") {
return EC_group_p384();
}
if (name == "secp521r1") {
return EC_group_p521();
}
t->PrintLine("Unknown curve '%s'", name.c_str());
return nullptr;
}
bssl::UniquePtr<BIGNUM> GetWycheproofBIGNUM(FileTest *t, const char *key,
bool instruction) {
std::string value;
bool ok = instruction ? t->GetInstruction(&value, key)
: t->GetAttribute(&value, key);
if (!ok) {
return nullptr;
}
BIGNUM *bn = nullptr;
if (value.size() > INT_MAX ||
BN_hex2bn(&bn, value.c_str()) != static_cast<int>(value.size())) {
BN_free(bn);
t->PrintLine("Could not decode value '%s'", value.c_str());
return nullptr;
}
bssl::UniquePtr<BIGNUM> ret(bn);
if (!value.empty()) {
// If the high bit is one, this is a negative number in Wycheproof.
// Wycheproof's tests generally mimic Java APIs, including all their
// mistakes. See
// https://github.com/google/wycheproof/blob/0329f5b751ef102bd6b7b7181b6e049522a887f5/java/com/google/security/wycheproof/JsonUtil.java#L62.
if ('0' > value[0] || value[0] > '7') {
bssl::UniquePtr<BIGNUM> tmp(BN_new());
if (!tmp || //
value.size() > INT_MAX / 4 ||
!BN_set_bit(tmp.get(), static_cast<int>(value.size() * 4)) ||
!BN_sub(ret.get(), ret.get(), tmp.get())) {
return nullptr;
}
}
}
return ret;
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2018, Google Inc.
// SPDX-License-Identifier: ISC
#ifndef OPENSSL_HEADER_CRYPTO_TEST_WYCHEPROOF_UTIL_H
#define OPENSSL_HEADER_CRYPTO_TEST_WYCHEPROOF_UTIL_H
#include <openssl/base.h>
#include <string>
#include <vector>
// This header contains convenience functions for Wycheproof tests.
static constexpr const char kWycheproofV1Path[] =
"third_party/vectors/converted/wycheproof/testvectors_v1/";
class FileTest;
enum class WycheproofRawResult {
kValid,
kInvalid,
kAcceptable,
};
struct WycheproofResult {
WycheproofRawResult raw_result;
std::vector<std::string> flags;
// IsValid returns true if the Wycheproof test should be considered valid. A
// test result of "acceptable" is treated as valid if all flags are included
// in |acceptable_flags| and invalid otherwise.
bool IsValid(const std::vector<std::string> &acceptable_flags = {}) const;
// StringifyFlags returns a printable string of all flags.
std::string StringifyFlags();
// HasFlag returns true if |flag| is present in the flags vector.
bool HasFlag(const std::string &flag) const;
};
// GetWycheproofResult sets |*out| to the parsed "result" and "flags" keys of |t|.
bool GetWycheproofResult(FileTest *t, WycheproofResult *out);
// GetWycheproofDigest returns a digest function using the Wycheproof name, or
// nullptr on error.
const EVP_MD *GetWycheproofDigest(FileTest *t, const char *key,
bool instruction);
// GetWycheproofCurve returns a curve using the Wycheproof name, or nullptr on
// error.
const EC_GROUP *GetWycheproofCurve(FileTest *t, const char *key,
bool instruction);
// GetWycheproofBIGNUM returns a BIGNUM in the Wycheproof format, or nullptr on
// error.
bssl::UniquePtr<BIGNUM> GetWycheproofBIGNUM(FileTest *t, const char *key,
bool instruction);
#endif // OPENSSL_HEADER_CRYPTO_TEST_WYCHEPROOF_UTIL_H

View File

@@ -0,0 +1,64 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#include "x509_util.h"
#include "test_util.h"
int Verify(X509 *leaf, const std::vector<X509 *> &roots,
const std::vector<X509 *> &intermediates,
const std::vector<X509_CRL *> &crls, unsigned long flags,
std::function<void(X509_STORE_CTX *)> configure_callback) {
bssl::UniquePtr<STACK_OF(X509)> roots_stack(CertsToStack(roots));
bssl::UniquePtr<STACK_OF(X509)> intermediates_stack(
CertsToStack(intermediates));
bssl::UniquePtr<STACK_OF(X509_CRL)> crls_stack(CRLsToStack(crls));
if (!roots_stack || !intermediates_stack || !crls_stack) {
return X509_V_ERR_UNSPECIFIED;
}
bssl::UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new());
bssl::UniquePtr<X509_STORE> store(X509_STORE_new());
if (!ctx || !store) {
return X509_V_ERR_UNSPECIFIED;
}
if (!X509_STORE_CTX_init(ctx.get(), store.get(), leaf,
intermediates_stack.get())) {
return X509_V_ERR_UNSPECIFIED;
}
X509_STORE_CTX_set0_trusted_stack(ctx.get(), roots_stack.get());
X509_STORE_CTX_set0_crls(ctx.get(), crls_stack.get());
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx.get());
X509_VERIFY_PARAM_set_time_posix(param, kReferenceTime);
if (configure_callback) {
configure_callback(ctx.get());
}
if (flags) {
X509_VERIFY_PARAM_set_flags(param, flags);
}
ERR_clear_error();
if (X509_verify_cert(ctx.get()) != 1) {
return X509_STORE_CTX_get_error(ctx.get());
}
return X509_V_OK;
}
bssl::UniquePtr<STACK_OF(X509_CRL)> CRLsToStack(
const std::vector<X509_CRL *> &crls) {
bssl::UniquePtr<STACK_OF(X509_CRL)> stack(sk_X509_CRL_new_null());
if (!stack) {
return nullptr;
}
for (auto crl : crls) {
if (!bssl::PushToStack(stack.get(), bssl::UpRef(crl))) {
return nullptr;
}
}
return stack;
}

View File

@@ -0,0 +1,23 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#ifndef OPENSSL_HEADER_CRYPTO_TEST_X509_UTIL_H
#define OPENSSL_HEADER_CRYPTO_TEST_X509_UTIL_H
#include <functional>
#include <vector>
#include <openssl/x509.h>
int Verify(
X509 *leaf, const std::vector<X509 *> &roots,
const std::vector<X509 *> &intermediates,
const std::vector<X509_CRL *> &crls, unsigned long flags = 0,
std::function<void(X509_STORE_CTX *)> configure_callback = nullptr);
// CRLsToStack converts a vector of |X509_CRL*| to an OpenSSL
// STACK_OF(X509_CRL), bumping the reference counts for each CRL in question.
bssl::UniquePtr<STACK_OF(X509_CRL)> CRLsToStack(
const std::vector<X509_CRL *> &crls);
#endif