Skip to content

Commit

Permalink
Refactor CPUID code
Browse files Browse the repository at this point in the history
We were using CPUID coded in several modules, but it was unclear how
it actually got there, and could fail randomly.

To remedy that, this change separates the CPUID C code from the rest
of cryptlib.c, and ensures the right modules get both that and the
assembler sources explicitly.

Fixes openssl#11281
  • Loading branch information
levitte committed Mar 31, 2021
1 parent 4f10a99 commit c1c52b5
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 167 deletions.
34 changes: 22 additions & 12 deletions crypto/build.info
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,24 @@ IF[{- !$disabled{asm} && $config{processor} ne '386' -}]
ENDIF
ENDIF

# CPUID support. We need to add that explicitly in every provider module that
# uses it.
SOURCE[../libcrypto]=$CPUIDASM cpuid.c
DEFINE[../libcrypto]=$CPUIDDEF
INCLUDE[cpuid.o]=..
IF[{- !$disabled{module} -}]
SOURCE[../providers/legacy]=$CPUIDASM cpuid.c
DEFINE[../providers/legacy]=$CPUIDDEF
ENDIF
SOURCE[../providers/fips]=$CPUIDASM cpuid.c
DEFINE[../providers/fips]=$CPUIDDEF

# Implementations are now spread across several libraries, so the CPUID define
# need to be applied to all affected libraries and modules.
DEFINE[../providers/libfips.a]=$CPUIDDEF
DEFINE[../providers/libimplementations.a]=$CPUIDDEF
DEFINE[../providers/libcommon.a]=$CPUIDDEF

# The Core
$CORE_COMMON=provider_core.c provider_predefined.c \
core_fetch.c core_algorithm.c core_namemap.c self_test_core.c
Expand All @@ -70,27 +88,19 @@ SOURCE[../providers/libfips.a]=$CORE_COMMON
$UTIL_COMMON=\
cryptlib.c params.c params_from_text.c bsearch.c ex_data.c o_str.c \
ctype.c threads_pthread.c threads_win.c threads_none.c initthread.c \
context.c sparse_array.c asn1_dsa.c packet.c param_build.c $CPUIDASM \
context.c sparse_array.c asn1_dsa.c packet.c param_build.c \
param_build_set.c der_writer.c passphrase.c threads_lib.c
$UTIL_DEFINE=$CPUIDDEF

SOURCE[../libcrypto]=$UTIL_COMMON \
mem.c mem_sec.c \
cversion.c info.c cpt_err.c ebcdic.c uid.c o_time.c o_dir.c \
o_fopen.c getenv.c o_init.c init.c trace.c provider.c \
punycode.c \
$UPLINKSRC
punycode.c
SOURCE[../providers/libfips.a]=$UTIL_COMMON
SOURCE[../providers/liblegacy.a]=cryptlib.c $CPUIDASM ctype.c

# Implementations are now spread across several libraries, so the defines
# need to be applied to all affected libraries and modules.
DEFINE[../libcrypto]=$UTIL_DEFINE $UPLINKDEF
DEFINE[../providers/libfips.a]=$UTIL_DEFINE
DEFINE[../providers/fips]=$UTIL_DEFINE
DEFINE[../providers/libimplementations.a]=$UTIL_DEFINE
DEFINE[../providers/liblegacy.a]=$UTIL_DEFINE
DEFINE[../providers/libcommon.a]=$UTIL_DEFINE
SOURCE[../libcrypto]=$UPLINKSRC
DEFINE[../libcrypto]=$UPLINKDEF

DEPEND[info.o]=buildinf.h
DEPEND[cversion.o]=buildinf.h
Expand Down
168 changes: 168 additions & 0 deletions crypto/cpuid.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright 1998-2020 The OpenSSL Project Authors. All Rights Reserved.
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/

#include "e_os.h"
#include "crypto/cryptlib.h"

#if defined(__i386) || defined(__i386__) || defined(_M_IX86) || \
defined(__x86_64) || defined(__x86_64__) || \
defined(_M_AMD64) || defined(_M_X64)

extern unsigned int OPENSSL_ia32cap_P[4];

# if defined(OPENSSL_CPUID_OBJ)

/*
* Purpose of these minimalistic and character-type-agnostic subroutines
* is to break dependency on MSVCRT (on Windows) and locale. This makes
* OPENSSL_cpuid_setup safe to use as "constructor". "Character-type-
* agnostic" means that they work with either wide or 8-bit characters,
* exploiting the fact that first 127 characters can be simply casted
* between the sets, while the rest would be simply rejected by ossl_is*
* subroutines.
*/
# ifdef _WIN32
typedef WCHAR variant_char;

