Add test of assembly code dispatch.

The first attempt involved using Linux's support for hardware
breakpoints to detect when assembly code was run. However, this doesn't
work with SDE, which is a problem.

This version has the assembly code update a global flags variable when
it's run, but only in non-FIPS and non-debug builds.

Update-Note: Assembly files now pay attention to the NDEBUG preprocessor
symbol. Ensure the build passes the symbol in. (If release builds fail
to link due to missing BORINGSSL_function_hit, this is the cause.)

Change-Id: I6b7ced442b7a77d0b4ae148b00c351f68af89a6e
Reviewed-on: https://boringssl-review.googlesource.com/c/33384
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
This commit is contained in:
Adam Langley 2018-11-27 14:07:12 -08:00 committed by CQ bot account: commit-bot@chromium.org
parent eadef4730e
commit c1615719ce
15 changed files with 283 additions and 6 deletions

View File

@ -65,6 +65,11 @@ if(BORINGSSL_ALLOW_CXX_RUNTIME)
add_definitions(-DBORINGSSL_ALLOW_CXX_RUNTIME)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Release")
# Windows release builds don't set NDEBUG in NASM flags automatically.
set(CMAKE_ASM_NASM_FLAGS "${CMAKE_ASM_NASM_FLAGS} -DNDEBUG")
endif()
if(BORINGSSL_PREFIX AND BORINGSSL_PREFIX_SYMBOLS)
add_definitions(-DBORINGSSL_PREFIX=${BORINGSSL_PREFIX})
# CMake automatically connects include_directories to the NASM command-line,

View File

@ -463,6 +463,7 @@ add_executable(
hkdf/hkdf_test.cc
hmac_extra/hmac_test.cc
hrss/hrss_test.cc
impl_dispatch_test.cc
lhash/lhash_test.cc
obj/obj_test.cc
pem/pem_test.cc

View File

@ -36,8 +36,8 @@
#define BORINGSSL_NO_STATIC_INITIALIZER
#endif
#endif /* !OPENSSL_NO_ASM && (OPENSSL_X86 || OPENSSL_X86_64 ||
OPENSSL_ARM || OPENSSL_AARCH64) */
#endif // !NO_ASM && !STATIC_ARMCAP &&
// (X86 || X86_64 || ARM || AARCH64 || PPC64LE)
// Our assembly does not use the GOT to reference symbols, which means
@ -60,8 +60,7 @@
// that tests the capability values will still skip the constructor but, so
// far, the init constructor function only sets the capability variables.
#if defined(OPENSSL_X86) || defined(OPENSSL_X86_64)
#if !defined(NDEBUG) && !defined(BORINGSSL_FIPS)
// This value must be explicitly initialised to zero in order to work around a
// bug in libtool or the linker on OS X.
//
@ -69,6 +68,12 @@
// archive, linking on OS X will fail to resolve common symbols. By
// initialising it to zero, it becomes a "data symbol", which isn't so
// affected.
HIDDEN uint8_t BORINGSSL_function_hit[7] = {0};
#endif
#if defined(OPENSSL_X86) || defined(OPENSSL_X86_64)
// This value must be explicitly initialized to zero. See similar comment above.
HIDDEN uint32_t OPENSSL_ia32cap_P[4] = {0};
#elif defined(OPENSSL_PPC64LE)

View File

