mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-10-24 04:09:07 +03:00
498 lines
20 KiB
C
498 lines
20 KiB
C
/* test_sp_fault_common.h
|
|
*
|
|
* Copyright (C) 2006-2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfSSL.
|
|
*
|
|
* wolfSSL is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfSSL is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
/*
|
|
* Shared body for the SP backend heap-fault white-boxes.
|
|
*
|
|
* WHY
|
|
* ---
|
|
* Every SP backend carries hundreds of decisions shaped
|
|
*
|
|
* if ((err == MP_OKAY) && <next step>)
|
|
*
|
|
* whose `err == MP_OKAY` operand has no false side in the builds.
|
|
* The reason is not that the failure is hard to produce, it is that nothing in
|
|
* the compiled code can produce it: SP_ALLOC_VAR is
|
|
*
|
|
* #ifdef WOLFSSL_SP_SMALL_STACK
|
|
* if (err == MP_OKAY) {
|
|
* (NAME) = XMALLOC(...);
|
|
* if ((NAME) == NULL) { err = MEMORY_E; }
|
|
* }
|
|
* #else
|
|
* WC_DO_NOTHING
|
|
*
|
|
* so without WOLFSSL_SP_SMALL_STACK the variables are plain stack arrays, err
|
|
* stays MP_OKAY from entry to exit, and the operand is dead by construction.
|
|
*
|
|
* This driver therefore only does useful work in a variant built with
|
|
* WOLFSSL_SP_SMALL_STACK. Elsewhere it runs the same operations with the
|
|
* injector never armed, which costs one quick pass and keeps the file building
|
|
* in every variant of the module (a white-box that fails to build is a silent
|
|
* skip, and the harness has lost a module's evidence to that twice).
|
|
*
|
|
* HOW
|
|
* ---
|
|
* mcdc_fault_alloc.h fails the n-th and every later allocation. Sweeping n
|
|
* across the allocation sites of an operation walks the MEMORY_E failure down
|
|
* the whole success chain, so each `(err == MP_OKAY)` checkpoint sees both a
|
|
* run where it holds and a run where it does not.
|
|
*
|
|
* The operations are the public SP entry points, driven with valid operands so
|
|
* that an unarmed pass completes normally: the only thing under test is where
|
|
* the failure lands, not the arithmetic.
|
|
*
|
|
* The including TU defines SP_FAULT_LABEL and includes the wolfCrypt .c under
|
|
* test before including this header.
|
|
*/
|
|
|
|
#ifndef SP_FAULT_LABEL
|
|
#error "define SP_FAULT_LABEL before including test_sp_fault_common.h"
|
|
#endif
|
|
|
|
#include "mcdc_fault_alloc.h"
|
|
|
|
#include <wolfssl/wolfcrypt/ecc.h>
|
|
#include <wolfssl/wolfcrypt/rsa.h>
|
|
#include <wolfssl/wolfcrypt/dh.h>
|
|
#include <wolfssl/wolfcrypt/random.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
static int wb_fail = 0;
|
|
#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0)
|
|
|
|
/* Sweep depth. SP entry points allocate a handful of temporaries each, so the
|
|
* failure index only has to walk a little past the deepest chain. Kept low on
|
|
* purpose: every index repeats a full keygen/sign/verify, TEST_TIMEOUT is wall
|
|
* clock, and variants run concurrently under MAXPAR -- a driver that finishes
|
|
* alone can still be killed under load, and a killed driver is a silent skip. */
|
|
#ifndef SP_FAULT_MAX_N
|
|
#define SP_FAULT_MAX_N 24
|
|
#endif
|
|
|
|
#if defined(WOLFSSL_HAVE_SP_ECC) && defined(HAVE_ECC) && \
|
|
!defined(MCDC_FA_UNAVAILABLE)
|
|
/* ECC. One armed region per operation, not one around the whole chain: the
|
|
* injector fails the n-th allocation AND every later one, so an arming that
|
|
* spans make_key + sign + verify + ECDH loses the failure inside make_key and
|
|
* the later three never run at all. Each operation therefore gets its own
|
|
* sweep, with its inputs built while disarmed. */
|
|
static void wb_fault_ecc(int curveId, int fieldSz)
|
|
{
|
|
int n;
|
|
|
|
/* make_key: the failure walks this operation's own allocation sites. */
|
|
for (n = 1; n <= SP_FAULT_MAX_N; n++) {
|
|
ecc_key key;
|
|
WC_RNG rng;
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
return;
|
|
}
|
|
if (wc_ecc_init(&key) == 0) {
|
|
mcdc_fa_arm(n);
|
|
(void)wc_ecc_make_key_ex(&rng, fieldSz, &key, curveId);
|
|
mcdc_fa_disarm();
|
|
wc_ecc_free(&key);
|
|
}
|
|
wc_FreeRng(&rng);
|
|
}
|
|
|
|
/* sign / verify / ECDH: each off a key built with the injector disarmed,
|
|
* so the operation under test starts from a valid state and the failure
|
|
* lands inside it rather than in its setup. */
|
|
for (n = 1; n <= SP_FAULT_MAX_N; n++) {
|
|
ecc_key key;
|
|
WC_RNG rng;
|
|
byte sig[144];
|
|
byte secret[80];
|
|
byte digest[32];
|
|
word32 sigLen;
|
|
word32 secretLen;
|
|
int res = 0;
|
|
|
|
XMEMSET(digest, 0x5a, sizeof(digest));
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
return;
|
|
}
|
|
if (wc_ecc_init(&key) != 0) {
|
|
wc_FreeRng(&rng);
|
|
return;
|
|
}
|
|
if (wc_ecc_make_key_ex(&rng, fieldSz, &key, curveId) == 0) {
|
|
sigLen = (word32)sizeof(sig);
|
|
mcdc_fa_arm(n);
|
|
(void)wc_ecc_sign_hash(digest, (word32)sizeof(digest), sig,
|
|
&sigLen, &rng, &key);
|
|
mcdc_fa_disarm();
|
|
|
|
/* A real signature to verify, made while disarmed. */
|
|
sigLen = (word32)sizeof(sig);
|
|
if (wc_ecc_sign_hash(digest, (word32)sizeof(digest), sig, &sigLen,
|
|
&rng, &key) == 0) {
|
|
mcdc_fa_arm(n);
|
|
(void)wc_ecc_verify_hash(sig, sigLen, digest,
|
|
(word32)sizeof(digest), &res, &key);
|
|
mcdc_fa_disarm();
|
|
}
|
|
|
|
secretLen = (word32)sizeof(secret);
|
|
PRIVATE_KEY_UNLOCK();
|
|
mcdc_fa_arm(n);
|
|
(void)wc_ecc_shared_secret(&key, &key, secret, &secretLen);
|
|
mcdc_fa_disarm();
|
|
PRIVATE_KEY_LOCK();
|
|
|
|
mcdc_fa_arm(n);
|
|
(void)wc_ecc_check_key(&key);
|
|
mcdc_fa_disarm();
|
|
}
|
|
wc_ecc_free(&key);
|
|
wc_FreeRng(&rng);
|
|
}
|
|
}
|
|
#else
|
|
static void wb_fault_ecc(int curveId, int fieldSz)
|
|
{
|
|
(void)curveId;
|
|
(void)fieldSz;
|
|
}
|
|
#endif
|
|
|
|
#if defined(WOLFSSL_HAVE_SP_ECC) && defined(HAVE_ECC) && \
|
|
!defined(MCDC_FA_UNAVAILABLE)
|
|
/* -------------------------------------------------------------------- *
|
|
* The multi-condition `err` chains this module still owes are NOT in the
|
|
* wc_* API's call graph.
|
|
*
|
|
* Shapes like
|
|
*
|
|
* if ((err == MP_OKAY) && (!inMont)) { ... }
|
|
* if ((err == MP_OKAY) && sp_<n>_iszero_<n>(p1->z)) { ... }
|
|
* if ((err == MP_OKAY) && ((sp_<n>_cmp_<n>(p->x, pub->x) != 0) || ...))
|
|
*
|
|
* live in sp_ecc_mulmod_add_<n>, sp_ecc_mulmod_base_add_<n>,
|
|
* sp_ecc_check_key_<n>, sp_ecc_verify_<n> and sp_ecc_sign_<n>. Some of
|
|
* those entry points nothing in wc_* reaches at all on this
|
|
* configuration; the ones it does reach it reaches through wrappers that
|
|
* allocate first, so a sweep aimed at the wrapper lands its failure
|
|
* before the callee is entered.
|
|
*
|
|
* So each entry point is called DIRECTLY, with its own arming hugging
|
|
* the one call and every input built while disarmed. The depth only has
|
|
* to walk a couple of SP_ALLOC_VARs past the start of each function --
|
|
* the injector fails allocation n and every later one, so a shallow
|
|
* sweep already puts `err` on the wrong side of every checkpoint in the
|
|
* function.
|
|
* -------------------------------------------------------------------- */
|
|
#ifndef SP_FAULT_DIRECT_N
|
|
#define SP_FAULT_DIRECT_N 6
|
|
#endif
|
|
|
|
#define SP_FAULT_DEFINE_DIRECT(BITS, SZ, CURVE_ID) \
|
|
static void wb_fault_direct_##BITS(void) \
|
|
{ \
|
|
ecc_key key; \
|
|
WC_RNG rng; \
|
|
ecc_point* rp = NULL; \
|
|
mp_int k; \
|
|
mp_int one; \
|
|
mp_int rmv; \
|
|
mp_int smv; \
|
|
mp_int rmv2; \
|
|
mp_int smv2; \
|
|
byte digest[32]; \
|
|
int res = 0; \
|
|
int n; \
|
|
int haveSig = 0; \
|
|
\
|
|
XMEMSET(&key, 0, sizeof(key)); \
|
|
XMEMSET(&rng, 0, sizeof(rng)); \
|
|
XMEMSET(digest, 0x5a, sizeof(digest)); \
|
|
\
|
|
if (wc_InitRng(&rng) != 0) { \
|
|
return; \
|
|
} \
|
|
if (wc_ecc_init(&key) != 0) { \
|
|
wc_FreeRng(&rng); \
|
|
return; \
|
|
} \
|
|
if (mp_init_multi(&k, &one, &rmv, &smv, &rmv2, &smv2) != MP_OKAY) { \
|
|
wc_ecc_free(&key); \
|
|
wc_FreeRng(&rng); \
|
|
return; \
|
|
} \
|
|
(void)mp_set(&k, 5); \
|
|
(void)mp_set(&one, 1); \
|
|
rp = wc_ecc_new_point(); \
|
|
\
|
|
if ((rp != NULL) && \
|
|
(wc_ecc_make_key_ex(&rng, SZ, &key, CURVE_ID) == 0)) { \
|
|
SP_FAULT_SIGN_SETUP(BITS) \
|
|
for (n = 1; n <= SP_FAULT_DIRECT_N; n++) { \
|
|
mcdc_fa_arm(n); \
|
|
(void)sp_ecc_mulmod_add_##BITS(&k, &key.pubkey, &key.pubkey, \
|
|
0, rp, 1, NULL); \
|
|
mcdc_fa_disarm(); \
|
|
\
|
|
mcdc_fa_arm(n); \
|
|
(void)sp_ecc_mulmod_base_add_##BITS(&k, &key.pubkey, 0, rp, 1, \
|
|
NULL); \
|
|
mcdc_fa_disarm(); \
|
|
\
|
|
mcdc_fa_arm(n); \
|
|
(void)sp_ecc_mulmod_##BITS(&k, &key.pubkey, rp, 1, NULL); \
|
|
mcdc_fa_disarm(); \
|
|
\
|
|
mcdc_fa_arm(n); \
|
|
(void)sp_ecc_mulmod_base_##BITS(&k, rp, 1, NULL); \
|
|
mcdc_fa_disarm(); \
|
|
\
|
|
mcdc_fa_arm(n); \
|
|
(void)sp_ecc_is_point_##BITS(key.pubkey.x, key.pubkey.y); \
|
|
mcdc_fa_disarm(); \
|
|
\
|
|
SP_FAULT_CHECK_KEY_ARM(BITS) \
|
|
SP_FAULT_SIGNVFY_ARM(BITS) \
|
|
} \
|
|
} \
|
|
\
|
|
if (rp != NULL) { \
|
|
wc_ecc_del_point(rp); \
|
|
} \
|
|
mp_clear(&smv2); \
|
|
mp_clear(&rmv2); \
|
|
mp_clear(&smv); \
|
|
mp_clear(&rmv); \
|
|
mp_clear(&one); \
|
|
mp_clear(&k); \
|
|
wc_ecc_free(&key); \
|
|
wc_FreeRng(&rng); \
|
|
(void)res; \
|
|
(void)haveSig; \
|
|
}
|
|
|
|
#if defined(HAVE_ECC_CHECK_KEY) || !defined(NO_ECC_CHECK_PUBKEY_ORDER)
|
|
#define SP_FAULT_CHECK_KEY_ARM(BITS) \
|
|
mcdc_fa_arm(n); \
|
|
(void)sp_ecc_check_key_##BITS(key.pubkey.x, key.pubkey.y, \
|
|
ecc_get_k(&key), NULL); \
|
|
mcdc_fa_disarm();
|
|
#else
|
|
#define SP_FAULT_CHECK_KEY_ARM(BITS) /* not compiled in this config */
|
|
#endif
|
|
|
|
#if defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY)
|
|
/* A real signature, made disarmed, so the armed verify below starts from
|
|
* valid inputs and the failure lands inside the verify. */
|
|
#define SP_FAULT_SIGN_SETUP(BITS) \
|
|
haveSig = (sp_ecc_sign_##BITS(digest, 32, &rng, ecc_get_k(&key), \
|
|
&rmv, &smv, NULL, NULL) == 0);
|
|
#define SP_FAULT_SIGNVFY_ARM(BITS) \
|
|
mcdc_fa_arm(n); \
|
|
(void)sp_ecc_sign_##BITS(digest, 32, &rng, ecc_get_k(&key), \
|
|
&rmv2, &smv2, NULL, NULL); \
|
|
mcdc_fa_disarm(); \
|
|
if (haveSig) { \
|
|
mcdc_fa_arm(n); \
|
|
(void)sp_ecc_verify_##BITS(digest, 32, key.pubkey.x, \
|
|
key.pubkey.y, &one, &rmv, &smv, &res, NULL); \
|
|
mcdc_fa_disarm(); \
|
|
}
|
|
#else
|
|
#define SP_FAULT_SIGN_SETUP(BITS) /* needs HAVE_ECC_SIGN/VERIFY */
|
|
#define SP_FAULT_SIGNVFY_ARM(BITS) /* needs HAVE_ECC_SIGN/VERIFY */
|
|
#endif
|
|
|
|
#ifndef WOLFSSL_SP_NO_256
|
|
SP_FAULT_DEFINE_DIRECT(256, 32, ECC_SECP256R1)
|
|
#endif
|
|
#ifdef WOLFSSL_SP_384
|
|
SP_FAULT_DEFINE_DIRECT(384, 48, ECC_SECP384R1)
|
|
#endif
|
|
#ifdef WOLFSSL_SP_521
|
|
SP_FAULT_DEFINE_DIRECT(521, 66, ECC_SECP521R1)
|
|
#endif
|
|
|
|
static void wb_fault_direct_all(void)
|
|
{
|
|
#ifndef WOLFSSL_SP_NO_256
|
|
wb_fault_direct_256();
|
|
#endif
|
|
#ifdef WOLFSSL_SP_384
|
|
wb_fault_direct_384();
|
|
#endif
|
|
#ifdef WOLFSSL_SP_521
|
|
wb_fault_direct_521();
|
|
#endif
|
|
}
|
|
#else
|
|
static void wb_fault_direct_all(void)
|
|
{
|
|
}
|
|
#endif /* WOLFSSL_HAVE_SP_ECC && HAVE_ECC && !MCDC_FA_UNAVAILABLE */
|
|
|
|
#if defined(WOLFSSL_HAVE_SP_DH) && !defined(NO_DH) && \
|
|
!defined(MCDC_FA_UNAVAILABLE)
|
|
/* DH: key agreement over a compiled-in FFDHE group. */
|
|
static void wb_fault_dh(void)
|
|
{
|
|
int n;
|
|
|
|
for (n = 1; n <= SP_FAULT_MAX_N; n++) {
|
|
DhKey key;
|
|
WC_RNG rng;
|
|
byte priv[384];
|
|
byte pub[384];
|
|
byte agree[384];
|
|
word32 privSz = (word32)sizeof(priv);
|
|
word32 pubSz = (word32)sizeof(pub);
|
|
word32 agreeSz = (word32)sizeof(agree);
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
return;
|
|
}
|
|
if (wc_InitDhKey(&key) != 0) {
|
|
wc_FreeRng(&rng);
|
|
return;
|
|
}
|
|
|
|
if (wc_DhSetNamedKey(&key, WC_FFDHE_2048) == 0) {
|
|
mcdc_fa_arm(n);
|
|
(void)wc_DhGenerateKeyPair(&key, &rng, priv, &privSz, pub, &pubSz);
|
|
mcdc_fa_disarm();
|
|
|
|
privSz = (word32)sizeof(priv);
|
|
pubSz = (word32)sizeof(pub);
|
|
if (wc_DhGenerateKeyPair(&key, &rng, priv, &privSz, pub,
|
|
&pubSz) == 0) {
|
|
mcdc_fa_arm(n);
|
|
(void)wc_DhAgree(&key, agree, &agreeSz, priv, privSz, pub,
|
|
pubSz);
|
|
mcdc_fa_disarm();
|
|
}
|
|
}
|
|
|
|
wc_FreeDhKey(&key);
|
|
wc_FreeRng(&rng);
|
|
}
|
|
}
|
|
#else
|
|
static void wb_fault_dh(void)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#if defined(WOLFSSL_HAVE_SP_RSA) && !defined(NO_RSA) && \
|
|
!defined(MCDC_FA_UNAVAILABLE)
|
|
/* RSA: public/private operations off a decoded key, under the sweep. */
|
|
static void wb_fault_rsa(void)
|
|
{
|
|
int n;
|
|
|
|
for (n = 1; n <= SP_FAULT_MAX_N; n++) {
|
|
RsaKey key;
|
|
WC_RNG rng;
|
|
byte out[256];
|
|
byte plain[256];
|
|
word32 idx = 0;
|
|
|
|
if (wc_InitRng(&rng) != 0) {
|
|
return;
|
|
}
|
|
if (wc_InitRsaKey(&key, NULL) != 0) {
|
|
wc_FreeRng(&rng);
|
|
return;
|
|
}
|
|
if (wc_RsaPrivateKeyDecode(client_key_der_2048, &idx, &key,
|
|
(word32)sizeof_client_key_der_2048) == 0) {
|
|
mcdc_fa_arm(n);
|
|
(void)wc_RsaPublicEncrypt((const byte*)"mcdc", 4, out,
|
|
(word32)sizeof(out), &key, &rng);
|
|
mcdc_fa_disarm();
|
|
|
|
if (wc_RsaPublicEncrypt((const byte*)"mcdc", 4, out,
|
|
(word32)sizeof(out), &key, &rng) > 0) {
|
|
mcdc_fa_arm(n);
|
|
(void)wc_RsaPrivateDecrypt(out, (word32)sizeof(out), plain,
|
|
(word32)sizeof(plain), &key);
|
|
mcdc_fa_disarm();
|
|
}
|
|
}
|
|
|
|
wc_FreeRsaKey(&key);
|
|
wc_FreeRng(&rng);
|
|
}
|
|
}
|
|
#else
|
|
static void wb_fault_rsa(void)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
int main(void)
|
|
{
|
|
/* Unbuffered: on a timeout the process is killed and anything still
|
|
* buffered is lost, which reads as an empty log. */
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
|
printf("%s heap-fault white-box supplement\n", SP_FAULT_LABEL);
|
|
|
|
#ifdef MCDC_FA_UNAVAILABLE
|
|
WB_NOTE("allocator hooks unavailable in this config; nothing to sweep");
|
|
#else
|
|
#ifndef WOLFSSL_SP_SMALL_STACK
|
|
WB_NOTE("WOLFSSL_SP_SMALL_STACK off: SP temporaries are stack arrays and "
|
|
"err cannot leave MP_OKAY; sweep runs but cannot fail an SP alloc");
|
|
#endif
|
|
mcdc_fa_install();
|
|
|
|
#ifndef WOLFSSL_SP_NO_256
|
|
wb_fault_ecc(ECC_SECP256R1, 32);
|
|
#endif
|
|
#ifdef WOLFSSL_SP_384
|
|
wb_fault_ecc(ECC_SECP384R1, 48);
|
|
#endif
|
|
#ifdef WOLFSSL_SP_521
|
|
wb_fault_ecc(ECC_SECP521R1, 66);
|
|
#endif
|
|
|
|
wb_fault_dh();
|
|
wb_fault_rsa();
|
|
|
|
/* Direct SP entry points, one arming per call: the multi-condition
|
|
* `err` chains this module still owes are all behind entry points the
|
|
* wc_* API either never takes or only reaches through a wrapper that
|
|
* allocates first. */
|
|
wb_fault_direct_all();
|
|
|
|
mcdc_fa_disarm();
|
|
mcdc_fa_restore();
|
|
#endif
|
|
|
|
printf("done (%s)\n", wb_fail ? "with skips" : "ok");
|
|
/* Always 0: a nonzero exit discards this variant's whole coverage. */
|
|
(void)wb_fail;
|
|
return 0;
|
|
}
|