static variant_char *ossl_getenv(const char *name)
{
/*
* Since we pull only one environment variable, it's simpler to
* to just ignore |name| and use equivalent wide-char L-literal.
* As well as to ignore excessively long values...
*/
static WCHAR value[48];
DWORD len = GetEnvironmentVariableW(L"OPENSSL_ia32cap", value, 48);

return (len > 0 && len < 48) ? value : NULL;
}
# else
typedef char variant_char;
# define ossl_getenv getenv
# endif

# include "crypto/ctype.h"

static int todigit(variant_char c)
{
if (ossl_isdigit(c))
return c - '0';
else if (ossl_isxdigit(c))
return ossl_tolower(c) - 'a' + 10;

/* return largest base value to make caller terminate the loop */
return 16;
}

static uint64_t ossl_strtouint64(const variant_char *str)
{
uint64_t ret = 0;
unsigned int digit, base = 10;

if (*str == '0') {
base = 8, str++;
if (ossl_tolower(*str) == 'x')
base = 16, str++;
}

while((digit = todigit(*str++)) < base)
ret = ret * base + digit;

return ret;
}

static variant_char *ossl_strchr(const variant_char *str, char srch)
{ variant_char c;

while((c = *str)) {
if (c == srch)
return (variant_char *)str;
str++;
}

return NULL;
}

# define OPENSSL_CPUID_SETUP
typedef uint64_t IA32CAP;

void OPENSSL_cpuid_setup(void)
{
static int trigger = 0;
IA32CAP OPENSSL_ia32_cpuid(unsigned int *);
IA32CAP vec;
const variant_char *env;

if (trigger)
return;

trigger = 1;
if ((env = ossl_getenv("OPENSSL_ia32cap")) != NULL) {
int off = (env[0] == '~') ? 1 : 0;

vec = ossl_strtouint64(env + off);

if (off) {
IA32CAP mask = vec;
vec = OPENSSL_ia32_cpuid(OPENSSL_ia32cap_P) & ~mask;
if (mask & (1<<24)) {
/*
* User disables FXSR bit, mask even other capabilities
* that operate exclusively on XMM, so we don't have to
* double-check all the time. We mask PCLMULQDQ, AMD XOP,
* AES-NI and AVX. Formally speaking we don't have to
* do it in x86_64 case, but we can safely assume that
* x86_64 users won't actually flip this flag.
*/
vec &= ~((IA32CAP)(1<<1|1<<11|1<<25|1<<28) << 32);
}
} else if (env[0] == ':') {
vec = OPENSSL_ia32_cpuid(OPENSSL_ia32cap_P);
}

if ((env = ossl_strchr(env, ':')) != NULL) {
IA32CAP vecx;

env++;
off = (env[0] == '~') ? 1 : 0;
vecx = ossl_strtouint64(env + off);
if (off) {
OPENSSL_ia32cap_P[2] &= ~(unsigned int)vecx;
OPENSSL_ia32cap_P[3] &= ~(unsigned int)(vecx >> 32);
} else {
OPENSSL_ia32cap_P[2] = (unsigned int)vecx;
OPENSSL_ia32cap_P[3] = (unsigned int)(vecx >> 32);
}
} else {
OPENSSL_ia32cap_P[2] = 0;
OPENSSL_ia32cap_P[3] = 0;
}
} else {
vec = OPENSSL_ia32_cpuid(OPENSSL_ia32cap_P);
}

/*
* |(1<<10) sets a reserved bit to signal that variable
* was initialized already... This is to avoid interference
* with cpuid snippets in ELF .init segment.
*/
OPENSSL_ia32cap_P[0] = (unsigned int)vec | (1 << 10);
OPENSSL_ia32cap_P[1] = (unsigned int)(vec >> 32);
}
# else
unsigned int OPENSSL_ia32cap_P[4];
# endif
#endif

#if !defined(OPENSSL_CPUID_SETUP) && !defined(OPENSSL_CPUID_OBJ)
void OPENSSL_cpuid_setup(void)
{
}
#endif
155 changes: 0 additions & 155 deletions crypto/cryptlib.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,161 +12,6 @@
#include "crypto/cryptlib.h"
#include <openssl/safestack.h>

#if defined(__i386) || defined(__i386__) || defined(_M_IX86) || \
defined(__x86_64) || defined(__x86_64__) || \
defined(_M_AMD64) || defined(_M_X64)

extern unsigned int OPENSSL_ia32cap_P[4];

# if defined(OPENSSL_CPUID_OBJ)

