# BST-TIMEOUT: 120 # MEASURED: see the Self-Test Record in the BRINTOS-932 # # design doc. Nothing in this suite can block: every # # cryptographic call ends at socket(2) returning # # EAFNOSUPPORT immediately, and the CLI cases are three # # short-lived forks. 120 is ~10x the measured run and # # exists as headroom for a loaded nightly host, not # # because any case is slow. # # . "$BST_LIB" is the first executable line; everything above and below to that # point is the reader's brief. # libkcapi 1.5.0 -- Stephan Mueller's userspace interface to the KERNEL crypto # API (meta-oe recipes-crypto/libkcapi). What ships is /usr/lib/libkcapi.a, # /usr/include/kcapi.h, /usr/lib/pkgconfig/libkcapi.pc, five tools in /usr/bin # (kcapi-hasher, kcapi-dgst, kcapi-rng, kcapi-enc, kcapi-speed), sixteen # kcapi-hasher aliases under /usr/libexec/libkcapi/, and one probe under # /usr/libexec/libkcapi-tests. # # --------------------------------------------------------------------------- # THIS LIBRARY COMPUTES NOTHING ITSELF, AND THAT IS THE WHOLE DESIGN PROBLEM. # # Every other crypto library on this image (openssl, mbedtls, wolfssl, # libgcrypt, nettle, libsodium, libtomcrypt, monocypher, bearssl) carries its # own AES and SHA. libkcapi carries none. It is a veneer over AF_ALG: each entry # point opens socket(AF_ALG, SOCK_SEQPACKET, 0), binds a salg_type/salg_name, # accepts an operation fd and hands buffers to the KERNEL's implementation. # # arch/wasm32/configs/wasm32_defconfig carries NO CONFIG_CRYPTO line at all -- # and therefore no CONFIG_CRYPTO_USER_API_HASH, _SKCIPHER or _RNG. The AF_ALG # family is never registered, so socket(AF_ALG, ...) returns EAFNOSUPPORT and # EVERY cryptographic entry point in this library fails at its first syscall. # That gap is brintos/linux#89, filed from this port with the defconfig change # as a candidate fix; every red below is attributed to it. # # The naive suite writes itself and is worthless. Call the five convenience # functions, watch five EAFNOSUPPORTs, attribute them to the missing kernel # option, ship. A libkcapi.a MISCOMPILED INTO RUBBLE WOULD PRODUCE THE SAME FIVE # FAILURES, because not one of them would have reached a line of library code # that does anything. The suite would assert nothing about the port while # looking thorough -- the libsocketcan lesson next door, in a different subsystem. # # So this file scores four groups that the naive one conflates: # # PACKAGING -- the archive, headers, .pc and the three tools the ticket names # are present, and the tools EXECUTE. `kcapi-dgst -h` is not a # formality: its usage() calls kcapi_version() out of the linked # archive, so a tool that prints its help has demonstrably loaded and # run library code. It also pins the exit-127 lesson from BRINTOS-739 # -- a tool that was never built and a tool that fails are different # findings, and only the exit status tells them apart. # # A group -- THE ARCHIVE IS ALIVE. Pure userspace entry points that touch no # kernel: kcapi_version(), kcapi_versionstring(), kcapi_memset_secure(). # If these are right, the archive linked, relocated and executes on # wasm32. NOTHING HERE MAY BE RED, today or after the gap closes. # # N group -- THE NULL CONTROL, and the reason the K group below means anything # at all. The probe opens AF_INET and AF_UNIX in the SAME PROCESS, # moments before AF_ALG. They must succeed. Without them # "AF_ALG is unsupported" is equally consistent with a broken socket # layer, a broken libc or a sandbox refusing every socket -- all of # which are port defects wearing the platform gap's clothes. With # them, the failure is pinned to ONE address family. # # K group -- the kernel path. Expected red on this image, each attributed to # the AF_ALG gap. The discrimination that matters is the ERRNO: only # EAFNOSUPPORT means "the library composed a correct call and the # kernel has no such family". Any other errno is scored as a genuine # port defect with different wording, because it would be one. # # The known-answer vectors (FIPS 180-4 B.1 for SHA-256("abc"), NIST SP 800-38A # F.2.1/F.2.2 for AES-128-CBC) are carried but cannot execute here. They are # scored `skip`, not `ok` and not `not ok`: an unexecuted vector has no verdict, # and pretending otherwise is how a suite starts lying. They become live, with # NO EDIT to this file or the probe, on the first image whose kernel registers # AF_ALG -- which is the point of shipping them now. # # --------------------------------------------------------------------------- # WHY THE K GROUP IS `not ok` AND NOT `skip`. # # `skip` means the subject is legitimately ABSENT. libkcapi is present, it is # installed, and its tools run -- what is absent is a KERNEL OPTION, and it is # one defconfig line away (crypto/algif_hash.c is already in the tree, so this # is a config gap and not missing code). Scoring these `skip` would retire the # only signal anyone has that the image ships a crypto library that cannot # perform crypto. They are red, attributed, and they turn green by themselves # the day the kernel grows CONFIG_CRYPTO_USER_API_*. . "$BST_LIB" R=${BST_ROOT:-} PROBE=$R/usr/libexec/libkcapi-tests/libkcapi-probe ARCHIVE=$R/usr/lib/libkcapi.a PCFILE=$R/usr/lib/pkgconfig/libkcapi.pc INC=$R/usr/include BIN=$R/usr/bin ALIASDIR=$R/usr/libexec/libkcapi # The version the recipe pins. Asserted rather than read back, so a recipe # bumped without updating this suite is a red here instead of a silent change # of subject -- the known-answer vectors below are the oracle for THIS version. WANT_VERSION=1.5.0 WANT_VERNUM=1050000 # kcapi_versionstring() and the CLI banner return "libkcapi 1.5.0", NOT the bare # version -- measured on an x86-64 build of this same 1.5.0 tree, where the # whole API works, rather than assumed from the .pc (which really is bare). # Pinning the assumed spelling here would have shipped two false reds. WANT_VERSTR="libkcapi 1.5.0" # SHA-256("abc") -- FIPS 180-4 B.1. KAT_SHA256=ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad if [ ! -f "$ARCHIVE" ]; then bst_skip libkcapi/present "libkcapi not installed on this image" exit 0 fi bst_ok libkcapi/present # --- the package is whole ----------------------------------------------------- check libkcapi/staticlib test -s "$ARCHIVE" check libkcapi/pkgconfig test -s "$PCFILE" check libkcapi/hdr-main test -s "$INC/kcapi.h" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape libkcapi/no-shared "$R/usr/lib/libkcapi.so" "$R/usr/lib/libkcapi.so.1" pcver=$(sed -n 's/^Version: *//p' "$PCFILE" 2>/dev/null | head -1) check_eq libkcapi/pkgconfig-version "$WANT_VERSION" "$pcver" check_out libkcapi/pkgconfig-libs "-lkcapi" cat "$PCFILE" # The archive carries the API. grep rather than nm: the image ships no binutils # nm, and the names are present as plain bytes. -a where the grep supports it -- # on this arm the archive members are LLVM IR bitcode (ThinLTO), so a grep that # suppresses matches in a file containing NULs reports nothing; GNU grep needs # -a, busybox grep as shipped does not. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi # Ten of the exported API, chosen to cover each cipher class the library wraps # (md, sym, aead, rng, kdf) plus the handle lifecycle, rather than to be # exhaustive -- a fork budget note applies here as in the neighbouring suites. for sym in kcapi_md_sha256 kcapi_md_init kcapi_cipher_enc_aes_cbc \ kcapi_cipher_dec_aes_cbc kcapi_cipher_init kcapi_aead_init \ kcapi_rng_get_bytes kcapi_pbkdf kcapi_version kcapi_memset_secure; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libkcapi/sym-$sym" else bst_notok "libkcapi/sym-$sym" "$sym absent from $ARCHIVE" fi done # --- the three tools the ticket names ---------------------------------------- # Presence first, then execution. The two are scored separately because a tool # that is absent and a tool that is present-and-broken are different findings, # and because upstream's do_install rmdir's ${bindir} when PACKAGECONFIG builds # no apps at all -- a silent way to ship a library and no CLI. for t in kcapi-hasher kcapi-dgst kcapi-rng; do if [ -x "$BIN/$t" ]; then bst_ok "libkcapi/tool-$t" else bst_notok "libkcapi/tool-$t" "$BIN/$t absent or not executable" fi done # kcapi-hasher's sixteen aliases live in ${libexecdir}/libkcapi, NOT in # ${bindir} -- so they do not shadow coreutils' sha256sum, which is the whole # reason upstream puts them there. Asserted because a packaging change that # moved them into ${bindir} would silently replace the image's checksum tools # with ones that cannot compute a checksum on this kernel. if [ -e "$ALIASDIR/sha256sum" ]; then bst_ok libkcapi/hasher-aliases else bst_notok libkcapi/hasher-aliases "$ALIASDIR/sha256sum absent -- the kcapi-hasher aliases did not install" fi if [ -e "$BIN/sha256sum" ] && [ ! -e "$ALIASDIR/sha256sum" ]; then bst_notok libkcapi/no-bindir-shadow "a kcapi-hasher alias landed in $BIN and shadows coreutils" else bst_ok libkcapi/no-bindir-shadow fi # THE EXIT-127 CASE. usage() calls kcapi_version() out of the linked archive and # writes to stderr, so a tool that prints this line has loaded and executed # library code -- without needing a kernel. A tool that was never built exits # 127 with nothing, which is a different finding and reads differently here. for t in kcapi-hasher kcapi-dgst kcapi-rng; do if [ ! -x "$BIN/$t" ]; then bst_skip "libkcapi/runs-$t" "$BIN/$t not installed" continue fi out=$("$BIN/$t" -h 2>&1); rc=$? if bst_execfail "$out"; then bst_unrun "libkcapi/runs-$t" "$out" elif [ "$rc" -eq 127 ]; then bst_notok "libkcapi/runs-$t" "$t exited 127 -- present in the image but not executable as built" elif echo "$out" | grep -q "Kernel Crypto API"; then bst_ok "libkcapi/runs-$t" else bst_notok "libkcapi/runs-$t" "$t -h printed no usage banner (rc=$rc): $(_bst_1line "$(echo "$out" | head -c 200)")" fi done # The version the tool reports comes from the archive it linked, so this is the # cross-check that the CLI and the .pc describe the same library. if [ -x "$BIN/kcapi-dgst" ]; then dgstver=$("$BIN/kcapi-dgst" -h 2>&1 | sed -n 's/^Kernel Crypto API interface library version: *//p' | head -1) check_eq libkcapi/cli-version "$WANT_VERSTR" "$dgstver" else bst_skip libkcapi/cli-version "kcapi-dgst not installed" fi # --- the probe ---------------------------------------------------------------- if [ ! -x "$PROBE" ]; then bst_skip libkcapi/probe-runs "$PROBE absent from the image" exit 0 fi tmp=${TMPDIR:-/tmp} pout=$tmp/bst-libkcapi-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libkcapi/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libkcapi/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" rm -f "$pout" exit 0 else bst_notok libkcapi/probe-runs "probe exited $prc: $(_bst_1line "$(head -c 300 "$pout")")" fi # PROBE-END is the terminator. Without it a probe killed partway through would # leave every unreached case reporting its default and the suite would score a # truncated run as a clean one. if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libkcapi/probe-complete else bst_notok libkcapi/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -1 "$pout" 2>/dev/null))" fi # ONE awk pass over the probe's output, turning every `key=value` line into a # shell variable V_. This is the whole of the process cost of scoring the # probe half -- variables, not a fork per case. eval "$(awk -F= ' function q(v) { gsub(/'"'"'/, "'"'"'\\'"'"''"'"'", v); return "'"'"'" v "'"'"'" } /^[a-z][a-z0-9_]*=/ && seen[$1]++ == 0 { id = $1; v = substr($0, index($0, "=") + 1); print "V_" id "=" q(v) } ' "$pout")" # --- A group: the archive is alive ------------------------------------------- # These need no kernel. If any of them is red the port is broken, and every # EAFNOSUPPORT below becomes uninterpretable -- so they are scored first and # read first. check_eq libkcapi/version "$WANT_VERNUM" "$V_a_version" check_eq libkcapi/versionstring "$WANT_VERSTR" "$V_a_versionstring" case "$V_a_memset_secure" in zeroed) bst_ok libkcapi/memset-secure ;; DIRTY) bst_notok libkcapi/memset-secure "kcapi_memset_secure did not zero its buffer -- the compiler elided it, which is exactly what the function exists to prevent" ;; *) bst_notok libkcapi/memset-secure "unexpected a_memset_secure: '$V_a_memset_secure'" ;; esac # --- N group: the null control ----------------------------------------------- # Opened by the PROBE, in the same process and moments before AF_ALG. These are # what make the K group below a statement about ONE address family rather than # about sockets, libc or the sandbox. If either is red, DO NOT read the K group # as a kernel-crypto finding -- read it as an unknown. case "$V_n_af_inet" in open) bst_ok libkcapi/control-af-inet ;; *) bst_notok libkcapi/control-af-inet "AF_INET could not be opened in the probe's own process ($V_n_af_inet) -- the AF_ALG cases below are NOT evidence of a crypto gap while this is red" ;; esac case "$V_n_af_unix" in open) bst_ok libkcapi/control-af-unix ;; *) bst_notok libkcapi/control-af-unix "AF_UNIX could not be opened in the probe's own process ($V_n_af_unix) -- see control-af-inet" ;; esac # --- K group: the kernel crypto path ----------------------------------------- # The raw socket first, so the errno is read from the syscall directly rather # than inferred through libkcapi's return convention. This is the case that # names the gap; the four below are the library-level consequences of it. case "$V_k_af_alg_raw" in open) bst_ok libkcapi/af-alg-socket ;; *EAFNOSUPPORT*) bst_notok libkcapi/af-alg-socket \ "PLATFORM-NO-AF-ALG (brintos/linux#89): socket(AF_ALG, SOCK_SEQPACKET, 0) returns EAFNOSUPPORT ($V_k_af_alg_raw) while AF_INET and AF_UNIX open in the same process -- wasm32_defconfig carries no CONFIG_CRYPTO line, so no CONFIG_CRYPTO_USER_API_* and the family is never registered. A kernel CONFIG gap, not missing code (crypto/algif_hash.c is in the tree) and not a libkcapi defect" ;; *) bst_notok libkcapi/af-alg-socket "socket(AF_ALG) failed in an unexpected way: '$V_k_af_alg_raw' -- this is NOT the known platform gap" ;; esac # One scorer for the four convenience functions: they differ only in which # transform they ask for, and on this image they fail identically and for the # same one reason. Written as a function so the wording of the attribution # cannot drift between them. score_kernel_call() { _case=$1; _val=$2; _what=$3 case "$_val" in ok|[0-9]*\ ok) bst_ok "$_case" ;; *EAFNOSUPPORT*) bst_notok "$_case" \ "PLATFORM-NO-AF-ALG (brintos/linux#89): $_what returned -EAFNOSUPPORT ($_val) -- the AF_ALG family is not registered by this kernel; libkcapi propagates the socket(2) errno verbatim, so this is the gap reaching us unmodified and not a port defect" ;; '') bst_notok "$_case" "$_what produced no line in the probe output -- the probe did not reach it" ;; *) bst_notok "$_case" "$_what failed in an unexpected way: '$_val' -- this is NOT the known platform gap and should be read as a port defect" ;; esac } score_kernel_call libkcapi/md-sha256 "$V_k_md_sha256" "kcapi_md_sha256" score_kernel_call libkcapi/aes-cbc-enc "$V_k_aes_cbc_enc" "kcapi_cipher_enc_aes_cbc" score_kernel_call libkcapi/aes-cbc-dec "$V_k_aes_cbc_dec" "kcapi_cipher_dec_aes_cbc" score_kernel_call libkcapi/rng-bytes "$V_k_rng_get_bytes" "kcapi_rng_get_bytes" # The known-answer vectors. An unexecuted vector has NO VERDICT -- it is neither # a pass nor a failure -- so it is `skip` with the reason, and it starts # asserting correctness by itself the day AF_ALG appears. This is the half of # the ticket's stated self-test that the platform currently withholds. if [ -n "$V_k_md_sha256_kat" ]; then case "$V_k_md_sha256_kat" in match) bst_ok libkcapi/md-sha256-kat ;; *) bst_notok libkcapi/md-sha256-kat "SHA-256(\"abc\") did not match FIPS 180-4 B.1: got $V_k_md_sha256_digest, want $KAT_SHA256" ;; esac else bst_skip libkcapi/md-sha256-kat "the digest was never computed (AF_ALG absent) -- FIPS 180-4 B.1 vector carried but unexecuted" fi if [ -n "$V_k_aes_cbc_kat" ]; then case "$V_k_aes_cbc_kat" in match) bst_ok libkcapi/aes-cbc-kat ;; *) bst_notok libkcapi/aes-cbc-kat "AES-128-CBC did not match NIST SP 800-38A F.2.1: got $V_k_aes_cbc_ct" ;; esac else bst_skip libkcapi/aes-cbc-kat "no ciphertext was produced (AF_ALG absent) -- NIST SP 800-38A F.2.1 vector carried but unexecuted" fi if [ -n "$V_k_aes_cbc_roundtrip" ]; then case "$V_k_aes_cbc_roundtrip" in match) bst_ok libkcapi/aes-cbc-roundtrip ;; *) bst_notok libkcapi/aes-cbc-roundtrip "AES-CBC decrypt did not return the original plaintext" ;; esac else bst_skip libkcapi/aes-cbc-roundtrip "the round trip never ran (AF_ALG absent)" fi # --- the CLI half of the ticket's self-test ---------------------------------- # The ticket asks for the same fixture hashed BOTH through the library and # through kcapi-dgst on the command line. This is that second half. It reaches # the same one socket, so it is expected red today for the same one reason -- # but it is scored separately because "the library cannot" and "the shipped tool # cannot" are different claims, and a future image could regress one alone. if [ -x "$BIN/kcapi-dgst" ]; then dout=$(printf 'abc' | "$BIN/kcapi-dgst" -c sha256 --hex 2>&1); drc=$? dhex=$(echo "$dout" | tr -d ' \r\n') if [ "$drc" -eq 0 ] && [ "$dhex" = "$KAT_SHA256" ]; then bst_ok libkcapi/dgst-sha256 elif [ "$drc" -eq 0 ]; then bst_notok libkcapi/dgst-sha256 "kcapi-dgst -c sha256 returned 0 but the digest is wrong: got '$dhex', want $KAT_SHA256" elif echo "$dout" | grep -qiE 'not supported|EAFNOSUPPORT|Address family'; then bst_notok libkcapi/dgst-sha256 \ "PLATFORM-NO-AF-ALG (brintos/linux#89): kcapi-dgst -c sha256 could not open an AF_ALG socket (rc=$drc): $(_bst_1line "$(echo "$dout" | head -c 200)")" else bst_notok libkcapi/dgst-sha256 \ "PLATFORM-NO-AF-ALG (brintos/linux#89): kcapi-dgst -c sha256 failed (rc=$drc) on a kernel with no AF_ALG: $(_bst_1line "$(echo "$dout" | head -c 200)")" fi else bst_skip libkcapi/dgst-sha256 "kcapi-dgst not installed" fi rm -f "$pout"