Skip to content

Commit c4f7e3b

Browse files
committed
Add run-time check of app vs. library build configuration
An application compiled with different feature macros from the wolfSSL library it loads compiles cleanly and then corrupts memory, because struct layouts and available APIs differ. Comparing version numbers does not catch a different configuration of the same version. Add wolfssl/wolfcrypt/build_check.h. An application calls wc_CheckBuildConfig(), or wc_CheckBuildConfigEx() for a readable list of differences, before wolfSSL_Init(). It compares the application's compile-time view with the library's: - the library version; - 243 ABI-affecting macros as feature bits; - sizeof() of 66 public structs, including the OpenSSL compatibility layer's EVP/SHA/HMAC contexts, and of void*, long and size_t; - the math digit width (sizeof(mp_digit)) and radix (DIGIT_BIT); - a hash of options.h, when both sides have one. The list of macros and structs is in build_check_list.h. It is expanded as an X-macro list into the header-side fill function, the library's name table and the unit test. Slots and bits are append-only, and the struct is versioned, so a newer library accepts an older application's struct. New exported APIs: wc_GetBuildConfig(), wc_CompareBuildConfig(), wc_BuildConfigMismatchString(), wc_BuildConfigFeatureName() and wc_BuildConfigSizeName(). They are also exported from the Linux kernel module. A mismatch returns the new error BUILD_CONFIG_MISMATCH_E (-1043) and a bitmask of what differed. Everything is gated on HAVE_WC_INTROSPECTION. Build system: - configure writes WOLFSSL_OPTIONS_HASH (a CRC of options.h) into options.h and .build_params. - CMake writes a SHA-256 prefix and gains the WOLFSSL_INTROSPECTION option, on by default, which defines HAVE_WC_INTROSPECTION. - Neither writes a hash for user_settings.h builds. asn.h defines WC_DECODEDCERT_DEFINED. When openssl/obj_mac.h is included first, the DecodedCert slots record WC_BUILD_SIZE_UNKNOWN, and the comparison skips them. Fix an undefined reference to sp_zero() when linking an opensslextra build with neither RSA nor ECC; the BIGNUM compat functions call it. CI: the new build-config-check.yml workflow - checks that build_check_list.h covers every public struct whose layout depends on the configuration (.github/scripts/check-build-config-list.py); - builds two differently configured libraries and checks that an application detects and names the differences (.github/scripts/build-config-mismatch.sh).
1 parent 44fa143 commit c4f7e3b

23 files changed

