1
0
mirror of https://github.com/wolfSSL/wolfssl.git synced 2026-10-24 04:09:07 +03:00
Files
wolfssl/tests/unit-mcdc/test_tfm_whitebox.c

951 lines
35 KiB
C

/* test_tfm_whitebox.c
*
* White-box MC/DC supplement for wolfcrypt/src/tfm.c (the FASTMATH
* big-integer module, bigint-tfm, iso26262/mcdc-per-module).
*
* The tests/api wolfmath suite (test_wolfmath.c, test_wc_Tfm*DecisionCoverage)
* drives tfm.c through its *public* mp_/fp_ API. A number of decisions instead
* live in file-static helpers whose branch-selecting operand combinations a
* public caller can never present (a helper is only ever reached after the
* public entry point has already normalized / range-checked its arguments), so
* their MC/DC independence pairs cannot be demonstrated from the API without
* editing library source. This translation unit reaches them by compiling
* tfm.c directly (#include) and calling the static helpers with BOTH halves of each
* targeted pair in this one binary (llvm-cov computes MC/DC per binary; the
* suite unions the "independence shown" bit across binaries by line:col).
*
* Build: compiled by the coverage runner's white-box step with the SAME MC/DC CFLAGS
* and -I<workspace> as the instrumented library, then linked against that
* variant's libwolfssl.a with its tfm.o removed (this TU supplies the
* instrumented tfm.c). NOT part of the wolfSSL build; not registered in
* tests/api. See tests/unit-mcdc/README.md.
*
* Every call is memory-safe (static helpers are handed initialized fp_ints and
* in-range selectors); setup failures print a skip and return 0 (a nonzero
* exit makes the harness discard the variant and its coverage).
*/
#include <wolfcrypt/src/tfm.c>
#include <stdio.h>
#ifndef HEAP_HINT
#define HEAP_HINT NULL
#endif
static int wb_fail = 0;
#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0)
#if defined(USE_FAST_MATH)
/* s_is_power_of_two(b, &p): "b power of two" true (b=8), false-via-not-set
* (b=6, more than one bit), and the b==0 guard. Both halves of the internal
* "is exactly one bit set" decision in one binary. */
static void wb_is_power_of_two(void)
{
int p = 0;
(void)s_is_power_of_two(8, &p); /* power of two -> FP_YES */
(void)s_is_power_of_two(6, &p); /* not a power of two -> FP_NO */
(void)s_is_power_of_two(1, &p); /* 1 == 2^0 boundary */
(void)s_is_power_of_two(0, &p); /* zero: rejected */
WB_NOTE("s_is_power_of_two both branches exercised");
}
/* fp_cond_swap_ct / fp_cond_swap_ct_ex: constant-time conditional swap; the
* mask = 0 - m selects swap (m=1) or no-swap (m=0). Both halves in-binary. */
static void wb_cond_swap_ct(void)
{
fp_int a, b;
fp_init(&a);
fp_init(&b);
fp_set(&a, 0x1234);
fp_set(&b, 0x5678);
(void)fp_cond_swap_ct(&a, &b, (int)a.used > (int)b.used ?
(int)a.used : (int)b.used, 1); /* m=1: swap */
(void)fp_cond_swap_ct(&a, &b, (int)a.used > (int)b.used ?
(int)a.used : (int)b.used, 0); /* m=0: no swap */
WB_NOTE("fp_cond_swap_ct swap/no-swap exercised");
}
/* fp_div_d (static): divisor 0 guard; b==1 / zero-dividend quick out; power of
* two branch (both c!=NULL and d!=NULL legs); general long-division loop with
* every c/d NULL combination so the "if (c != NULL)" / "if (d != NULL)"
* decisions are covered both ways. */
static void wb_div_d(void)
{
fp_int a, c;
fp_digit d = 0;
fp_init(&a);
fp_init(&c);
fp_set(&a, 0x9ABCDE);
(void)fp_div_d(&a, 0, &c, &d); /* b == 0 */
(void)fp_div_d(&a, 1, &c, &d); /* b == 1 quick out */
(void)fp_div_d(&a, 16, &c, &d); /* power of two, c&d set */
(void)fp_div_d(&a, 16, NULL, &d); /* power of two, c NULL */
(void)fp_div_d(&a, 16, &c, NULL); /* power of two, d NULL */
(void)fp_div_d(&a, 13, &c, &d); /* general, c&d set */
(void)fp_div_d(&a, 13, NULL, &d); /* general, c NULL */
(void)fp_div_d(&a, 13, &c, NULL); /* general, d NULL */
fp_zero(&a);
(void)fp_div_d(&a, 13, &c, &d); /* zero dividend quick out */
(void)fp_mod_d(&a, 13, &d); /* fp_mod_d thin wrapper */
WB_NOTE("fp_div_d branch selectors exercised");
}
/* fp_read_radix_16 (static): the eol_done / whitespace decision. Trailing
* whitespace before any digit is seen (scanned from the end) hits
* "!eol_done && CharIsWhiteSpace" true (continue); a non-hex, non-whitespace
* char returns FP_VAL; embedded whitespace once digits are seen hits eol_done
* true (FP_VAL). Also the '-' sign branch. */
static void wb_read_radix_16(void)
{
fp_int a;
fp_init(&a);
(void)fp_read_radix_16(&a, "1Ab2"); /* plain */
fp_init(&a);
(void)fp_read_radix_16(&a, "1Ab2 "); /* trailing ws: !eol_done true */
fp_init(&a);
(void)fp_read_radix_16(&a, "1A b2"); /* embedded ws: eol_done true */
fp_init(&a);
(void)fp_read_radix_16(&a, "-1Ab2"); /* negative sign */
fp_init(&a);
(void)fp_read_radix_16(&a, "1G2"); /* invalid hex char -> FP_VAL */
WB_NOTE("fp_read_radix_16 whitespace/sign branches exercised");
}
/* fp_invmod_slow (static, general binary extended Euclid): a real inverse
* (a=3 mod 497) and a small odd modulus so the u/v parity while-loops iterate
* their true and false sides. */
static void wb_invmod_slow(void)
{
fp_int a, b, c;
fp_init(&a);
fp_init(&b);
fp_init(&c);
fp_set(&a, 3);
fp_set(&b, 497);
(void)fp_invmod_slow(&a, &b, &c);
WB_NOTE("fp_invmod_slow exercised");
}
/* fp_prime_miller_rabin(_ex) (static): a prime candidate that passes (a=17,
* base 3) and a composite that fails (a=15, base 2) so the composite / probably
* prime decision arms are both taken in-binary. */
static void wb_miller_rabin(void)
{
fp_int a, b;
int res = 0;
fp_init(&a);
fp_init(&b);
fp_set(&a, 17);
fp_set(&b, 3);
(void)fp_prime_miller_rabin(&a, &b, &res); /* probable prime */
fp_set(&a, 15);
fp_set(&b, 2);
(void)fp_prime_miller_rabin(&a, &b, &res); /* composite */
WB_NOTE("fp_prime_miller_rabin prime/composite exercised");
}
/* _fp_exptmod_ct / _fp_exptmod_nct / _fp_exptmod_base_2 (static engines):
* exercise directly with a small odd modulus so their internal
* window/montgomery loops run. Which of the two _fp_exptmod_ct definitions is
* compiled depends on TFM_TIMING_RESISTANT; whichever it is, this reaches it.
*/
static void wb_exptmod_engines(void)
{
fp_int g, x, p, y;
fp_init(&g);
fp_init(&x);
fp_init(&p);
fp_init(&y);
fp_set(&g, 4);
fp_set(&x, 13);
fp_set(&p, 497); /* odd modulus */
#ifdef TFM_TIMING_RESISTANT
(void)_fp_exptmod_ct(&g, &x, x.used, &p, &y);
#else
(void)_fp_exptmod_nct(&g, &x, &p, &y);
#endif
/* base-2 special engine: X as the exponent, P the modulus. */
(void)_fp_exptmod_base_2(&x, x.used, &p, &y);
WB_NOTE("_fp_exptmod_* engines exercised");
}
#ifdef HAVE_INTEL_MULX
/* fp_montgomery_reduce_mulx (static, only compiled with HAVE_INTEL_MULX): the
* MULX-accelerated Montgomery reduction, non-constant-time and constant-time
* decision arms. */
static void wb_montgomery_mulx(void)
{
fp_int a, m;
fp_digit mp = 0;
fp_init(&a);
fp_init(&m);
fp_set(&m, 0xF1); /* odd modulus */
if (fp_montgomery_setup(&m, &mp) == FP_OKAY) {
fp_set(&a, 3);
(void)fp_montgomery_reduce_mulx(&a, &m, mp, 0);
fp_set(&a, 3);
(void)fp_montgomery_reduce_mulx(&a, &m, mp, 1);
}
WB_NOTE("fp_montgomery_reduce_mulx exercised");
}
#endif /* HAVE_INTEL_MULX */
#endif /* USE_FAST_MATH */
/* ------------------------------------------------------------------------- *
* Additional gap drivers (merged from the former _gap TU). These reach into
* fp_int struct fields directly (->used, ->dp, ->sign), so they additionally
* require WOLFSSL_PUBLIC_MP (non-opaque fp_int).
*
* Targeted gaps (wolfcrypt/src/tfm.c), by class:
* Class 1 fp_mul()/fp_sqr() comba tail-clear loop: destination ->used
* left negative by a corrupted caller (structurally impossible
* via any legitimate call sequence).
* Class 2 fp_invmod_slow() entry guard - fp_invmod() (the only public
* caller) already rejects a negative/zero modulus before calling
* fp_invmod_slow(), so this file-static function's own defensive
* copy of the same guard can only be exercised by calling it
* directly.
* Class 3 fp_cnt_lsb() least-significant-zero-digit loop running out of
* digits: only reachable with a non-normalized (all-zero-digit,
* ->used > 0) fp_int.
* ------------------------------------------------------------------------- */
#if defined(USE_FAST_MATH) && defined(WOLFSSL_PUBLIC_MP)
/* ------------------------------------------------------------------------- *
* Class 1a: fp_mul() comba tail-clear loop.
*
* for (y = C->used; y >= 0 && y < oldused; y++) { C->dp[y] = 0; }
*
* y is assigned from C->used at the "clean:" label - on every legitimate
* call path C->used is a real digit count and can never be negative, so
* "y >= 0" is always true there. The only way to show it false is to hand
* fp_mul() a destination whose ->used field was corrupted to a negative
* value beforehand (never producible by any public mutator) and take the
* early "goto clean" path (operand size over FP_SIZE) so fp_mul() never
* overwrites C->used itself before the loop runs.
* ------------------------------------------------------------------------- */
static void wb_fp_mul_negative_used(void)
{
fp_int a, b, c;
XMEMSET(&a, 0, sizeof(a));
XMEMSET(&b, 0, sizeof(b));
XMEMSET(&c, 0, sizeof(c));
fp_init(&a);
fp_init(&b);
fp_init(&c);
/* Normal small multiply: C->used ends up >= 0 via the ordinary path
* (the "y >= 0" true side, exercised here for this binary's own
* independence pair). */
fp_set(&a, 3);
fp_set(&b, 5);
(void)fp_mul(&a, &b, &c);
/* Force the "operand too big" early-exit (goto clean before C is
* touched), with C->used corrupted to a negative value beforehand:
* "y >= 0" now evaluates false on the very first loop check, so the
* clear loop body never runs. */
fp_set(&a, 1);
a.used = FP_SIZE; /* a->used + b->used >= FP_SIZE forces the overflow
* check (fails before any comba call touches C) */
fp_set(&b, 1);
c.used = -1; /* corrupted: not reachable via any public mutator */
(void)fp_mul(&a, &b, &c);
c.used = 0; /* restore before fp_clear-equivalent cleanup below */
WB_NOTE("fp_mul comba tail-clear loop 'y >= 0' both sides exercised");
}
/* ------------------------------------------------------------------------- *
* Class 1b: fp_sqr() comba tail-clear loop - same reasoning as fp_mul()
* above, on the B (destination) operand of fp_sqr(A, B).
* ------------------------------------------------------------------------- */
static void wb_fp_sqr_negative_used(void)
{
fp_int a, b;
XMEMSET(&a, 0, sizeof(a));
XMEMSET(&b, 0, sizeof(b));
fp_init(&a);
fp_init(&b);
fp_set(&a, 5);
(void)fp_sqr(&a, &b);
fp_set(&a, 1);
a.used = FP_SIZE; /* a->used * 2 > FP_SIZE forces the overflow check */
b.used = -1; /* corrupted, unreachable via any public mutator */
(void)fp_sqr(&a, &b);
b.used = 0;
WB_NOTE("fp_sqr comba tail-clear loop 'y >= 0' both sides exercised");
}
/* ------------------------------------------------------------------------- *
* Class 2: fp_invmod_slow() entry guard.
*
* if (b->sign == FP_NEG || fp_iszero(b) == FP_YES) { return FP_VAL; }
*
* fp_invmod() - the only in-tree caller of this file-static helper - itself
* rejects a negative or zero modulus (the identical check) before ever
* reaching the `fp_iseven(b) == FP_YES` dispatch that calls
* fp_invmod_slow(); a caller can therefore never observe fp_invmod_slow()
* running its OWN copy of that guard with a negative/zero 'b'. Call it
* directly to complete both independence pairs.
* ------------------------------------------------------------------------- */
static void wb_fp_invmod_slow_entry_guard(void)
{
fp_int a, b, c;
XMEMSET(&a, 0, sizeof(a));
XMEMSET(&b, 0, sizeof(b));
XMEMSET(&c, 0, sizeof(c));
fp_init(&a);
fp_init(&b);
fp_init(&c);
/* b->sign == FP_NEG (true), fp_iszero(b) == FP_YES (false, b is -10):
* isolates "b->sign == FP_NEG" (cond0) with cond1 held false. */
fp_set(&a, 3);
fp_set(&b, 10);
b.sign = FP_NEG;
if (fp_invmod_slow(&a, &b, &c) != WC_NO_ERR_TRACE(FP_VAL)) {
wb_fail = 1;
WB_NOTE("unexpected: fp_invmod_slow did not reject negative b");
}
/* b->sign == FP_ZPOS (false), fp_iszero(b) == FP_YES (true, b == 0):
* isolates "fp_iszero(b) == FP_YES" (cond1) with cond0 held false. */
fp_zero(&b);
if (fp_invmod_slow(&a, &b, &c) != WC_NO_ERR_TRACE(FP_VAL)) {
wb_fail = 1;
WB_NOTE("unexpected: fp_invmod_slow did not reject zero b");
}
/* Both false (b positive, nonzero, even): falls through to the actual
* HAC-based computation - completes both operands' independence pairs
* (cond0 = F held across this call and the negative-b call above via
* the entry guard's own true/false halves; cond1 = F held across this
* call and the zero-b call above). */
fp_set(&a, 3);
fp_set(&b, 10);
(void)fp_invmod_slow(&a, &b, &c);
WB_NOTE("fp_invmod_slow entry guard both operands' pairs exercised");
}
/* ------------------------------------------------------------------------- *
* Class 3: fp_cnt_lsb() least-significant-zero-digit loop.
*
* for (x = 0; x < a->used && a->dp[x] == 0; x++) {}
*
* fp_cnt_lsb() is public, but a normalized nonzero fp_int always has SOME
* nonzero digit below a->used (that is what "nonzero" means), so the loop
* always terminates via "a->dp[x] == 0" going false, never via running out
* of digits ("x < a->used" going false) - fp_iszero() (checked first, by
* a->used == 0) rejects the only other way to reach x == a->used. Reach the
* "x < a->used" false side with a non-normalized all-zero-digit value
* (->used > 0, every digit 0) - a state no public mutator can produce.
* ------------------------------------------------------------------------- */
static void wb_fp_cnt_lsb_all_zero_digits(void)
{
fp_int a;
XMEMSET(&a, 0, sizeof(a));
fp_init(&a);
/* Non-normalized: ->used > 0 but every digit below ->used is 0.
* fp_iszero() only checks ->used == 0, so this slips past it and the
* for-loop runs to x == a->used (loop condition false via
* "x < a->used", not via dp[x] == 0 going false). The one-past-used
* slot a->dp[a->used] is deliberately left NONZERO here: fp_cnt_lsb()
* reads it as 'q' right after this loop and, if it were also zero,
* the subsequent "while (qq == 0)" bit-scan would spin forever (q
* never becomes nonzero) - a latent hang in fp_cnt_lsb() for a fully
* zero-digit input, not something this MC/DC pair needs to trigger. */
a.used = 2;
a.dp[0] = 0;
a.dp[1] = 0;
a.dp[2] = 1;
(void)fp_cnt_lsb(&a);
/* Normalized nonzero value (ordinary, API-reachable case), repeated
* here so both sides of "x < a->used" are shown within this binary. */
a.used = 1;
a.dp[0] = 4;
(void)fp_cnt_lsb(&a);
WB_NOTE("fp_cnt_lsb least-significant-zero-digit loop exercised");
}
/* ------------------------------------------------------------------------- *
* Relocated from tests/api (test_wolfmath.c). These drive library-internal,
* non-exported fp_* symbols (fp_set/fp_mul_2/fp_mul_2d/fp_invmod_mont_ct/
* fp_exptmod_nct/...) that are declared WITHOUT MP_API and so hidden under
* -fvisibility=hidden in a shared-library build: calling them from tests/api
* broke the normal (shared) wolfSSL CI link. Compiled here via #include of
* tfm.c they resolve directly. Coverage-only: both directions of each decision
* are executed; no return values are asserted (the exact call arguments are
* preserved, since those are what drive each MC/DC pair).
* ------------------------------------------------------------------------- */
/*
* Testing fp_mul_2/fp_mul_2d/fp_div_2d/fp_mod_2d/fp_invmod/
* fp_invmod_mont_ct/fp_to_unsigned_bin_len/mp_read_radix/mp_radix_size/
* mp_toradix/mp_rand_prime/mp_prime_is_prime(_ex): argument-check,
* capacity/FP_SIZE-guard and sign-dispatch decision branches of the
* FASTMATH (tfm.c) backend (bigint-tfm module).
*/
static void wb_TfmDecisionCoverage(void)
{
#if !defined(WOLFSSL_SP_MATH) && !defined(WOLFSSL_SP_MATH_ALL)
fp_int a;
fp_int b;
fp_int c;
XMEMSET(&a, 0, sizeof(a));
XMEMSET(&b, 0, sizeof(b));
XMEMSET(&c, 0, sizeof(c));
/* fp_mul_2: range check is
* (a->used > FP_SIZE-1) || ((a->used == FP_SIZE-1) && (top bit set))
* Four calls complete all three operands' independence pairs (masking
* MC/DC: the OR's first operand shares 'a->used' with the AND's first
* operand, so they cannot vary independently of each other - each is
* paired against the boundary case instead). */
fp_zero(&a);
a.used = FP_SIZE; /* a->used > FP_SIZE-1: true, rest masked */
(void)fp_mul_2(&a, &b);
fp_zero(&a);
a.used = FP_SIZE - 2; /* both OR operands false: ordinary small value */
a.dp[FP_SIZE - 3] = 1;
(void)fp_mul_2(&a, &b);
/* Note: the guard reads the FIXED sentinel slot a->dp[FP_SIZE-1] (one
* past the last digit ->used == FP_SIZE-1 actually counts), not
* a->dp[a->used-1] - so the "top bit set" side needs that specific
* slot poked directly (fp_zero() leaves it 0, which every legitimate
* caller's value would too, since it is beyond ->used). */
fp_zero(&a);
a.used = FP_SIZE - 1; /* a->used == FP_SIZE-1 true, top bit clear */
a.dp[FP_SIZE - 2] = 1;
(void)fp_mul_2(&a, &b);
fp_zero(&a);
a.used = FP_SIZE - 1; /* a->used == FP_SIZE-1 true, top bit SET */
a.dp[FP_SIZE - 2] = 1;
a.dp[FP_SIZE - 1] = (fp_digit)1 << (DIGIT_BIT - 1);
(void)fp_mul_2(&a, &b);
/* fp_mul_2d: "carry && x < FP_SIZE" - carry true with room to store it
* (x < FP_SIZE, small 'a') vs carry true with the destination already
* completely full (x == FP_SIZE, top digit high bit set). */
fp_zero(&a);
fp_set(&a, 1);
a.dp[0] = (fp_digit)1 << (DIGIT_BIT - 1); /* shifting by 1 overflows */
(void)fp_mul_2d(&a, 1, &c); /* carry true, x(1) < FP_SIZE */
fp_zero(&a);
a.used = FP_SIZE;
a.dp[FP_SIZE - 1] = (fp_digit)1 << (DIGIT_BIT - 1);
(void)fp_mul_2d(&a, 1, &c); /* x==FP_SIZE */
/* fp_div_2d: "a == c && d != NULL" / "a != c && d != NULL". Three
* calls complete both operands' independence pairs. */
fp_zero(&a);
fp_set(&a, 200);
fp_div_2d(&a, 3, &a, &c); /* a == c (true), d != NULL (true) */
fp_zero(&a);
fp_set(&a, 200);
fp_div_2d(&a, 3, &b, &c); /* a != c (false/true pair vs above), d!=NULL */
fp_zero(&a);
fp_set(&a, 200);
fp_div_2d(&a, 3, &b, NULL); /* a != c, d == NULL: isolates d!=NULL */
/* fp_mod_2d: first guard "c->sign==FP_ZPOS && b>=DIGIT_BIT*a->used"
* (c is fp_copy(a,c) inside, so c->sign tracks a->sign); second guard
* "c->sign==FP_NEG && bmax>=FP_SIZE" (only reached once the first is
* false). */
fp_zero(&a);
fp_set(&a, 5); /* a->used == 1 */
fp_mod_2d(&a, (int)DIGIT_BIT, &c); /* ZPOS(T) && b>=DIGIT_BIT*1(T):
early return, c == a unchanged (5) */
(void)fp_cmp_d(&c, 5);
fp_zero(&a);
fp_set(&a, 5);
fp_setneg(&a);
fp_mod_2d(&a, (int)DIGIT_BIT, &c); /* ZPOS(F): isolates the sign operand */
fp_zero(&a);
fp_set(&a, 5);
fp_mod_2d(&a, 0, &c); /* ZPOS(T), b>=... (F): isolates the 'b' operand */
(void)fp_iszero(&c);
/* second guard: build a huge negative 'a' (a->used == FP_SIZE) so the
* first guard's "b >= DIGIT_BIT*a->used" is false for a 'b' just under
* FP_SIZE*DIGIT_BIT, yet bmax = ceil(b/DIGIT_BIT) can still reach
* FP_SIZE. */
fp_zero(&a);
a.used = FP_SIZE;
a.dp[FP_SIZE - 1] = 1;
fp_setneg(&a);
fp_mod_2d(&a, (int)(DIGIT_BIT * FP_SIZE) - 1, &c); /* NEG(T), bmax>=FP_SIZE(T) */
fp_zero(&a);
a.used = FP_SIZE;
a.dp[FP_SIZE - 1] = 1;
fp_mod_2d(&a, (int)(DIGIT_BIT * FP_SIZE) - 1, &c); /* ZPOS: isolates NEG op */
fp_zero(&a);
a.used = FP_SIZE;
a.dp[FP_SIZE - 1] = 1;
fp_setneg(&a);
fp_mod_2d(&a, 4, &c); /* NEG(T), bmax small (F): isolates bmax operand */
/* fp_invmod: "b->sign==FP_NEG || fp_iszero(b)==FP_YES" (fp_iszero(b)
* half already shown elsewhere; complete the sign operand's pair). */
fp_zero(&a);
fp_set(&a, 3);
fp_zero(&b);
fp_set(&b, 7);
fp_setneg(&b);
(void)fp_invmod(&a, &b, &c);
fp_zero(&b);
fp_set(&b, 7);
(void)fp_invmod(&a, &b, &c);
/* fp_invmod_mont_ct: "(a->used*2 > FP_SIZE) || (b->used*2 > FP_SIZE)".
* Three calls complete both operands' pairs. */
fp_zero(&a);
a.used = FP_SIZE;
fp_zero(&b);
fp_set(&b, 3);
(void)fp_invmod_mont_ct(&a, &b, &c, 1);
fp_zero(&a);
fp_set(&a, 2);
fp_zero(&b);
b.used = FP_SIZE;
(void)fp_invmod_mont_ct(&a, &b, &c, 1);
fp_zero(&a);
fp_set(&a, 2);
fp_zero(&b);
fp_set(&b, 3);
(void)fp_invmod_mont_ct(&a, &b, &c, 1);
/* fp_to_unsigned_bin_len: "i == a->used-1 && (a->dp[i]>>j) != 0" -
* output length exactly reaches the top digit, with (A) enough spare
* high bits to hold it (success) and (B) not enough (truncated,
* FP_VAL). A third call with a fully-sized buffer isolates the
* "i == a->used-1" operand (never reached: overall false). */
#if DIGIT_BIT == 64 || DIGIT_BIT == 32 || DIGIT_BIT == 16
{
byte buf[8];
XMEMSET(buf, 0, sizeof(buf));
fp_zero(&a);
fp_set(&a, 0xFF); /* fits in 1 byte */
(void)fp_to_unsigned_bin_len(&a, buf, 4); /* i!=used-1 */
XMEMSET(buf, 0, sizeof(buf));
(void)fp_to_unsigned_bin_len(&a, buf, 1); /* i==used-1,
top bits already 0 */
fp_zero(&a);
fp_set(&a, 0x1FF); /* needs 2 bytes */
XMEMSET(buf, 0, sizeof(buf));
(void)fp_to_unsigned_bin_len(&a, buf, 1); /* i==used-1, top bits nonzero */
}
#endif
/* mp_read_radix (fp_read_radix): "radix < 2 || radix > 64". */
(void)mp_read_radix(&a, "10", 1);
(void)mp_read_radix(&a, "10", 65);
(void)mp_read_radix(&a, "10", MP_RADIX_DEC);
/* mp_radix_size / mp_toradix: "radix < 2 || radix > 64" (each function
* has its own copy of the guard). */
{
int size = 0;
char buf[8];
XMEMSET(buf, 0, sizeof(buf));
(void)mp_radix_size(&a, 1, &size);
(void)mp_radix_size(&a, 65, &size);
(void)mp_radix_size(&a, MP_RADIX_DEC, &size);
(void)mp_toradix(&a, buf, 1);
(void)mp_toradix(&a, buf, 65);
XMEMSET(buf, 0, sizeof(buf));
(void)mp_toradix(&a, buf, MP_RADIX_DEC);
}
#if !defined(WC_NO_RNG)
/* mp_rand_prime (fp_randprime): "len < 2 || len > 512" (len<2 already
* shown elsewhere; complete the len>512 operand's pair). */
{
WC_RNG rng;
XMEMSET(&rng, 0, sizeof(rng));
(void)wc_InitRng(&rng);
fp_zero(&a);
/* mp_rand_prime (fp_randprime) is declared/defined only under
* WOLFSSL_KEY_GEN; gate the calls to match so keygen-off variants
* neither implicitly declare nor leave it an undefined reference. */
#ifdef WOLFSSL_KEY_GEN
(void)mp_rand_prime(&a, 513, &rng, HEAP_HINT);
(void)mp_rand_prime(&a, 32, &rng, HEAP_HINT);
#endif
/* mp_prime_is_prime_ex (own trial-division/Miller-Rabin engine):
* "t <= 0 || t > FP_PRIME_SIZE" (t<=0 already shown elsewhere;
* complete the t>FP_PRIME_SIZE operand's pair). */
{
int result = 0;
fp_zero(&a);
fp_set(&a, 17);
(void)mp_prime_is_prime_ex(&a, FP_PRIME_SIZE + 1, &result, &rng);
(void)mp_prime_is_prime_ex(&a, 8, &result, &rng);
(void)result;
}
(void)wc_FreeRng(&rng);
}
#endif /* !WC_NO_RNG */
/* mp_prime_is_prime (fp_isprime_ex): trial-division loop
* "res != MP_OKAY || d == 0" - only the 'd == 0' half is targeted here
* (res != MP_OKAY is a defensive residual, see REPORT.md); and the
* Miller-Rabin loop "err != FP_OKAY || res == FP_NO": a composite that
* survives trial division (product of two primes above the built-in
* table) reaches Miller-Rabin and is correctly rejected there. */
{
int result = 0;
fp_zero(&a);
fp_set(&a, 17); /* prime: table hit */
(void)mp_prime_is_prime(&a, 8, &result);
(void)result;
fp_zero(&a);
fp_set(&a, 6); /* divisible by 2 and 3 */
(void)mp_prime_is_prime(&a, 8, &result);
(void)result;
/* 1013 * 1019 = 1032247: both factors are larger than every prime
* in the FP_PRIME_SIZE (256) built-in table, so trial division
* finds no divisor and the composite reaches Miller-Rabin, which
* correctly reports it composite (res == FP_NO). */
fp_zero(&a);
fp_set(&a, 1032247u);
(void)mp_prime_is_prime(&a, 8, &result);
(void)result;
}
mp_clear(&a);
mp_clear(&b);
mp_clear(&c);
#endif /* !WOLFSSL_SP_MATH && !WOLFSSL_SP_MATH_ALL */
WB_NOTE("TfmDecisionCoverage decision branches exercised");
}
/*
* Testing fp_exptmod/fp_exptmod_ex/fp_exptmod_nct: the negative-exponent
* (X->sign == FP_NEG) branch's "invmod succeeded" / "modulus is negative"
* decision chain, common to all three entry points, and the tail
* "mode == 2 && bitcpy > 0" leftover-window decision inside the shared
* non-constant-time engine (bigint-tfm module, tfm.c).
*/
static void wb_TfmExptModDecisionCoverage(void)
{
#if !defined(POSITIVE_EXP_ONLY) && \
!defined(WOLFSSL_SP_MATH) && !defined(WOLFSSL_SP_MATH_ALL)
fp_int g;
fp_int x;
fp_int p;
fp_int y;
XMEMSET(&g, 0, sizeof(g));
XMEMSET(&x, 0, sizeof(x));
XMEMSET(&p, 0, sizeof(p));
XMEMSET(&y, 0, sizeof(y));
/* fp_exptmod: "fp_iszero(P) || (P->used > FP_SIZE/2)" - the iszero half
* is exercised elsewhere; complete the size operand's pair with a huge
* (but nonzero) modulus. */
fp_set(&g, 3);
fp_set(&x, 2);
fp_zero(&p);
p.used = (FP_SIZE / 2) + 1;
p.dp[p.used - 1] = 1;
(void)fp_exptmod(&g, &x, &p, &y);
fp_set(&p, 15);
(void)fp_exptmod(&g, &x, &p, &y);
/* fp_exptmod / fp_exptmod_ex / fp_exptmod_nct share the same
* negative-exponent chain:
* if ((err == 0) && (P->sign == FP_NEG)) { err = fp_add(Y, P, Y); }
* where 'err' comes from invmod(G, |P|, ...). Three calls per entry
* point complete both operands' independence pairs:
* call A: G=3, X=-3, P=7 (invmod succeeds: err==0 T; P ZPOS: F)
* call B: G=3, X=-3, P=-7 (invmod succeeds: err==0 T; P NEG: T)
* call C: G=7, X=-3, P=-7 (invmod fails (gcd=7))
* Pair (A,B) isolates the P->sign operand (err==0 held true).
*
* CORRECTION (step 7): call C does NOT isolate the err==0 operand. The
* decision sits inside `if (err == FP_OKAY) { ... }`, so an fp_invmod
* failure skips the whole block and never reaches the test - `err` there
* can only come from _fp_exptmod_ct/_nct. Call C is kept because it does
* cover the invmod-failure return path, but the err==0 operand's FALSE
* half is closed in test_tfm_fault_whitebox.c, by an even |P| that lets
* fp_invmod succeed (via fp_invmod_slow) and then makes
* fp_montgomery_setup reject the modulus inside the engine. */
fp_set(&g, 3);
fp_set(&x, 3);
fp_setneg(&x);
fp_set(&p, 7);
(void)fp_exptmod(&g, &x, &p, &y); /* call A */
fp_set(&g, 3);
fp_set(&x, 3);
fp_setneg(&x);
fp_set(&p, 7);
fp_setneg(&p);
(void)fp_exptmod(&g, &x, &p, &y); /* call B */
fp_set(&g, 7);
fp_set(&x, 3);
fp_setneg(&x);
fp_set(&p, 7);
fp_setneg(&p);
(void)fp_exptmod(&g, &x, &p, &y); /* call C */
/* fp_exptmod_ex: same size guard ("fp_iszero(P)||P->used>FP_SIZE/2")
* and the same negative-exponent chain, reached through its own entry
* point (digits == 0 lets it pick X->used internally). */
fp_set(&g, 3);
fp_set(&x, 2);
fp_zero(&p);
p.used = (FP_SIZE / 2) + 1;
p.dp[p.used - 1] = 1;
(void)fp_exptmod_ex(&g, &x, 0, &p, &y);
fp_set(&g, 3);
fp_set(&x, 3);
fp_setneg(&x);
fp_set(&p, 7);
(void)fp_exptmod_ex(&g, &x, 0, &p, &y); /* call A */
fp_set(&g, 3);
fp_set(&x, 3);
fp_setneg(&x);
fp_set(&p, 7);
fp_setneg(&p);
(void)fp_exptmod_ex(&g, &x, 0, &p, &y); /* call B */
fp_set(&g, 7);
fp_set(&x, 3);
fp_setneg(&x);
fp_set(&p, 7);
fp_setneg(&p);
(void)fp_exptmod_ex(&g, &x, 0, &p, &y); /* call C */
/* fp_exptmod_nct: always uses the non-constant-time engine regardless
* of TFM_TIMING_RESISTANT, so its own copy of the negative-exponent
* chain is independently reachable through this entry point. */
fp_set(&g, 3);
fp_set(&x, 3);
fp_setneg(&x);
fp_set(&p, 7);
(void)fp_exptmod_nct(&g, &x, &p, &y); /* call A */
fp_set(&g, 3);
fp_set(&x, 3);
fp_setneg(&x);
fp_set(&p, 7);
fp_setneg(&p);
(void)fp_exptmod_nct(&g, &x, &p, &y); /* call B */
fp_set(&g, 7);
fp_set(&x, 3);
fp_setneg(&x);
fp_set(&p, 7);
fp_setneg(&p);
(void)fp_exptmod_nct(&g, &x, &p, &y); /* call C */
/* _fp_exptmod_nct tail (reached via the public fp_exptmod_nct):
* "mode == 2 && bitcpy > 0" - winsize is chosen from fp_count_bits(X):
* <=21 bits picks winsize 1, which flushes every bit immediately
* (bitcpy is always 0 at the tail: the false side); a bigger X (e.g.
* 30 bits, winsize 3) leaves a partial window at the end (bitcpy > 0:
* the true side). */
fp_set(&g, 3);
fp_set(&x, 7); /* 3 bits: winsize 1, bitcpy==0 tail */
fp_set(&p, 15);
(void)fp_exptmod_nct(&g, &x, &p, &y);
fp_set(&g, 3);
fp_set(&x, 0x3FFFFFFF); /* 30 bits: winsize 3 */
fp_set(&p, 15);
(void)fp_exptmod_nct(&g, &x, &p, &y);
mp_clear(&g);
mp_clear(&x);
mp_clear(&p);
mp_clear(&y);
#endif /* !POSITIVE_EXP_ONLY && !WOLFSSL_SP_MATH && !WOLFSSL_SP_MATH_ALL */
WB_NOTE("TfmExptModDecisionCoverage decision branches exercised");
}
#endif /* USE_FAST_MATH && WOLFSSL_PUBLIC_MP */
/* ------------------------------------------------------------------------
* Public-entry ARGUMENT-GUARD residuals.
*
* suite/reports/bigint-tfm/the uncovered-condition report lists several multi-operand OR guards at
* the top of public entry points whose operands the ordinary tests only ever
* present all-false (they always pass valid arguments), so no operand's
* independence pair is shown. Each is closed here by calling the entry point
* once per operand with exactly THAT operand true and the rest false, against
* the all-false row that the same calls' valid form provides -- both halves in
* this one binary.
*
* These need no fault injection: they are pure argument shapes.
* ---------------------------------------------------------------------- */
static void wb_entry_arg_guards(void)
{
#ifdef WOLFSSL_PUBLIC_MP
fp_int a;
fp_int b;
fp_int c;
unsigned char out[64];
int res = 0;
int ret;
fp_init(&a);
fp_init(&b);
fp_init(&c);
fp_set(&a, 7);
fp_set(&b, 11);
XMEMSET(out, 0, sizeof(out));
/* 3918: fp_to_unsigned_bin_len_ct():
* if ((a == NULL) || (out == NULL) || (outSz < 0))
* all-false row plus one row per operand. */
ret = fp_to_unsigned_bin_len_ct(&a, out, (int)sizeof(out));
if (ret != MP_OKAY) {
WB_NOTE("fp_to_unsigned_bin_len_ct(valid) unexpectedly failed");
wb_fail = 1;
}
if (fp_to_unsigned_bin_len_ct(NULL, out, (int)sizeof(out)) !=
WC_NO_ERR_TRACE(MP_VAL) ||
fp_to_unsigned_bin_len_ct(&a, NULL, (int)sizeof(out)) !=
WC_NO_ERR_TRACE(MP_VAL) ||
fp_to_unsigned_bin_len_ct(&a, out, -1) != WC_NO_ERR_TRACE(MP_VAL)) {
WB_NOTE("fp_to_unsigned_bin_len_ct arg guard did not reject");
wb_fail = 1;
}
/* 5480: fp_lcm():
* if (fp_iszero(a) == FP_YES || fp_iszero(b) == FP_YES)
* a=0 isolates operand 0; b=0 isolates operand 1; (7, 11) is all-false. */
if (fp_lcm(&a, &b, &c) != FP_OKAY) {
WB_NOTE("fp_lcm(valid) unexpectedly failed");
wb_fail = 1;
}
{
fp_int z;
fp_init(&z); /* zero */
if (fp_lcm(&z, &b, &c) != FP_VAL ||
fp_lcm(&a, &z, &c) != FP_VAL) {
WB_NOTE("fp_lcm zero guard did not reject");
wb_fail = 1;
}
}
#if !defined(WC_NO_RNG)
/* 5222: mp_prime_is_prime_ex():
* if (a == NULL || result == NULL || rng == NULL)
* 5226: same function:
* if (t <= 0 || t > FP_PRIME_SIZE)
* One row per operand plus the all-false valid call. */
{
WC_RNG rng;
XMEMSET(&rng, 0, sizeof(rng));
if (wc_InitRng(&rng) != 0) {
WB_NOTE("wc_InitRng failed; prime_is_prime_ex guards skipped");
}
else {
/* all-false: valid pointers and 0 < t <= FP_PRIME_SIZE */
(void)mp_prime_is_prime_ex(&a, 8, &res, &rng);
if (mp_prime_is_prime_ex(NULL, 8, &res, &rng) != FP_VAL ||
mp_prime_is_prime_ex(&a, 8, NULL, &rng) != FP_VAL ||
mp_prime_is_prime_ex(&a, 8, &res, NULL) != FP_VAL) {
WB_NOTE("mp_prime_is_prime_ex NULL guard did not reject");
wb_fail = 1;
}
if (mp_prime_is_prime_ex(&a, 0, &res, &rng) != FP_VAL ||
mp_prime_is_prime_ex(&a, FP_PRIME_SIZE + 1, &res, &rng)
!= FP_VAL) {
WB_NOTE("mp_prime_is_prime_ex t-range guard did not reject");
wb_fail = 1;
}
wc_FreeRng(&rng);
}
}
#endif /* !WC_NO_RNG */
/* 2858: fp_exptmod()'s
* if (fp_iszero(P) || (P->used > (FP_SIZE/2)))
* P == 0 isolates operand 0 against the ordinary valid-modulus row. */
{
fp_int zeroP;
fp_int r;
fp_init(&zeroP);
fp_init(&r);
(void)fp_exptmod(&a, &b, &zeroP, &r); /* operand 0 true */
(void)fp_exptmod(&a, &b, &b, &r); /* both false */
}
WB_NOTE("public-entry argument-guard rows driven");
#endif /* WOLFSSL_PUBLIC_MP */
}
int main(void)
{
setvbuf(stdout, NULL, _IONBF, 0);
printf("tfm.c white-box MC/DC supplement\n");
#if !defined(USE_FAST_MATH)
printf(" USE_FAST_MATH not defined; nothing to exercise\n");
return 0;
#else
wb_is_power_of_two();
wb_cond_swap_ct();
wb_div_d();
wb_read_radix_16();
wb_invmod_slow();
wb_miller_rabin();
wb_exptmod_engines();
#ifdef HAVE_INTEL_MULX
wb_montgomery_mulx();
#endif
#ifdef WOLFSSL_PUBLIC_MP
wb_fp_mul_negative_used();
wb_fp_sqr_negative_used();
wb_fp_invmod_slow_entry_guard();
wb_fp_cnt_lsb_all_zero_digits();
wb_TfmDecisionCoverage();
wb_TfmExptModDecisionCoverage();
#endif
wb_entry_arg_guards();
printf("done (%s)\n", wb_fail ? "with skips" : "ok");
/* Setup failures surface as skips, not failures: a nonzero exit makes the
* suite discard this variant's coverage. */
return 0;
#endif
}