@ -84,6 +84,9 @@ open OUT,">$output";
&asm_init($ARGV[0]);
&external_label("OPENSSL_ia32cap_P");
&preprocessor_ifndef("NDEBUG")
&external_label("BORINGSSL_function_hit");
&preprocessor_endif();
&static_label("key_const");
if ($PREFIX eq $AESNI_PREFIX) { $movekey=\&movups; }
@ -193,6 +196,8 @@ sub aesni_generate1 # fully unrolled loop
# void $PREFIX_encrypt (const void *inp,void *out,const AES_KEY *key);
&aesni_generate1("enc") if (!$inline);
&function_begin_B("${PREFIX}_encrypt");
&record_function_hit(1);
&mov ("eax",&wparam(0));
&mov ($key,&wparam(2));
&movups ($inout0,&QWP(0,"eax"));
@ -875,6 +880,8 @@ if ($PREFIX eq $AESNI_PREFIX) {
# 80 saved %esp
&function_begin("${PREFIX}_ctr32_encrypt_blocks");
&record_function_hit(0);
&mov ($inp,&wparam(0));
&mov ($out,&wparam(1));
&mov ($len,&wparam(2));
@ -2483,6 +2490,8 @@ if ($PREFIX eq $AESNI_PREFIX) {
# int $PREFIX_set_encrypt_key (const unsigned char *userKey, int bits,
# AES_KEY *key)
&function_begin_B("${PREFIX}_set_encrypt_key");
&record_function_hit(3);
&mov ("eax",&wparam(0));
&mov ($rounds,&wparam(1));
&mov ($key,&wparam(2));

View File

@ -275,6 +275,12 @@ $code.=<<___;
.align 16
${PREFIX}_encrypt:
.cfi_startproc
#ifndef NDEBUG
#ifndef BORINGSSL_FIPS
.extern BORINGSSL_function_hit
movb \$1,BORINGSSL_function_hit+1(%rip)
#endif
#endif
movups ($inp),$inout0 # load input
mov 240($key),$rounds # key->rounds
___
@ -1199,6 +1205,11 @@ $code.=<<___;
.align 16
${PREFIX}_ctr32_encrypt_blocks:
.cfi_startproc
#ifndef NDEBUG
#ifndef BORINGSSL_FIPS
movb \$1,BORINGSSL_function_hit(%rip)
#endif
#endif
cmp \$1,$len
jne .Lctr32_bulk
@ -4252,7 +4263,7 @@ $code.=<<___;
.cfi_endproc
.size ${PREFIX}_cbc_encrypt,.-${PREFIX}_cbc_encrypt
___
}
}
# int ${PREFIX}_set_decrypt_key(const unsigned char *inp,
# int bits, AES_KEY *key)
#
@ -4343,6 +4354,11 @@ $code.=<<___;
${PREFIX}_set_encrypt_key:
__aesni_set_encrypt_key:
.cfi_startproc
#ifndef NDEBUG
#ifndef BORINGSSL_FIPS
movb \$1,BORINGSSL_function_hit+3(%rip)
#endif
#endif
.byte 0x48,0x83,0xEC,0x08 # sub rsp,8
.cfi_adjust_cfa_offset 8
mov \$-1,%rax

View File

@ -1920,6 +1920,12 @@ $code.=<<___;
.align 16
bsaes_ctr32_encrypt_blocks:
.cfi_startproc
#ifndef NDEBUG
#ifndef BORINGSSL_FIPS
.extern BORINGSSL_function_hit
movb \$1, BORINGSSL_function_hit+6(%rip)
#endif
#endif
mov %rsp, %rax
.Lctr_enc_prologue:
push %rbp

View File

@ -69,6 +69,9 @@ $PREFIX="vpaes";
my ($round, $base, $magic, $key, $const, $inp, $out)=
("eax", "ebx", "ecx", "edx","ebp", "esi","edi");
&preprocessor_ifndef("NDEBUG")
&external_label("BORINGSSL_function_hit");
&preprocessor_endif();
&static_label("_vpaes_consts");
&static_label("_vpaes_schedule_low_round");
@ -758,6 +761,8 @@ $k_dsbo=0x2c0; # decryption sbox final output
# Interface to OpenSSL
#
&function_begin("${PREFIX}_set_encrypt_key");
record_function_hit(5);
&mov ($inp,&wparam(0)); # inp
&lea ($base,&DWP(-56,"esp"));
&mov ($round,&wparam(1)); # bits
@ -812,6 +817,8 @@ $k_dsbo=0x2c0; # decryption sbox final output
&function_end("${PREFIX}_set_decrypt_key");
&function_begin("${PREFIX}_encrypt");
record_function_hit(4);
&lea ($const,&DWP(&label("_vpaes_consts")."+0x30-".&label("pic_point")));
&call ("_vpaes_preheat");
&set_label("pic_point");

View File

@ -664,6 +664,12 @@ _aesni_ctr32_6x:
.align 32
aesni_gcm_encrypt:
.cfi_startproc
#ifndef NDEBUG
#ifndef BORINGSSL_FIPS
.extern BORINGSSL_function_hit
movb \$1,BORINGSSL_function_hit+2(%rip)
#endif
#endif
xor $ret,$ret
# We call |_aesni_ctr32_6x| twice, each call consuming 96 bytes of

View File