/*
* Purpose of these minimalistic and character-type-agnostic subroutines
* is to break dependency on MSVCRT (on Windows) and locale. This makes
* OPENSSL_cpuid_setup safe to use as "constructor". "Character-type-
* agnostic" means that they work with either wide or 8-bit characters,
* exploiting the fact that first 127 characters can be simply casted
* between the sets, while the rest would be simply rejected by ossl_is*
* subroutines.
*/
# ifdef _WIN32
typedef WCHAR variant_char;

static variant_char *ossl_getenv(const char *name)
{
/*
* Since we pull only one environment variable, it's simpler to
* to just ignore |name| and use equivalent wide-char L-literal.
* As well as to ignore excessively long values...
*/
static WCHAR value[48];
DWORD len = GetEnvironmentVariableW(L"OPENSSL_ia32cap", value, 48);

return (len > 0 && len < 48) ? value : NULL;
}
# else
typedef char variant_char;
# define ossl_getenv getenv
# endif

# include "crypto/ctype.h"

static int todigit(variant_char c)
{
if (ossl_isdigit(c))
return c - '0';
else if (ossl_isxdigit(c))
return ossl_tolower(c) - 'a' + 10;

/* return largest base value to make caller terminate the loop */
return 16;
}

static uint64_t ossl_strtouint64(const variant_char *str)
{
uint64_t ret = 0;
unsigned int digit, base = 10;

if (*str == '0') {
base = 8, str++;
if (ossl_tolower(*str) == 'x')
base = 16, str++;
}

while((digit = todigit(*str++)) < base)
ret = ret * base + digit;

return ret;
}

static variant_char *ossl_strchr(const variant_char *str, char srch)
{ variant_char c;

while((c = *str)) {
if (c == srch)
return (variant_char *)str;
str++;
}

return NULL;
}

# define OPENSSL_CPUID_SETUP
typedef uint64_t IA32CAP;

void OPENSSL_cpuid_setup(void)
{
static int trigger = 0;
IA32CAP OPENSSL_ia32_cpuid(unsigned int *);
IA32CAP vec;
const variant_char *env;

if (trigger)
return;

trigger = 1;
if ((env = ossl_getenv("OPENSSL_ia32cap")) != NULL) {
int off = (env[0] == '~') ? 1 : 0;

vec = ossl_strtouint64(env + off);

if (off) {
IA32CAP mask = vec;
vec = OPENSSL_ia32_cpuid(OPENSSL_ia32cap_P) & ~mask;
if (mask & (1<<24)) {
/*
* User disables FXSR bit, mask even other capabilities
* that operate exclusively on XMM, so we don't have to
* double-check all the time. We mask PCLMULQDQ, AMD XOP,
* AES-NI and AVX. Formally speaking we don't have to
* do it in x86_64 case, but we can safely assume that
* x86_64 users won't actually flip this flag.
*/
vec &= ~((IA32CAP)(1<<1|1<<11|1<<25|1<<28) << 32);
}
} else if (env[0] == ':') {
vec = OPENSSL_ia32_cpuid(OPENSSL_ia32cap_P);
}

if ((env = ossl_strchr(env, ':')) != NULL) {
IA32CAP vecx;

env++;
off = (env[0] == '~') ? 1 : 0;
vecx = ossl_strtouint64(env + off);
if (off) {
OPENSSL_ia32cap_P[2] &= ~(unsigned int)vecx;
OPENSSL_ia32cap_P[3] &= ~(unsigned int)(vecx >> 32);
} else {
OPENSSL_ia32cap_P[2] = (unsigned int)vecx;
OPENSSL_ia32cap_P[3] = (unsigned int)(vecx >> 32);
}
} else {
OPENSSL_ia32cap_P[2] = 0;
OPENSSL_ia32cap_P[3] = 0;
}
} else {
vec = OPENSSL_ia32_cpuid(OPENSSL_ia32cap_P);
}

/*
* |(1<<10) sets a reserved bit to signal that variable
* was initialized already... This is to avoid interference
* with cpuid snippets in ELF .init segment.
*/
OPENSSL_ia32cap_P[0] = (unsigned int)vec | (1 << 10);
OPENSSL_ia32cap_P[1] = (unsigned int)(vec >> 32);
}
# else
unsigned int OPENSSL_ia32cap_P[4];
# endif
#endif
#if !defined(OPENSSL_CPUID_SETUP) && !defined(OPENSSL_CPUID_OBJ)
void OPENSSL_cpuid_setup(void)
{
}
#endif

#if defined(_WIN32)
# include <tchar.h>
# include <signal.h>
Expand Down

0 comments on commit c1c52b5

Please sign in to comment.