Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ SCEN_IOTFLEET = examples/scenarios/iot_fleet_config
SCEN_SENSOR = examples/scenarios/sensor_attestation
SCEN_BROADCAST = examples/scenarios/group_broadcast_mac

.PHONY: all shared test pkg-config-test ecdsa-policy-test rsapss-policy-test countersign-config-test zero-alloc-check zeroize-test deprecated-algs-test ecc-import-policy-test ext-sign-test ext-sign-demo ext-sign-force-failure coverage eat-psa-test eat-psa-float-test eat-psa-min-buffers-test eat-psa-claim-limits-test eat-psa-profile-test eat-psa-config-check eat-psa-ext-sign-test eat-psa-ext-sign-force-failure eat-psa-coverage eat-psa-coverage-force-failure hpke-coverage-force-failure generic-reduced-alg-test tool tool-test cmdline-test demo demos hpke-demo lean-verify psa-eat-lean-verify psa-eat-demo mldsa-demo mldsa-verify lms-demo lms-verify comprehensive scenarios release-scenarios release-coverage cxx-check valgrind-check release-validate release-artifacts interop-tcose tcose-upstream interop-go-cose interop-python-cwt interop-rust-coset c99-check c99-check-lms c99-hpke-check experimental-check clean FORCE
.PHONY: all shared test pkg-config-test ecdsa-policy-test rsapss-policy-test countersign-config-test zero-alloc-check force-zero-check zeroize-test deprecated-algs-test ecc-import-policy-test ext-sign-test ext-sign-demo ext-sign-force-failure coverage eat-psa-test eat-psa-float-test eat-psa-min-buffers-test eat-psa-claim-limits-test eat-psa-profile-test eat-psa-config-check eat-psa-ext-sign-test eat-psa-ext-sign-force-failure eat-psa-coverage eat-psa-coverage-force-failure hpke-coverage-force-failure generic-reduced-alg-test tool tool-test cmdline-test demo demos hpke-demo lean-verify psa-eat-lean-verify psa-eat-demo mldsa-demo mldsa-verify lms-demo lms-verify comprehensive scenarios release-scenarios release-coverage cxx-check valgrind-check release-validate release-artifacts interop-tcose tcose-upstream interop-go-cose interop-python-cwt interop-rust-coset c99-check c99-check-lms c99-hpke-check experimental-check clean FORCE

# --- Core library ---
all: $(LIB_A)
Expand Down Expand Up @@ -356,7 +356,11 @@ countersign-config-test:
zero-alloc-check:
sh scripts/check_zero_alloc.sh

test: pkg-config-test ecdsa-policy-test rsapss-policy-test zero-alloc-check $(LIB_A)
force-zero-check:
CC='$(CC)' CFLAGS='$(CFLAGS)' sh scripts/check_force_zero.sh

test: pkg-config-test ecdsa-policy-test rsapss-policy-test zero-alloc-check \
force-zero-check $(LIB_A)
$(CC) $(CFLAGS) -o $(TEST_BIN) $(TEST_SRC) $(LIB_A) $(LDFLAGS) $(LDLIBS)
./$(TEST_BIN)

Expand Down
1 change: 0 additions & 1 deletion docs/MISRA-Compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ tracked conditional preprocessing directive.

| ID | Rule | Location | Rationale and validation |
|----|------|----------|--------------------------|
| D-11.5-001 | 11.5 | `wolfCose_ForceZero` | Converting the caller's object pointer to a volatile character pointer is the standard C mechanism for securely erasing its object representation. Character access is alignment-safe and the function allocates no memory. `make zeroize-test`, `make zero-alloc-check`, and `make c99-check` validate the implementation. |
| D-19.2-001 | 19.2 | `WOLFCOSE_KEY.key` | The public, discriminated union preserves the established ABI and embedded-memory footprint. `kty` and `attachedType` govern member access. Replacing it with a structure would break ABI and increase RAM. The full tests exercise the supported members, and CI anchors the deviation to the exact union boundaries. |
| D-11.8-002 | 11.8 | `wolfCose_Hpke0Seal` plaintext input | The pinned wolfSSL `wc_HpkeSealBase()` interface accepts a mutable plaintext pointer even though its implementation only consumes plaintext while writing to its distinct ciphertext output. The wrapper keeps the wolfCOSE public input const-qualified and avoids a payload-sized copy in constrained builds. The deviation is anchored to the one interoperability cast and covered by the HPKE unit and sanitizer CI lanes. |
| D-11.8-003 | 11.8 | `wolfCose_Hpke0Open` ciphertext input | The pinned wolfSSL `wc_HpkeOpenBase()` interface accepts a mutable ciphertext pointer even though its implementation only consumes ciphertext while writing to its distinct plaintext output. The wrapper keeps the wolfCOSE public input const-qualified and avoids a ciphertext-sized copy in constrained builds. The deviation is anchored to the one interoperability cast and covered by the HPKE unit and sanitizer CI lanes. |
Expand Down
101 changes: 101 additions & 0 deletions scripts/check_force_zero.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/bin/sh
#
# Prove wolfCose_ForceZero survives dead-store elimination. A caller in the
# same translation unit as wolfcose_util.c wipes a local buffer that is dead
# afterwards; at -O3 the zeroing stores must still be in its assembly. A
# plain XMEMSET control in the same file must lose them, which shows the
# optimizer could drop the wipe and the store patterns below are not noise.
# Runs with and without WOLFSSL_NO_ASM, which selects the barrier body.