@ -0,0 +1,153 @@
/* Copyright (c) 2018, Google Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include <openssl/base.h>
#if !defined(NDEBUG) && !defined(BORINGSSL_FIPS) && \
!defined(BORINGSSL_SHARED_LIBRARY)
#include <functional>
#include <utility>
#include <vector>
#include <openssl/aead.h>
#include <openssl/aes.h>
#include <openssl/cpu.h>
#include <openssl/mem.h>
#include <gtest/gtest.h>
#include "internal.h"
class ImplDispatchTest : public ::testing::Test {
public:
void SetUp() override {
#if defined(OPENSSL_X86) || defined(OPENSSL_X86_64)
aesni_ = OPENSSL_ia32cap_P[1] & (1 << (57 - 32));
avx_movbe_ = ((OPENSSL_ia32cap_P[1] >> 22) & 0x41) == 0x41;
ssse3_ = OPENSSL_ia32cap_P[1] & (1 << (41 - 32));
is_x86_64_ =
#if defined(OPENSSL_X86_64)
true;
#else
false;
#endif
#endif // X86 || X86_64
}
protected:
// AssertFunctionsHit takes a list of pairs (flag index, boolean), and a
// function to test. It runs the given function and asserts, for each flag
// index, that the boolean reflects whether that flag index was written or
// not, and that no other flagged functions were triggered.
void AssertFunctionsHit(std::vector<std::pair<size_t, bool>> flags,
std::function<void()> f) {
OPENSSL_memset(BORINGSSL_function_hit, 0, sizeof(BORINGSSL_function_hit));
f();
for (const auto flag : flags) {
SCOPED_TRACE(flag.first);
ASSERT_LT(flag.first, sizeof(BORINGSSL_function_hit));
EXPECT_EQ(flag.second, BORINGSSL_function_hit[flag.first] == 1);
BORINGSSL_function_hit[flag.first] = 0;
}
for (size_t i = 0; i < sizeof(BORINGSSL_function_hit); i++) {
EXPECT_EQ(0u, BORINGSSL_function_hit[i])
<< "Flag " << i << " unexpectedly hit";
}
}
#if defined(OPENSSL_X86) || defined(OPENSSL_X86_64)
bool aesni_ = false;
bool avx_movbe_ = false;
bool ssse3_ = false;
bool is_x86_64_ = false;
#endif
};
#if !defined(OPENSSL_NO_ASM) && \
(defined(OPENSSL_X86) || defined(OPENSSL_X86_64))
constexpr size_t kFlag_aes_hw_ctr32_encrypt_blocks = 0;
constexpr size_t kFlag_aes_hw_encrypt = 1;
constexpr size_t kFlag_aesni_gcm_encrypt = 2;
constexpr size_t kFlag_aes_hw_set_encrypt_key = 3;
constexpr size_t kFlag_vpaes_encrypt = 4;
constexpr size_t kFlag_vpaes_set_encrypt_key = 5;
constexpr size_t kFlag_bsaes_ctr32_encrypt_blocks = 6;
TEST_F(ImplDispatchTest, AEAD_AES_GCM) {
AssertFunctionsHit(
{
{kFlag_aes_hw_ctr32_encrypt_blocks, aesni_},
{kFlag_aes_hw_encrypt, aesni_},
{kFlag_aes_hw_set_encrypt_key, aesni_},
{kFlag_aesni_gcm_encrypt, is_x86_64_ && aesni_ && avx_movbe_},
{kFlag_vpaes_encrypt, !is_x86_64_ && ssse3_ && !aesni_},
{kFlag_vpaes_set_encrypt_key, !is_x86_64_ && ssse3_ && !aesni_},
{kFlag_bsaes_ctr32_encrypt_blocks, is_x86_64_ && ssse3_ && !aesni_},
},
[] {
const uint8_t kZeros[16] = {0};
const uint8_t kPlaintext[40] = {1, 2, 3, 4, 0};
uint8_t ciphertext[sizeof(kPlaintext) + 16];
size_t ciphertext_len;
EVP_AEAD_CTX ctx;
ASSERT_TRUE(EVP_AEAD_CTX_init(&ctx, EVP_aead_aes_128_gcm(), kZeros,
sizeof(kZeros),
EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr));
ASSERT_TRUE(EVP_AEAD_CTX_seal(
&ctx, ciphertext, &ciphertext_len, sizeof(ciphertext), kZeros,
EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm()), kPlaintext,
sizeof(kPlaintext), nullptr, 0));
});
}
TEST_F(ImplDispatchTest, AES_set_encrypt_key) {
AssertFunctionsHit(
{
{kFlag_aes_hw_set_encrypt_key, aesni_},
// VPAES / BSAES will not be used for the |AES_*| functions.
},
[] {
AES_KEY key;
static const uint8_t kZeros[16] = {0};
AES_set_encrypt_key(kZeros, sizeof(kZeros) * 8, &key);
});
}
TEST_F(ImplDispatchTest, AES_single_block) {
AES_KEY key;
static const uint8_t kZeros[16] = {0};
AES_set_encrypt_key(kZeros, sizeof(kZeros) * 8, &key);
AssertFunctionsHit(
{
{kFlag_aes_hw_encrypt, aesni_},
// VPAES / BSAES will not be used for the |AES_*| functions.
},
[&key] {
uint8_t in[AES_BLOCK_SIZE] = {0};
uint8_t out[AES_BLOCK_SIZE];
AES_encrypt(in, out, &key);
});
}
#endif // X86 || X86_64
#endif // !NDEBUG && !FIPS && !SHARED_LIBRARY

View File

@ -1181,7 +1181,17 @@ while(defined(my $line=<>)) {
$line =~ s|\R$||; # Better chomp
if ($nasm) {
$line =~ s|^#ifdef |%ifdef |;
$line =~ s|^#ifndef |%ifndef |;
$line =~ s|^#endif|%endif|;
$line =~ s|[#!].*$||; # get rid of asm-style comments...
} else {
# Get rid of asm-style comments but not preprocessor directives. The
# latter are identified by not having a space after the '#'.
$line =~ s|[#!] .*$||;
}
$line =~ s|/\*.*\*/||; # ... and C-style comments...
$line =~ s|^\s+||; # ... and skip white spaces in beginning
$line =~ s|\s+$||; # ... and at the end

