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) add_definitions(-DBORINGSSL_ALLOW_CXX_RUNTIME)
endif() 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) if(BORINGSSL_PREFIX AND BORINGSSL_PREFIX_SYMBOLS)
add_definitions(-DBORINGSSL_PREFIX=${BORINGSSL_PREFIX}) add_definitions(-DBORINGSSL_PREFIX=${BORINGSSL_PREFIX})
# CMake automatically connects include_directories to the NASM command-line, # CMake automatically connects include_directories to the NASM command-line,

View File

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

View File

@ -36,8 +36,8 @@
#define BORINGSSL_NO_STATIC_INITIALIZER #define BORINGSSL_NO_STATIC_INITIALIZER
#endif #endif
#endif /* !OPENSSL_NO_ASM && (OPENSSL_X86 || OPENSSL_X86_64 || #endif // !NO_ASM && !STATIC_ARMCAP &&
OPENSSL_ARM || OPENSSL_AARCH64) */ // (X86 || X86_64 || ARM || AARCH64 || PPC64LE)
// Our assembly does not use the GOT to reference symbols, which means // 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 // that tests the capability values will still skip the constructor but, so
// far, the init constructor function only sets the capability variables. // 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 // This value must be explicitly initialised to zero in order to work around a
// bug in libtool or the linker on OS X. // 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 // 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 // initialising it to zero, it becomes a "data symbol", which isn't so
// affected. // 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}; HIDDEN uint32_t OPENSSL_ia32cap_P[4] = {0};
#elif defined(OPENSSL_PPC64LE) #elif defined(OPENSSL_PPC64LE)

View File

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

View File

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

View File

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

View File

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

View File

@ -664,6 +664,12 @@ _aesni_ctr32_6x:
.align 32 .align 32
aesni_gcm_encrypt: aesni_gcm_encrypt:
.cfi_startproc .cfi_startproc
#ifndef NDEBUG
#ifndef BORINGSSL_FIPS
.extern BORINGSSL_function_hit
movb \$1,BORINGSSL_function_hit+2(%rip)
#endif
#endif
xor $ret,$ret xor $ret,$ret
# We call |_aesni_ctr32_6x| twice, each call consuming 96 bytes of # 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 $line =~ s|\R$||; # Better chomp
$line =~ s|[#!].*$||; # get rid of asm-style comments... 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|/\*.*\*/||; # ... and C-style comments...
$line =~ s|^\s+||; # ... and skip white spaces in beginning $line =~ s|^\s+||; # ... and skip white spaces in beginning
$line =~ s|\s+$||; # ... and at the end $line =~ s|\s+$||; # ... and at the end

View File

@ -33,6 +33,26 @@ sub ::AUTOLOAD
&generic($opcode,@_) or die "undefined subroutine \&$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 sub ::emit
{ my $opcode=shift; { my $opcode=shift;

View File

@ -265,6 +265,14 @@ ___
sub ::dataseg sub ::dataseg
{ push(@out,".data\n"); } { 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); *::hidden = sub { push(@out,".hidden\t$nmdecor$_[0]\n"); } if ($::elf);
1; 1;

View File

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

View File

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

View File

@ -190,6 +190,21 @@ extern unsigned long OPENSSL_ppc64le_hwcap2;
#endif // OPENSSL_PPC64LE #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) #if defined(__cplusplus)
} // extern C } // extern C