set -eu

CC=${CC:-cc}
CFLAGS=${CFLAGS:-}

# Keep only include paths and defines from CFLAGS: the optimization level is
# fixed here, and sanitizer or coverage flags would rewrite the stores.
flags=""
take_next=0
for flag in $CFLAGS; do
if [ "$take_next" -eq 1 ]; then
flags="$flags $flag"
take_next=0
continue
fi
case "$flag" in
-isystem|-I|-D) flags="$flags $flag"; take_next=1 ;;
-I*|-D*|-isystem*) flags="$flags $flag" ;;
esac
done

work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT

cat >"$work/probe.c" <<'EOF'
#include "wolfcose_util.c"

void wcProbeFill(unsigned char* buf);
void wcProbeWipe(void);
void wcProbeControl(void);

void wcProbeWipe(void)
{
unsigned char buf[256];
wcProbeFill(buf);
wolfCose_ForceZero(buf, sizeof(buf));
}

void wcProbeControl(void)
{
unsigned char buf[256];
wcProbeFill(buf);
(void)XMEMSET(buf, 0, sizeof(buf));
}
EOF

# Zero sources for x86-64 and AArch64 with GCC and Clang: a memset or bzero
# call, rep stos, an x86 vector zero idiom or $0 immediate store, an AArch64
# zero-register store, or an AArch64 vector zero.
word='(^|[^[:alnum:]_])'
zero_store="memset|bzero|stos|${word}v?(pxor|xorps)[[:space:]]"
zero_store="$zero_store|${word}mov[a-z]*[[:space:]]+[\$]0,"
zero_store="$zero_store|${word}st[a-z]*[[:space:]]+[xw]zr,"
zero_store="$zero_store|movi[^,]*,[[:space:]]*#?0+([[:space:]]|$)"
inline_call='(call|jmp|bl|b)[[:space:]]+_?wolfCose_ForceZero([@[:space:]]|$)'

body() {
awk -v fn="$1" '
$0 ~ "^_?" fn ":" { on = 1; next }
on && /^[[:space:]]*\.(cfi_endproc|size)/ { exit }
on { print }
' "$2"
}

for asm_mode in "" "-DWOLFSSL_NO_ASM"; do
label=${asm_mode:-default}
# shellcheck disable=SC2086
$CC -std=c99 -O3 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -I./include -Isrc \
$flags $asm_mode -S -o "$work/probe.s" "$work/probe.c"

body wcProbeWipe "$work/probe.s" >"$work/wipe.s"
body wcProbeControl "$work/probe.s" >"$work/control.s"