Lines changed: 4234 additions & 5 deletions
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/bin/bash
2+
# Checks that an application including every public wolfSSL header computes
3+
# the same build configuration fingerprint as the library itself.
4+
#
5+
# The library's fingerprint is computed in build_check.h, which includes
6+
# only some headers. If a header that build_check.h does not (always)
7+
# include derives a fingerprinted macro, an application that includes it
8+
# sees a different fingerprint and wc_CheckBuildConfig() reports a false
9+
# mismatch. This builds and installs the library with the given configure
10+
# arguments, then builds an application that includes options.h, every
11+
# installed header and finally build_check.h, and runs it against the
12+
# installed library. If the configuration builds tests/unit.test, its
13+
# build configuration tests are run as well (examples, and so unit.test,
14+
# are not built in wolfCrypt-only configurations).
15+
#
16+
# Usage: build-config-match.sh WORK_DIR [CONFIGURE_ARGS...]
17+
# Run from the top of a checkout that has been through ./autogen.sh.
18+
19+
set -euo pipefail
20+
21+
work=${1:?usage: $0 WORK_DIR [CONFIGURE_ARGS...]}
22+
shift
23+
mkdir -p "$work"
24+
work=$(cd "$work" && pwd)
25+
prefix=$work/install
26+
jobs=$(nproc 2>/dev/null || echo 4)
27+
28+
./configure --prefix="$prefix" --enable-shared --disable-static "$@"
29+
make -j"$jobs"
30+
make install
31+
32+
if [ -x tests/unit.test ]; then
33+
./tests/unit.test --api -test_wc_BuildConfig -test_wc_BuildConfigNames
34+
fi
35+
36+
inc=$prefix/include
37+
app=$work/app.c
38+
{
39+
echo '#include <wolfssl/options.h>'
40+
echo '#include <wolfssl/wolfcrypt/settings.h>'
41+
# Skipped: port headers, which only compile on their platforms, and the
42+
# math backends, which conflict with each other (wolfmath.h includes
43+
# the configured one).
44+
(cd "$inc" && find wolfssl -name '*.h' | LC_ALL=C sort) | \
45+
grep -v -E '^wolfssl/(options|wolfcrypt/build_check(_list)?)\.h$' | \
46+
grep -v -E '^wolfssl/wolfcrypt/port/' | \
47+
grep -v -E '^wolfssl/wolfcrypt/(integer|tfm|sp_int)\.h$' | \
48+
while read -r h; do
49+
echo "#include <$h>"
50+
done
51+
echo '#include <wolfssl/wolfcrypt/build_check.h>'
52+
cat <<'EOF'
53+
#include <stdio.h>
54+
#ifndef HAVE_WC_INTROSPECTION
55+
#error "HAVE_WC_INTROSPECTION is not defined in options.h"
56+
#endif
57+
int main(void)
58+
{
59+
char desc[2048];
60+
word32 mismatch = 0;
61+
int ret = wc_CheckBuildConfigEx(&mismatch, desc, (word32)sizeof(desc));
62+
if (ret != 0) {
63+
printf("build config mismatch (%d, 0x%x): %s\n", ret,
64+
(unsigned)mismatch, desc);
65+
return 1;
66+
}
67+
printf("build config matches\n");
68+
return 0;
69+
}
70+
EOF
71+
} > "$app"
72+
73+
${CC:-cc} -I"$inc" -o "$work/app" "$app" -L"$prefix/lib" -lwolfssl
74+
LD_LIBRARY_PATH=$prefix/lib "$work/app"
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/bin/bash
2+
# End-to-end test of the build configuration check (wc_CheckBuildConfig):
3+
# an application built against one wolfSSL must detect, at run time, that
4+
# the shared library it actually loaded was configured differently.
5+
#
6+
# Builds and installs two libraries from this tree into separate prefixes:
7+
# A: default configuration
8+
# B: a different configuration (OpenSSL compat on, SHA-3 off, AES-XTS on),
9+
# chosen so that feature bits, struct sizes and the options.h hash all
10+
# differ from A
11+
# then builds a small application against each library's headers and runs
12+
# it with each library loaded:
13+
# app(A) + lib A -> 0, empty description
14+
# app(B) + lib B -> 0, empty description
15+
# app(A) + lib B -> BUILD_CONFIG_MISMATCH_E, the expected items named
16+
# app(B) + lib A -> BUILD_CONFIG_MISMATCH_E, the same items reversed
17+
# app(A) built with an options.h whose hash define was removed, all else
18+
# equal + lib A -> 0 (the hash is only compared when both sides have one)
19+
# app(A) built with an options.h edited after configure (here: only its
20+
# hash changed) + lib A -> BUILD_CONFIG_MISMATCH_E, options hash only
21+
#
22+
# Usage: build-config-mismatch.sh [WORK_DIR]
23+
# Run from the top of a git checkout that has been through ./autogen.sh.
24+
# WORK_DIR (default: a fresh mktemp directory) receives the installs and
25+
# the test application. The source tree is reconfigured; its previous
26+
# configuration is not restored.
27+
28+
set -euo pipefail
29+
30+
work=${1:-$(mktemp -d)}
31+
mkdir -p "$work"
32+
work=$(cd "$work" && pwd)
33+
jobs=$(nproc 2>/dev/null || echo 4)
34+
35+
conf_common=(--disable-examples --disable-crypttests --enable-shared
36+
--disable-static)
37+
conf_a=()
38+
conf_b=(--enable-opensslextra --disable-sha3 --enable-aesxts)
39+
40+
failures=0
41+
fail() {
42+
echo "::error ::build-config mismatch test: $*"
43+
failures=$((failures + 1))
44+
}
45+
46+
build() { # name, configure args...
47+
local name=$1
48+
shift
49+
echo "=== Building library $name: $*"
50+
if [ -f Makefile ]; then
51+
make distclean >/dev/null 2>&1 || true
52+
fi
53+
./configure --prefix="$work/$name" "${conf_common[@]}" "$@" \
54+
> "$work/configure-$name.log" 2>&1 ||
55+
{ tail -30 "$work/configure-$name.log"; exit 1; }
56+
make -j"$jobs" > "$work/make-$name.log" 2>&1 ||
57+
{ tail -30 "$work/make-$name.log"; exit 1; }
58+
make install > "$work/install-$name.log" 2>&1 ||
59+
{ tail -30 "$work/install-$name.log"; exit 1; }
60+
grep -q '#define HAVE_WC_INTROSPECTION' \
61+
"$work/$name/include/wolfssl/options.h" ||
62+
{ echo "HAVE_WC_INTROSPECTION missing from library $name"; exit 1; }
63+
}
64+
65+
cat > "$work/app.c" <<'EOF'
66+
#include <wolfssl/options.h>
67+
#include <wolfssl/wolfcrypt/build_check.h>
68+
#include <stdio.h>
69+
70+
int main(void)
71+
{
72+
char why[4096];
73+
word32 mismatch = 0;
74+
int ret = wc_CheckBuildConfigEx(&mismatch, why, sizeof(why));
75+
76+
printf("ret=%d mismatch=0x%x\n", ret, (unsigned)mismatch);
77+
printf("%s\n", why);
78+
return (ret == 0) ? 0 : 1;
79+
}
80+
EOF
81+
82+
app() { # name, headers prefix, extra cflags...
83+
local name=$1 prefix=$2
84+
shift 2
85+
${CC:-cc} -Wall -Werror "$@" -I"$prefix/include" "$work/app.c" \
86+
-L"$prefix/lib" -lwolfssl -o "$work/$name"
87+
}
88+
89+
run() { # app, library prefix -> sets out, rc
90+
set +e
91+
out=$(LD_LIBRARY_PATH="$2/lib" "$work/$1" 2>&1)
92+
rc=$?
93+
set -e
94+
echo "--- $1 with $(basename "$2"): exit $rc"
95+
echo "$out" | sed 's/; /\n /g'
96+
}
97+
98+
expect_match() { # app, library prefix
99+
run "$1" "$2"
100+
[ "$rc" -eq 0 ] || fail "$1 with $(basename "$2"): expected a match"
101+
echo "$out" | head -1 | grep -q '^ret=0 mismatch=0x0$' ||
102+
fail "$1 with $(basename "$2"): expected ret=0 mismatch=0x0"
103+
}
104+
105+
expect_mismatch() { # app, library prefix, expected substrings...
106+
local a=$1 p=$2
107+
shift 2
108+
run "$a" "$p"
109+
[ "$rc" -eq 1 ] || fail "$a with $(basename "$p"): expected a mismatch"
110+
echo "$out" | head -1 | grep -q '^ret=-1043 mismatch=0x[0-9a-f]*e$' ||
111+
fail "$a with $(basename "$p"): expected BUILD_CONFIG_MISMATCH_E" \
112+
"with features, sizes and options hash all differing"
113+
local want
114+
for want in "$@"; do
115+
echo "$out" | grep -qF -- "$want" ||
116+
fail "$a with $(basename "$p"): description lacks \"$want\""
117+
done
118+
}
119+
120+
build a "${conf_a[@]}"
121+
app app_a "$work/a"
122+
build b "${conf_b[@]}"
123+
app app_b "$work/b"
124+
# Modified copies of A's options.h, found ahead of the installed one.
125+
mkdir -p "$work/nohash/wolfssl" "$work/edited/wolfssl"
126+
grep -v 'WOLFSSL_OPTIONS_HASH' "$work/a/include/wolfssl/options.h" \
127+
> "$work/nohash/wolfssl/options.h"
128+
sed 's/^#define WOLFSSL_OPTIONS_HASH .*/#define WOLFSSL_OPTIONS_HASH 0x12345678U/' \
129+
"$work/a/include/wolfssl/options.h" > "$work/edited/wolfssl/options.h"
130+
cmp -s "$work/a/include/wolfssl/options.h" "$work/edited/wolfssl/options.h" &&
131+
{ echo "options.h of library a has no WOLFSSL_OPTIONS_HASH"; exit 1; }
132+
app app_a_nohash "$work/a" -I"$work/nohash"
133+
app app_a_edited "$work/a" -I"$work/edited"
134+
135+
expect_match app_a "$work/a"
136+
expect_match app_b "$work/b"
137+
expect_match app_a_nohash "$work/a"
138+
run app_a_edited "$work/a"
139+
echo "$out" | head -1 | grep -q '^ret=-1043 mismatch=0x8$' ||
140+
fail "app_a_edited with a: expected only an options hash mismatch (0x8)"
141+
echo "$out" | grep -qF 'options.h hash: app=0x12345678 lib=0x' ||
142+
fail "app_a_edited with a: description lacks the options hash"
143+
expect_mismatch app_a "$work/b" \
144+
'OPENSSL_EXTRA: app=off lib=on' \
145+
'WOLFSSL_SHA3: app=on lib=off' \
146+
'WOLFSSL_AES_XTS: app=off lib=on' \
147+
'sizeof(struct WOLFSSL_EVP_MD_CTX): app=0 lib=' \
148+
'options.h hash: app=0x'
149+
expect_mismatch app_b "$work/a" \
150+
'OPENSSL_EXTRA: app=on lib=off' \
151+
'WOLFSSL_SHA3: app=off lib=on' \
152+
'WOLFSSL_AES_XTS: app=on lib=off' \
153+
'sizeof(struct WOLFSSL_EVP_MD_CTX): app=' \
154+
'options.h hash: app=0x'
155+
156+
if [ "$failures" -ne 0 ]; then
157+
echo "build-config mismatch test: $failures failure(s)"
158+
exit 1
159+
fi
160+
echo "build-config mismatch test: OK"

0 commit comments

Comments
 (0)