View File

@ -33,6 +33,26 @@ sub ::AUTOLOAD
&generic($opcode,@_) or die "undefined subroutine \&$AUTOLOAD";
}
# record_function_hit(int) writes a byte with value one to the given offset of
# |BORINGSSL_function_hit|, but only if NDEBUG is not defined. This is used in
# impl_dispatch_test.cc to test whether the expected assembly functions are
# triggered by high-level API calls.
sub ::record_function_hit
{ my($index)=@_;
&preprocessor_ifndef("NDEBUG");
&push("ebx");
&push("edx");
&call(&label("pic"));
&set_label("pic");
&blindpop("ebx");
&lea("ebx",&DWP("BORINGSSL_function_hit+$index"."-".&label("pic"),"ebx"));
&mov("edx", 1);
&movb(&BP(0, "ebx"), "dl");
&pop("edx");
&pop("ebx");
&preprocessor_endif();
}
sub ::emit
{ my $opcode=shift;

View File

@ -265,6 +265,14 @@ ___
sub ::dataseg
{ push(@out,".data\n"); }
sub ::preprocessor_ifndef
{ my($define)=@_;
push(@out,"#ifndef ${define}\n");
}
sub ::preprocessor_endif
{ push(@out,"#endif\n"); }
*::hidden = sub { push(@out,".hidden\t$nmdecor$_[0]\n"); } if ($::elf);
1;

View File

@ -203,4 +203,12 @@ sub ::safeseh
push(@out,"ENDIF\n");
}
sub ::preprocessor_ifndef
{ my($define)=@_;
push(@out,"%ifndef ${define}\n");
}
sub ::preprocessor_endif
{ push(@out,"%endif\n"); }
1;

View File

@ -191,4 +191,12 @@ sub ::safeseh
push(@out,"%endif\n");
}
sub ::preprocessor_ifndef
{ my($define)=@_;
push(@out,"%ifndef ${define}\n");
}
sub ::preprocessor_endif
{ push(@out,"%endif\n"); }
1;

View File

@ -190,6 +190,21 @@ extern unsigned long OPENSSL_ppc64le_hwcap2;
#endif // OPENSSL_PPC64LE
#if !defined(NDEBUG) && !defined(BORINGSSL_FIPS)
// Runtime CPU dispatch testing support
// BORINGSSL_function_hit is an array of flags. The following functions will
// set these flags in non-FIPS builds if NDEBUG is not defined.
// 0: aes_hw_ctr32_encrypt_blocks
// 1: aes_hw_encrypt
// 2: aesni_gcm_encrypt
// 3: aes_hw_set_encrypt_key
// 4: vpaes_encrypt
// 5: vpaes_set_encrypt_key
// 6: bsaes_ctr32_encrypt_blocks
extern uint8_t BORINGSSL_function_hit[7];
#endif // !NDEBUG && !FIPS
#if defined(__cplusplus)
} // extern C