if [ ! -s "$work/wipe.s" ] || [ ! -s "$work/control.s" ]; then
echo "FAIL ($label): probe functions not found in assembly"
exit 1
fi
if grep -Eq "$inline_call" "$work/wipe.s"; then
echo "FAIL ($label): wolfCose_ForceZero not inlined, check is moot"
exit 1
fi
if grep -Eq "$zero_store" "$work/control.s"; then
echo "FAIL ($label): plain memset kept, check cannot tell"
exit 1
fi
if ! grep -Eq "$zero_store" "$work/wipe.s"; then
echo "FAIL ($label): wolfCose_ForceZero stores optimized out"
cat "$work/wipe.s"
exit 1
fi
echo "PASS ($label): wolfCose_ForceZero stores kept at -O3"
done
10 changes: 0 additions & 10 deletions scripts/misra-deviations.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
{
"schema": 1,
"deviations": [
{
"id": "D-11.5-001",
"rule": "11.5",
"path": "src/wolfcose_util.c",
"symbol": "wolfCose_ForceZero",
"anchors": [
"volatile unsigned char* p = (volatile unsigned char*)mem;"
],
"expected": 1
},
{
"id": "D-11.4-001",
"rule": "11.4",
Expand Down
10 changes: 6 additions & 4 deletions src/wolfcose_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,12 @@ extern "C" {
#endif

/* ----- Secure memory zero -----
* Portable secure-zero using a volatile pointer so the compiler cannot
* optimise the writes away. Used in place of wc_ForceZero so wolfCOSE
* links against the full 5.x range (wc_ForceZero only became a public
* WOLFSSL_API symbol in v5.8.4). Definition lives in wolfcose_util.c. */
* Portable secure-zero: XMEMSET, then a call through a volatile function
* pointer to an asm memory barrier (a no-op under WOLFSSL_NO_ASM), so the
* compiler cannot drop the stores as dead. Used in place of wc_ForceZero
* so wolfCOSE links against the full 5.x range (wc_ForceZero only became a
* public WOLFSSL_API symbol in v5.8.4). Definition lives in
* wolfcose_util.c; scripts/check_force_zero.sh checks it survives -O3. */
WOLFCOSE_LOCAL void wolfCose_ForceZero(void* mem, size_t len);

/* ----- Big-endian load/store helpers (bit-shift only, no platform dependencies) ----- */
Expand Down
27 changes: 20 additions & 7 deletions src/wolfcose_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@
/* ----- Secure memory zero ----- */

/**
* Portable secure-zero. Volatile pointer prevents the compiler optimising
* the writes away when the buffer is dead at function exit. Used in place
* Portable secure-zero. XMEMSET is followed by a call through a volatile
* function pointer the compiler cannot resolve, so it must assume the callee
* reads the buffer and cannot drop the stores as dead. With GNU asm the
* callee is also an asm memory barrier. Used in place
* of wc_ForceZero so wolfCOSE links against the full wolfSSL 5.x range
* (wc_ForceZero only became a public WOLFSSL_API symbol in v5.8.4).
*/
Expand All @@ -62,14 +64,25 @@
extern void wolfCose_TestZeroizeRecord(const void* mem, size_t len);
#endif

static void wolfCose_ZeroBarrier(const void* mem)
{
#if defined(__GNUC__) && !defined(__CC_ARM) && !defined(WOLFSSL_NO_ASM)
/* Still a barrier if the optimizer resolves and inlines this call, e.g.
* profile-guided call promotion. Same guard as wolfSSL's WC_BARRIER_DATA,
* which only exists in newer releases. */
__asm__ __volatile__("" : : "r"(mem) : "memory");
#else
(void)mem;
#endif
}

WOLFCOSE_LOCAL void wolfCose_ForceZero(void* mem, size_t len)
{
static void (*const volatile barrier)(const void*) = wolfCose_ZeroBarrier;

if ((mem != NULL) && (len > 0u)) {
volatile unsigned char* p = (volatile unsigned char*)mem;
size_t i;
for (i = 0u; i < len; i++) {
p[i] = 0u;
}
(void)XMEMSET(mem, 0, len);
barrier(mem);
}
#ifdef WOLFCOSE_TEST_ZEROIZE_HOOK
wolfCose_TestZeroizeRecord(mem, len);
Expand Down
22 changes: 22 additions & 0 deletions tests/test_cose.c
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,9 @@ static void test_wolfcose_force_zero(void)
int allZero;
int prefixZero;
int suffixUntouched;
size_t off;
size_t len;
int exactRange;

TEST_LOG(" [wolfCose_ForceZero]\n");

Expand Down Expand Up @@ -560,6 +563,25 @@ static void test_wolfcose_force_zero(void)
TEST_ASSERT(prefixZero == 1, "ForceZero prefix zeroed");
TEST_ASSERT(suffixUntouched == 1, "ForceZero suffix untouched");

/* Every start offset and length: exactly [off, off + len) is zeroed,
* nothing outside it */
exactRange = 1;
for (off = 0; off < (2u * sizeof(size_t)); off++) {
for (len = 0; len <= (3u * sizeof(size_t)) + 1u; len++) {
for (i = 0; i < sizeof(buf); i++) {
buf[i] = 0xDDu;
}
wolfCose_ForceZero(&buf[off], len);
for (i = 0; i < sizeof(buf); i++) {
if (((i >= off) && (i < (off + len))) ?
(buf[i] != 0u) : (buf[i] != 0xDDu)) {
exactRange = 0;
}
}
}
}
TEST_ASSERT(exactRange == 1, "ForceZero exact range at every offset");

/* len == 0: no-op, must not crash, must not modify buffer */
for (i = 0; i < sizeof(buf); i++) {
buf[i] = 0xCCu;
Expand Down
Loading