-
Notifications
You must be signed in to change notification settings - Fork 1k
Fixes for AF-ALG code paths and add CI test #11162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
801063d
6c8a44a
626950b
9304abe
4fb7883
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| name: AF_ALG Tests | ||
|
|
||
| # START OF COMMON SECTION | ||
| on: | ||
| push: | ||
| branches: [ 'release/**' ] | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, ready_for_review] | ||
| branches: [ '*' ] | ||
| # Weekday-morning cron (10:00 UTC) seeds the master-scoped ccache that PR runs | ||
| # restore: re-runs --build-only (compile only, no tests) on the | ||
| # default branch. PR runs are read-only (see ccache-setup). | ||
| schedule: | ||
| - cron: '2 10 * * 1-5' | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
| # END OF COMMON SECTION | ||
|
|
||
| jobs: | ||
| # The Linux AF_ALG port (wolfcrypt/src/port/af_alg/) offloads AES and SHA-256 | ||
| # to the kernel crypto API over AF_ALG sockets. Note that Docker's default | ||
| # seccomp profile blocks socket(AF_ALG). | ||
| # | ||
| # Both configs build on one runner via .github/scripts/parallel-make-check.py | ||
| # (see os-check.yml for the full pattern): each builds in its own out-of-tree | ||
| # ("VPATH") build directory off one checkout/autogen, on a pool of one-per-CPU | ||
| # worker threads, longest first. | ||
| make_check: | ||
| name: make check | ||
| if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} | ||
| runs-on: ubuntu-24.04 | ||
| # Generous for a cold ccache; warm reruns finish in a fraction. | ||
| timeout-minutes: 20 | ||
| steps: | ||
| - uses: actions/checkout@v5 | ||
| name: Checkout wolfSSL | ||
|
|
||
| - name: Install dependencies | ||
| uses: ./.github/actions/install-apt-deps | ||
| with: | ||
| packages: autoconf automake libtool build-essential bubblewrap | ||
| ghcr-debs-tag: ubuntu-24.04-minimal | ||
|
|
||
| # Ubuntu 24.04 can restrict unprivileged user namespaces via AppArmor, | ||
| # which would stop the test scripts from re-execing under | ||
| # bwrap --unshare-net (their port-isolation mechanism). | ||
| - name: Allow unprivileged user namespaces (for bwrap) | ||
| run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true | ||
|
|
||
| # The socket families the port binds through (algif_hash, algif_skcipher, | ||
| # algif_aead) are loadable modules, normally autoloaded on bind() via | ||
| # their module aliases. On the -azure kernels the hosted runners boot, | ||
| # algif_aead is not in the installed linux-modules-azure package, and | ||
| # Ubuntu's generated blacklist neutralizes it with an | ||
| # "install algif_aead /bin/false" rule -- which defeats the bind-time | ||
| # autoload too, so aead/gcm(aes) is unreachable until it is loaded by | ||
| # hand. The underlying cipher is not the problem: gcm(aes) is already | ||
| # registered by aesni_intel. | ||
| # | ||
| # --ignore-install skips that /bin/false rule; linux-modules-extra | ||
| # supplies the .ko if the base package really lacks it (that package is | ||
| # often absent from the mirrors for the runner's exact kernel revision, | ||
| # so it is a best-effort second try, not a dependency). Failures stay | ||
| # non-fatal here: the probe below is what turns a missing algorithm into | ||
| # a red check, and it names the algorithm when it does. | ||
| - name: Load AF_ALG kernel modules | ||
| run: | | ||
| uname -r | ||
| missing= | ||
| for m in algif_hash algif_skcipher algif_aead gcm; do | ||
| if sudo modprobe --ignore-install "$m"; then | ||
| echo "modprobe $m: ok" | ||
| else | ||
| echo "modprobe $m: not loadable, will retry after modules-extra" | ||
| missing="$missing $m" | ||
| fi | ||
| done | ||
| if [ -n "$missing" ]; then | ||
| grep -rn 'algif_' /etc/modprobe.d /lib/modprobe.d || true | ||
| sudo apt-get update -qq || true | ||
| sudo apt-get install -y "linux-modules-extra-$(uname -r)" || true | ||
| for m in $missing; do | ||
| if sudo modprobe --ignore-install "$m"; then | ||
| echo "modprobe $m: ok after modules-extra" | ||
| else | ||
| echo "modprobe $m: still not loadable" | ||
| fi | ||
| done | ||
| fi | ||
| echo '--- registered AES/SHA-256 algorithms (name/driver) ---' | ||
| awk '/^name/ { n = $3 } /^driver/ { print n "\t" $3 }' /proc/crypto \ | ||
| | grep -E 'aes|sha256' | sort -u || true | ||
|
|
||
| # Preflight: bind every (type, name) pair wolfcrypt/src/port/af_alg/ uses, | ||
| # so a runner image without one of them fails here with the missing | ||
| # algorithm named, rather than deep inside testwolfcrypt. Deliberately a | ||
| # hard failure and not a skip: a green check that exercised no AF_ALG code | ||
| # would be worse than a red one. The set mirrors afalg_hash.c (sha256) and | ||
| # afalg_aes.c (cbc/ecb/ctr/gcm); extend it when the port grows an | ||
| # algorithm. | ||
| - name: Verify the kernel provides the algorithms the port needs | ||
| run: | | ||
| cat > "$RUNNER_TEMP/afalg-probe.c" <<'EOF' | ||
| #include <stdio.h> | ||
| #include <string.h> | ||
| #include <unistd.h> | ||
| #include <sys/socket.h> | ||
| #include <linux/if_alg.h> | ||
|
|
||
| static const char* types[] = { | ||
| "hash", "skcipher", "skcipher", "skcipher", "aead" | ||
| }; | ||
| static const char* names[] = { | ||
| "sha256", "cbc(aes)", "ecb(aes)", "ctr(aes)", "gcm(aes)" | ||
| }; | ||
|
|
||
| int main(void) | ||
| { | ||
| struct sockaddr_alg sa; | ||
| size_t i; | ||
| int fd; | ||
| int missing = 0; | ||
|
|
||
| for (i = 0; i < sizeof(types) / sizeof(types[0]); i++) { | ||
| fd = socket(AF_ALG, SOCK_SEQPACKET, 0); | ||
| if (fd < 0) { | ||
| printf("::error::socket(AF_ALG) unavailable on this kernel\n"); | ||
| return 1; | ||
| } | ||
| memset(&sa, 0, sizeof(sa)); | ||
| sa.salg_family = AF_ALG; | ||
| strncpy((char*)sa.salg_type, types[i], sizeof(sa.salg_type) - 1); | ||
| strncpy((char*)sa.salg_name, names[i], sizeof(sa.salg_name) - 1); | ||
| if (bind(fd, (struct sockaddr*)&sa, sizeof(sa)) < 0) { | ||
| printf("::error::kernel is missing %s/%s\n", types[i], names[i]); | ||
| missing = 1; | ||
| } | ||
| else { | ||
| printf("ok: %s/%s\n", types[i], names[i]); | ||
| } | ||
| close(fd); | ||
| } | ||
| return missing; | ||
| } | ||
| EOF | ||
| gcc -Wall -Werror -o "$RUNNER_TEMP/afalg-probe" "$RUNNER_TEMP/afalg-probe.c" | ||
| "$RUNNER_TEMP/afalg-probe" | ||
|
|
||
| # ccache via the cross-platform composite; the script passes the | ||
| # compiler to configure as CC="ccache gcc" (or a per-config "cc"). | ||
| - name: Set up ccache | ||
| uses: ./.github/actions/ccache-setup | ||
| with: | ||
| workflow-id: afalg | ||
| read-only: ${{ github.event_name == 'pull_request' }} | ||
| max-size: 100M | ||
|
|
||
| - name: Build all configs (parallel, out-of-tree) | ||
| run: | | ||
| cat > "$RUNNER_TEMP/afalg-configs.json" <<'EOF' | ||
| [ | ||
| {"name": "defaults-afalg", "minutes": 2, | ||
| "configure": ["--enable-afalg"], | ||
| "cflags": "-pedantic -Wdeclaration-after-statement -Wnull-dereference -Wno-overlength-strings"}, | ||
| {"name": "all-afalg", "minutes": 5, | ||
| "configure": ["--enable-all", "--enable-testcert", "--enable-acert", | ||
| "--enable-dtls13", "--enable-dtls-mtu", "--enable-dtls-frag-ch", | ||
| "--enable-dtlscid", "--enable-quic", "--enable-afalg", | ||
| "--disable-srtp", "--disable-sha224", "--disable-hashflags", | ||
| "--disable-cryptocb", "--disable-aesgcm-stream"], | ||
| "cflags": "-pedantic -Wdeclaration-after-statement -Wnull-dereference -Wno-overlength-strings"} | ||
| ] | ||
| EOF | ||
| .github/scripts/parallel-make-check.py \ | ||
| ${{ github.event_name == 'schedule' && '--build-only' || '' }} \ | ||
| --private-dir=certs \ | ||
| "$RUNNER_TEMP/afalg-configs.json" | ||
|
|
||
| - name: ccache stats | ||
| if: always() | ||
| run: ccache -s || true | ||
|
|
||
| - name: Upload logs on failure | ||
| if: failure() | ||
| uses: actions/upload-artifact@v6 | ||
| with: | ||
| retention-days: 7 | ||
| name: afalg-logs | ||
| path: | | ||
| build-*/make-check.log | ||
| build-*/test-suite.log | ||
| build-*/config.log | ||
| if-no-files-found: ignore |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -362,7 +362,11 @@ static int wc_Afalg_AesDirect(Aes* aes, byte* out, const byte* in, word32 sz) | |
| #if defined(WOLFSSL_AES_DIRECT) && defined(WOLFSSL_AFALG) | ||
| int wc_AesEncryptDirect(Aes* aes, byte* out, const byte* in) | ||
| { | ||
| if (aes && (aes->dir != AES_ENCRYPTION)) { | ||
| if (aes == NULL || out == NULL || in == NULL) { | ||
| return BAD_FUNC_ARG; | ||
| } | ||
|
|
||
| if (aes->dir != AES_ENCRYPTION) { | ||
| return KEYUSAGE_E; | ||
| } | ||
|
|
||
|
|
@@ -372,7 +376,11 @@ int wc_AesEncryptDirect(Aes* aes, byte* out, const byte* in) | |
|
|
||
| int wc_AesDecryptDirect(Aes* aes, byte* out, const byte* in) | ||
| { | ||
| if (aes && (aes->dir != AES_DECRYPTION)) { | ||
| if (aes == NULL || out == NULL || in == NULL) { | ||
| return BAD_FUNC_ARG; | ||
| } | ||
|
|
||
| if (aes->dir != AES_DECRYPTION) { | ||
| return KEYUSAGE_E; | ||
| } | ||
|
|
||
|
|
@@ -585,10 +593,12 @@ int wc_AesGcmSetKey(Aes* aes, const byte* key, word32 len) | |
|
|
||
| /* Performs AES-GCM encryption and returns 0 on success | ||
| * | ||
| * Warning: If using Xilinx hardware acceleration it is assumed that the out | ||
| * buffer is large enough to hold both cipher text and tag. That is | ||
| * sz | 16 bytes. The input and output buffer is expected to be 64 bit | ||
| * aligned | ||
| * Warning: If using Xilinx hardware acceleration it is assumed that both the in | ||
| * and out buffers are large enough to hold cipher text and tag. That is | ||
| * sz | 16 bytes. sz | 16 bytes are sent to the kernel from the in | ||
| * buffer, with the trailing 16 bytes being scratch space for the tag, | ||
| * and sz | 16 bytes are read back into the out buffer. The input and | ||
| * output buffer is expected to be 64 bit aligned | ||
| * | ||
| */ | ||
| int wc_AesGcmEncrypt(Aes* aes, byte* out, const byte* in, word32 sz, | ||
|
|
@@ -657,6 +667,16 @@ int wc_AesGcmEncrypt(Aes* aes, byte* out, const byte* in, word32 sz, | |
| WOLFSSL_MSG("CMSG_FIRSTHDR() in wc_AesGcmEncrypt() returned NULL unexpectedly."); | ||
| return SYSLIB_FAILED_E; | ||
| } | ||
|
|
||
| /* Always set the operation. The same Aes structure, and with it the same | ||
| * AF_ALG socket, can be used for both encrypt and decrypt calls, so the | ||
| * operation currently stored in the control message could be left over | ||
| * from a previous call in the other direction. */ | ||
| if (wc_Afalg_SetOp(cmsg, AES_ENCRYPTION) < 0) { | ||
| WOLFSSL_MSG("Error with setting AF_ALG operation"); | ||
| return WC_AFALG_SOCK_E; | ||
| } | ||
|
|
||
| cmsg = CMSG_NXTHDR(msg, cmsg); | ||
| if (cmsg == NULL) { | ||
| WOLFSSL_MSG("CMSG_NEXTHDR() in wc_AesGcmEncrypt() returned NULL unexpectedly."); | ||
|
|
@@ -789,10 +809,12 @@ int wc_AesGcmEncrypt(Aes* aes, byte* out, const byte* in, word32 sz, | |
| #if defined(HAVE_AES_DECRYPT) || defined(HAVE_AESGCM_DECRYPT) | ||
| /* Performs AES-GCM decryption and returns 0 on success | ||
| * | ||
| * Warning: If using Xilinx hardware acceleration it is assumed that the in | ||
| * buffer is large enough to hold both cipher text and tag. That is | ||
| * Warning: If using Xilinx hardware acceleration it is assumed that both the in | ||
| * and out buffers are large enough to hold cipher text and tag. That is | ||
| * sz | 16 bytes. The in buffer has tag appended even though it is | ||
| * const for this wolfSSL API. | ||
| * const for this wolfSSL API, and sz | 16 bytes are read back into the | ||
| * out buffer. The input and output buffer is expected to be 64 bit | ||
| * aligned. | ||
| */ | ||
| int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz, | ||
| const byte* iv, word32 ivSz, | ||
|
|
@@ -831,7 +853,10 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz, | |
| return ret; | ||
|
|
||
| if (aes->rdFd == WC_SOCK_NOTSET) { | ||
| aes->dir = AES_DECRYPTION; | ||
| /* aes->dir is not changed here, the operation used with the socket is | ||
| * set on every call below. It is left as AES_ENCRYPTION, the value set | ||
| * by wc_AesGcmSetKey, so that the software tag handling can still make | ||
| * use of wc_AesEncryptDirect. */ | ||
| if ((ret = wc_AesSetup(aes, WC_TYPE_AEAD, WC_NAME_AESGCM, ivSz, | ||
| authInSz)) != 0) { | ||
| WOLFSSL_MSG("Error with first time setup of AF_ALG socket"); | ||
|
|
@@ -855,7 +880,9 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz, | |
| if ((cmsg = CMSG_FIRSTHDR(msg)) == NULL) { | ||
| return WC_AFALG_SOCK_E; | ||
| } | ||
| if (wc_Afalg_SetOp(cmsg, aes->dir) < 0) { | ||
| /* Always set the operation. The socket could have been created by a | ||
| * previous wc_AesGcmEncrypt call made with this same Aes structure. */ | ||
| if (wc_Afalg_SetOp(cmsg, AES_DECRYPTION) < 0) { | ||
| WOLFSSL_MSG("Error with setting AF_ALG operation"); | ||
| return WC_AFALG_SOCK_E; | ||
| } | ||
|
|
@@ -991,7 +1018,13 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz, | |
| #ifdef HAVE_AES_ECB | ||
| int wc_AesEcbEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) | ||
| { | ||
| if (aes && (aes->dir != AES_ENCRYPTION)) { | ||
| /* argument sanity checks come before the key usage check so that bad | ||
| * arguments always report BAD_FUNC_ARG, matching the software version */ | ||
| if (aes == NULL || out == NULL || in == NULL) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 [Low] AF_ALG ECB sanity checks omit the block-size length check the software path enforces · API contract violations The new check block is documented as matching the software version, but Related known finding #7443 (similar but distinct): Both concern AF_ALG AES API-contract behavior, but this is ECB partial-block input validation in wc_AesEcbEncrypt/Decrypt, whereas #7443 is AES-GCM authentication-tag-size caching in different operations. Their root causes and required patches are distinct. Fix: Return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚪ [Info] New AF-ALG argument sanity checks have no test coverage · Missing edge-case coverage on a function the PR also changed The PR adds Related known finding #7442 (similar but distinct): Both concern AF-ALG AES behavior, but the candidate is missing tests for newly added argument/key-direction guards in ECB/direct APIs; issue 7442 is a stale socket operation-direction bug in GCM decrypt. The operations, root causes, functions, and required patches differ. Fix: Add assertions that NULL There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 [Low] AF_ALG ECB entry points omit the block-size length check enforced by the portable implementation · Buffer overflows The reworked argument validation in Related known finding #7442 (similar but distinct): Both affect AF_ALG AES code in afalg_aes.c, but this finding concerns ECB encrypt/decrypt accepting non-block-aligned lengths because the entry points omit validation and the direct helper ignores short reads. Issue 7442 concerns GCM decrypt using a stale encryption direction after socket reuse. The faulting operations, root causes, functions, and required patches differ. Fix: Return |
||
| return BAD_FUNC_ARG; | ||
| } | ||
|
|
||
| if (aes->dir != AES_ENCRYPTION) { | ||
| return KEYUSAGE_E; | ||
| } | ||
|
|
||
|
|
@@ -1001,7 +1034,11 @@ int wc_AesEcbEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) | |
|
|
||
| int wc_AesEcbDecrypt(Aes* aes, byte* out, const byte* in, word32 sz) | ||
| { | ||
| if (aes && (aes->dir != AES_DECRYPTION)) { | ||
| if (aes == NULL || out == NULL || in == NULL) { | ||
| return BAD_FUNC_ARG; | ||
| } | ||
|
|
||
| if (aes->dir != AES_DECRYPTION) { | ||
| return KEYUSAGE_E; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 [Medium] AES-XTS bulk ECB now routed through AF_ALG helper that ignores short socket transfers · Cryptographic correctness
The new macros make
_AesXtsHelper(aes.c:17773/17776) run the multi-block XTS ECB step through AF_ALG'swc_Afalg_AesDirect, which checks only< 0onsendmsg/read. A short transfer returns 0 whileoutstill holds plaintext XOR tweak, which the caller then un-XORs back to cleartext.Related known finding #3557 (similar but distinct): Both concern AES-XTS behavior in aes.c, but the candidate faults the AF_ALG-backed bulk ECB operation in _AesXtsHelper due to unchecked short socket I/O, whereas #3557 faults missing XTS data-unit length enforcement in wc_AesXtsEncrypt. The root causes and required patches are separate.
Fix: Require the AF_ALG
sendmsg/readbyte counts to equalsz, or split the XTS ECB call into socket-sized chunks.