tests: drive the infra cryptocb devId, memory bucket and wc_port errno guards

pull/11122/head
Daniele Lacamera 2026-08-12 10:01:21 +02:00
parent 860210a330
commit 7bbd6e2f39
3 changed files with 280 additions and 17 deletions

View File

@ -95,8 +95,52 @@
* The SHA-384/SHA-512 post-cb guards additionally need dev->cb to return 0
* (success) for the fallback attempt specifically - see wb_cb_hash_fallback_ok
* below for how that is done without needing to inject a fault mid-dispatch.
*
* Third pass - the "no devId argument" guard at cryptocb.c :1159 / :1189,
* `if (dev == NULL || dev->cb == NULL) dev = wc_CryptoCb_FindDeviceByIndex(0);`
* in wc_CryptoCb_Curve25519MakePub() and wc_CryptoCb_Curve25519Generic().
* These two functions take no key struct, so they resolve their device with
* wc_CryptoCb_FindDevice(INVALID_DEVID, WC_ALGO_TYPE_PK) and the guard is
* driven purely by the state of the gCryptoDev[] table:
* - (F,T) at least one free slot exists. wc_CryptoCb_ClearDev() leaves
* free slots at devId == INVALID_DEVID with cb == NULL, so
* wc_CryptoCb_GetDevice(INVALID_DEVID) returns that slot:
* dev != NULL but dev->cb == NULL.
* - (T,-) every one of MAX_CRYPTO_DEVID_CALLBACKS slots is registered, so
* no slot holds INVALID_DEVID and wc_CryptoCb_GetDevice() returns
* NULL. This is reachable with the public registration API alone.
* - (F,F) needs a lookup that yields a device with a non-NULL callback.
* wc_CryptoCb_RegisterDevice() explicitly refuses devId ==
* INVALID_DEVID ("INVALID_DEVID marks a free slot"), so no free
* slot can ever carry a callback and the plain lookup cannot
* produce this state. The supported mechanism that can is
* WOLF_CRYPTO_CB_FIND: wc_CryptoCb_FindDevice() first passes the
* requested devId through the registered find callback, so a find
* callback that maps INVALID_DEVID onto a real registered devId
* makes the lookup return a device WITH a callback. Neither infra
* variant's user_settings defines WOLF_CRYPTO_CB_FIND, so this
* file compiles it in for its own translation unit, exactly the
* way test_memory_whitebox.c compiles WOLFSSL_STATIC_MEMORY in for
* memory.c. It costs nothing in denominator: the only code the
* macro adds to cryptocb.c is a file-static pointer, the setter
* wc_CryptoCb_SetDeviceFindCb(), and a SINGLE-condition
* `if (CryptoCb_FindCb != NULL)` inside wc_CryptoCb_FindDevice()
* (single-condition decisions carry no MC/DC conditions), and it
* changes no struct layout shared with the linked archive.
* The find callback is installed only around the (F,F) vectors and
* reset to NULL immediately afterwards, so every other decision in
* this file is evaluated with CryptoCb_FindCb == NULL, i.e. with
* wc_CryptoCb_FindDevice() behaving exactly as in the shipped
* variants.
*/
/* See the "third pass" note above: compiled in for this TU only, so the
* :1159/:1189 (F,F) vector can be driven through the public
* wc_CryptoCb_SetDeviceFindCb() API. Must precede the #include below. */
#ifndef WOLF_CRYPTO_CB_FIND
#define WOLF_CRYPTO_CB_FIND
#endif
#include <wolfcrypt/src/cryptocb.c>
#include <stdio.h>
@ -112,6 +156,11 @@ static int wb_fail = 0;
#define WB_DEVID_NOCB 2
#define WB_DEVID_NONE 424242
/* Base devId for the MAX_CRYPTO_DEVID_CALLBACKS "fill the whole table"
* registrations used by the :1159/:1189 (T,-) vector. Distinct from every
* other devId in this file and never equal to INVALID_DEVID. */
#define WB_DEVID_FILL 500
static int wb_cb(int devId, wc_CryptoInfo* info, void* ctx)
{
(void)devId;
@ -144,6 +193,20 @@ static int wb_cb_hash_fallback_ok(int devId, wc_CryptoInfo* info, void* ctx)
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
#ifdef WOLF_CRYPTO_CB_FIND
/* Find callback used ONLY by the :1159/:1189 (F,F) vectors. It rewrites the
* INVALID_DEVID lookup that wc_CryptoCb_Curve25519MakePub/Generic issue into
* WB_DEVID, which is registered with wb_cb, so wc_CryptoCb_FindDevice()
* returns a device whose ->cb is non-NULL. Installed and removed around
* those two calls only. */
static int wb_find_cb(int devId, int algoType)
{
(void)devId;
(void)algoType;
return WB_DEVID;
}
#endif /* WOLF_CRYPTO_CB_FIND */
/* WB_DRIVE3(lvalue, call): sets `lvalue` (a struct field or plain local
* devId variable) to each of the three vectors and issues `call` once per
* vector. One invocation completes the `dev && dev->cb` MC/DC independence
@ -985,6 +1048,83 @@ int main(void)
WB_NOTE("HAVE_CURVE25519 not defined; Curve25519MakePub/Generic skipped");
#endif
/* ---- Curve25519MakePub/Generic: `dev == NULL || dev->cb == NULL`
* (cryptocb.c :1159 and :1189) ----
* Three vectors, all in this binary; see the "third pass" note in the
* file header for why each table state produces the operand values it
* does. Each vector rebuilds the table from wc_CryptoCb_Init() so the
* preceding section's leftovers cannot influence it. Nothing here can
* touch the key buffers: whichever device the guard ends up selecting,
* its callback is wb_cb, which returns CRYPTOCB_UNAVAILABLE without
* reading the wc_CryptoInfo it is handed. */
#ifdef HAVE_CURVE25519
{
byte g25pub[CURVE25519_KEYSIZE];
byte g25priv[CURVE25519_KEYSIZE];
byte g25base[CURVE25519_KEYSIZE];
int slot;
XMEMSET(g25pub, 0, sizeof(g25pub));
XMEMSET(g25priv, 1, sizeof(g25priv));
XMEMSET(g25base, 9, sizeof(g25base));
/* (F,T): one device registered, so free slots remain. The
* INVALID_DEVID lookup lands on the first free slot: non-NULL, cb
* NULL. Guard TRUE, so FindDeviceByIndex(0) supplies the device. */
wc_CryptoCb_Init();
if (wc_CryptoCb_RegisterDevice(WB_DEVID, wb_cb, NULL) != 0)
wb_fail = 1;
(void)wc_CryptoCb_Curve25519MakePub(sizeof(g25pub), g25pub,
sizeof(g25priv), g25priv);
(void)wc_CryptoCb_Curve25519Generic(sizeof(g25pub), g25pub,
sizeof(g25priv), g25priv, sizeof(g25base), g25base);
/* (T,-): every slot registered, so no slot holds INVALID_DEVID and
* wc_CryptoCb_GetDevice(INVALID_DEVID) returns NULL. dev == NULL is
* TRUE and short-circuits the OR - the independence pair for the
* first operand against the (F,T) vector above. */
wc_CryptoCb_Init();
for (slot = 0; slot < MAX_CRYPTO_DEVID_CALLBACKS; slot++) {
if (wc_CryptoCb_RegisterDevice(WB_DEVID_FILL + slot, wb_cb,
NULL) != 0)
wb_fail = 1;
}
(void)wc_CryptoCb_Curve25519MakePub(sizeof(g25pub), g25pub,
sizeof(g25priv), g25priv);
(void)wc_CryptoCb_Curve25519Generic(sizeof(g25pub), g25pub,
sizeof(g25priv), g25priv, sizeof(g25base), g25base);
/* (F,F): the find callback rewrites the INVALID_DEVID lookup into
* WB_DEVID, which is registered with wb_cb, so the lookup returns a
* device WITH a callback - the independence pair for the second
* operand against the (F,T) vector above. */
#ifdef WOLF_CRYPTO_CB_FIND
wc_CryptoCb_Init();
if (wc_CryptoCb_RegisterDevice(WB_DEVID, wb_cb, NULL) != 0)
wb_fail = 1;
wc_CryptoCb_SetDeviceFindCb(wb_find_cb);
(void)wc_CryptoCb_Curve25519MakePub(sizeof(g25pub), g25pub,
sizeof(g25priv), g25priv);
(void)wc_CryptoCb_Curve25519Generic(sizeof(g25pub), g25pub,
sizeof(g25priv), g25priv, sizeof(g25base), g25base);
wc_CryptoCb_SetDeviceFindCb(NULL);
WB_NOTE("Curve25519MakePub/Generic: dev==NULL||dev->cb==NULL "
"[:1159,:1189] driven (F,T) / (T,-) / (F,F)");
#else
WB_NOTE("Curve25519MakePub/Generic: dev==NULL||dev->cb==NULL "
"[:1159,:1189] driven (F,T) / (T,-); (F,F) needs "
"WOLF_CRYPTO_CB_FIND, not compiled here");
#endif
/* Leave the table the way the rest of this file expects it. */
wc_CryptoCb_Init();
if (wc_CryptoCb_RegisterDevice(WB_DEVID, wb_cb, NULL) != 0)
wb_fail = 1;
}
#else
WB_NOTE("HAVE_CURVE25519 not defined; :1159/:1189 vectors skipped");
#endif
wc_CryptoCb_UnRegisterDevice(WB_DEVID);
wc_CryptoCb_UnRegisterDevice(WB_DEVID_NOCB);
wc_CryptoCb_UnRegisterDevice(WB_DEVID_HASH_OK);
@ -992,6 +1132,11 @@ int main(void)
(void)res;
(void)intSize;
(void)devId;
#ifdef WOLF_CRYPTO_CB_FIND
/* Keep wb_find_cb referenced even in a variant without HAVE_CURVE25519,
* where the only call site above is preprocessed away. */
(void)wb_find_cb;
#endif
printf("done (%s)\n", wb_fail ? "with skips" : "ok");
#else

View File

@ -83,8 +83,9 @@
* :844 wolfSSL_StaticBufferSz_ex() same alignment pattern as :621.
* :851 wolfSSL_StaticBufferSz_ex() same IO_POOL/IO_POOL_FIXED OR as :635.
* :867 wolfSSL_StaticBufferSz_ex() `(ava >= sizeList[0]+padSz+memSz) &&
* (ava > 0)` -- see RESIDUAL note below; only the first operand's
* independence is satisfiable.
* (ava > 0)` -- both operands' independence pairs driven; see the
* comment above the third vector in Section 4a for why the second
* operand needs a sizeList[0] that wraps the word32 sum to zero.
* :1013 wolfSSL_Malloc() `heap==NULL && globalHeapHint==NULL`.
* :1068 wolfSSL_Malloc() `mem->flag & WOLFMEM_IO_POOL_FIXED &&
* (type==DYNAMIC_TYPE_OUT_BUFFER || type==DYNAMIC_TYPE_IN_BUFFER)`.
@ -101,13 +102,6 @@
*
* RESIDUALS (structurally dead operand, provably unsatisfiable -- not a gap
* in this test, a property of the source):
* - :867 `ava > 0`: every bucket size in sizeList[] is a positive value
* (callers only ever pass positive bucket sizes), so
* `ava >= sizeList[0]+padSz+memSz` being true always implies `ava > 0`
* (padSz+memSz >= 0, sizeList[0] > 0). The pair that would show `ava>0`
* independently (first operand true, second false) requires
* `sizeList[0]+padSz+memSz <= 0`, which cannot happen. Only the first
* operand's independence pair is driven here.
* - :1414 `res == NULL`: this check is reached solely via the `else`
* branch of the IO-pool `if` immediately above it (memory.c ~:1387-1400
* in this same function). Every route into that `else` branch leaves
@ -353,11 +347,10 @@ int main(void)
WOLFMEM_IO_POOL_FIXED);
WB_CHECK(sz >= 0, "IO-flag OR: second true (WOLFMEM_IO_POOL_FIXED)");
/* :867 `ava >= sizeList[0]+padSz+memSz && ava > 0` -- only the
* first operand's independence is satisfiable (see RESIDUAL note
* in the header comment): "true" vector (loop iterates, ample
* buffer) and "false" vector (buffer smaller than one bucket). */
WB_NOTE("wolfSSL_StaticBufferSz_ex(): ava-loop [:867] (residual: ava>0 dead, see header)");
/* :867 `ava >= sizeList[0]+padSz+memSz && ava > 0`.
* First operand: "true" vector (loop iterates, ample buffer) and
* "false" vector (buffer smaller than one bucket). */
WB_NOTE("wolfSSL_StaticBufferSz_ex(): ava-loop [:867]");
sz = wolfSSL_StaticBufferSz_ex(3, s_sizeList, s_distList, aligned,
(word32)(sizeof(s_scratch) - (aligned - s_scratch)),
WOLFMEM_GENERAL);
@ -366,6 +359,43 @@ int main(void)
sz = wolfSSL_StaticBufferSz_ex(3, s_sizeList, s_distList, aligned,
4 /* smaller than sizeList[0]=64 + overhead */, WOLFMEM_GENERAL);
WB_CHECK(sz == 0, "ava-loop first-operand false vector (buffer too small)");
/* Second operand (`ava > 0`) FALSE half, i.e. first operand TRUE and
* second FALSE. That needs `sizeList[0] + padSz + memSz` to be 0:
* `ava` is word32 and the sum is computed in word32 (sizeList[0],
* padSz and memSz are all word32 == unsigned int here, so the usual
* arithmetic conversions keep the whole sum unsigned 32-bit), and for
* any strictly positive sum `ava >= sum` already implies `ava > 0`.
* The sum is only zero when it wraps, which is well defined for
* unsigned arithmetic and is reachable because sizeList[] is a caller
* -supplied argument of the public wolfSSL_StaticBufferSz_ex() API.
* With sum == 0 and sz == 0:
* - the alignment while-loop at :844 cannot run (pt == buffer + sz
* immediately), so ava stays 0 and no byte of the buffer is read
* or written;
* - the :863 "not enough room for even one bucket" pre-check is
* `0 < 0`, false, so control reaches :867;
* - :867 evaluates `0 >= 0` (TRUE) && `0 > 0` (FALSE) and the loop
* body never executes, so distList[]/sizeList[] are never indexed
* and the wrapped bucket size is never used for anything.
* The function returns sz - ava == 0. */
{
word32 wrapMemSz;
word32 wrapPadSz;
word32 wrapList[3];
wrapMemSz = (word32)sizeof(wc_Memory);
wrapPadSz = (word32)(-(int)wrapMemSz & (WOLFSSL_STATIC_ALIGN - 1));
wrapList[0] = (word32)(0U - (wrapPadSz + wrapMemSz));
wrapList[1] = 128;
wrapList[2] = 256;
sz = wolfSSL_StaticBufferSz_ex(3, wrapList, s_distList,
s_bufGeneral, 0, WOLFMEM_GENERAL);
WB_CHECK(sz == 0,
"ava-loop second-operand false vector (sizeList[0] chosen "
"so sizeList[0]+padSz+memSz wraps to 0, sz==0)");
}
}
/* ==================================================================

View File

@ -25,8 +25,70 @@
* tests/api "port" group cannot reach it. Its loop guard
* "n >= s2_len && s1[0]" needs both operands driven false independently,
* which needs a haystack shorter than the needle and an empty haystack.
*
* This file also interposes accept4() for wc_accept_cloexec(), see the
* block immediately below and the note above wb_cloexec_wrappers().
*/
/* ---- accept4() macro interposition -------------------------------------- *
*
* wc_accept_cloexec()'s guard `if (errno != ENOSYS && errno != EINVAL)`
* (wc_port.c ~:5684) can only see errno == ENOSYS on a kernel that does not
* implement accept4(). Every host this campaign runs on does, so the first
* operand has no reachable independence pair from the outside.
*
* The white-box TU #includes wc_port.c directly, so accept4() can be
* replaced at PREPROCESSING time for this translation unit only - the same
* technique mcdc_fault_hash.h uses for the hash primitives. Ordering is
* load-bearing and mirrors that header exactly:
* 1. define _GNU_SOURCE before the first libc header, exactly as
* wc_port.c does at its own top. Without it glibc does not declare
* accept4(), __USE_GNU is never set, and wc_port.c's
* `#if defined(__USE_GNU) && ...` block - the block that CONTAINS the
* target guard - would compile out of this TU entirely;
* 2. include <sys/socket.h> so the REAL accept4() declaration is in scope
* and is never rewritten by the macro;
* 3. define the wrapper, which is compiled BEFORE the macro exists and so
* still calls the real accept4() when disarmed;
* 4. only then #define accept4 to the wrapper, and include wc_port.c.
* wc_port.c's own `#include <sys/socket.h>` becomes a no-op (include guard)
* and its own `#define _GNU_SOURCE 1` is skipped by its `!defined(_GNU_SOURCE)`
* test, so the preprocessor state wc_port.c sees is identical to a normal
* build. accept4 appears exactly once in wc_port.c (in wc_accept_cloexec),
* so nothing else in the file is affected.
* ------------------------------------------------------------------------ */
#if (defined(__linux__) || defined(__ANDROID__)) && \
!defined(WOLFSSL_LINUXKM) && !defined(WOLFSSL_ZEPHYR) && \
!defined(_GNU_SOURCE)
#define _GNU_SOURCE 1
#endif
#if (defined(__unix__) || defined(__APPLE__)) && \
!defined(WOLFSSL_LINUXKM) && !defined(WOLFSSL_KERNEL_MODE) && \
!defined(WOLFSSL_ZEPHYR) && !defined(WOLFSSL_SGX)
#include <errno.h>
#include <sys/socket.h>
#endif
#if defined(__USE_GNU) && (defined(__linux__) || defined(__ANDROID__))
#define WB_HAVE_ACCEPT4_HOOK
/* 0 = pass through to the real accept4(); otherwise fail with this errno. */
static int wb_accept4_errno = 0;
static int wb_accept4(int sockfd, struct sockaddr* addr, socklen_t* addrlen,
int flags)
{
if (wb_accept4_errno != 0) {
errno = wb_accept4_errno;
return -1;
}
return accept4(sockfd, addr, addrlen, flags);
}
#define accept4 wb_accept4
#endif /* __USE_GNU && (__linux__ || __ANDROID__) */
#include <wolfcrypt/src/wc_port.c>
#include <stdio.h>
@ -102,6 +164,7 @@ static void wb_strnstr(void) { WB_NOTE("wolfSSL_strnstr not compiled; skipped");
* socket(AF_INET, SOCK_STREAM, 0) -> succeeds (F,-)
* accept on a NON-listening socket -> EINVAL (T,F) at 5684
* accept on a bad descriptor -> EBADF (T,T) at 5684
* accept4() interposed to fail with ENOSYS -> (F,-) at 5684
*
* Every failing call returns a negative fd that the wrapper only ever passes
* to wc_set_cloexec(), which returns immediately for fd < 0; the two
@ -109,9 +172,15 @@ static void wb_strnstr(void) { WB_NOTE("wolfSSL_strnstr not compiled; skipped");
* no socket is ever connected or bound, so nothing outside this process is
* touched.
*
* 5684's idx0 ("errno != ENOSYS") stays a justified residual: making accept4()
* report ENOSYS needs a kernel without the syscall, which no build variant of
* this campaign runs on, so that operand has no reachable independence pair.
* 5684's idx0 ("errno != ENOSYS") is the one operand no argument choice can
* reach, because it needs a kernel that does not implement accept4(). It is
* driven instead by the macro interposition set up at the top of this file:
* the wrapper reports ENOSYS for exactly one call, which short-circuits the
* AND and drops wc_accept_cloexec() into its plain accept() fallback. That
* fallback is issued on fd -1, so accept() returns -1/EBADF and
* wc_set_cloexec(-1) returns immediately - no descriptor is produced and
* nothing blocks. The hook is disarmed again on the next line, so every other
* call in this file reaches the real accept4().
* ------------------------------------------------------------------------ */
#if (defined(__unix__) || defined(__APPLE__)) && \
!defined(WOLFSSL_KERNEL_MODE) && !defined(WOLFSSL_ZEPHYR) && \
@ -194,6 +263,25 @@ static void wb_cloexec_wrappers(void)
WB_NOTE("accept on fd -1 unexpectedly succeeded");
}
/* --- 5684 idx0 FALSE: accept4() reports ENOSYS, so `errno != ENOSYS` is
* false and the AND short-circuits into the accept() fallback below it.
* Paired in this same binary with the EBADF vector immediately above,
* which has idx0 TRUE and the same outcome flip. --- */
#ifdef WB_HAVE_ACCEPT4_HOOK
errno = 0;
wb_accept4_errno = ENOSYS;
fd = wc_accept_cloexec(-1, NULL, NULL);
wb_accept4_errno = 0;
if (fd >= 0) {
close(fd);
WB_NOTE("accept() fallback on fd -1 unexpectedly succeeded");
}
WB_NOTE("accept4 ENOSYS interposition drove 5684 idx0 false");
#else
WB_NOTE("accept4() not compiled in this TU; 5684 idx0 false vector "
"skipped");
#endif
WB_NOTE("cloexec open/socket/accept fallback guard pairs done");
}
#else