Skip to content
Open
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
198 changes: 198 additions & 0 deletions .github/workflows/afalg.yml
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
22 changes: 22 additions & 0 deletions doc/dox_comments/header_files/aes.h
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,17 @@ int wc_AesGcmSetKey(Aes* aes, const byte* key, word32 len);
It also encodes the input authentication vector, authIn, into the
authentication tag, authTag.

\note When built with WOLFSSL_AFALG_XILINX_AES, the Xilinx AF_ALG kernel
interface operates on a combined cipher text + tag buffer, so this function
does not honor the exact-size buffer contract described below. Both in and
out must be allocated with WC_AES_BLOCK_SIZE (16) bytes of room beyond sz:
sz + 16 bytes are sent to the kernel from in (the trailing 16 bytes are
scratch space for the tag and their contents are irrelevant), and sz + 16
bytes are read back into out. The tag is additionally copied out to authTag
as usual. Both buffers should also be aligned to WOLFSSL_XILINX_ALIGN; an
unaligned in is staged through a temporary allocation, or rejected with
BAD_ALIGN_E if NO_WOLFSSL_ALLOC_ALIGN is defined.

\return 0 On successfully encrypting the input message

\param aes - pointer to the AES object used to encrypt data
Expand Down Expand Up @@ -420,6 +431,17 @@ int wc_AesGcmEncrypt(Aes* aes, byte* out,
the output data is undefined. However, callers must unconditionally zeroize
the output buffer to guard against leakage of cleartext data.

\note When built with WOLFSSL_AFALG_XILINX_AES, the Xilinx AF_ALG kernel
interface operates on a combined cipher text + tag buffer, so this function
does not honor the exact-size buffer contract described below. Both in and
out must be allocated with WC_AES_BLOCK_SIZE (16) bytes of room beyond sz.
The tag to check against is written into in + sz by this function, which
means the in buffer is modified even though it is declared const, and
sz + 16 bytes are read back into out. Both buffers should also be aligned
to WOLFSSL_XILINX_ALIGN; an unaligned in is staged through a temporary
allocation, or rejected with BAD_ALIGN_E if NO_WOLFSSL_ALLOC_ALIGN is
defined.

\return 0 On successfully decrypting and authenticating the input message
\return AES_GCM_AUTH_E If the authentication tag does not match the
supplied authentication code vector, authTag.
Expand Down
5 changes: 5 additions & 0 deletions wolfcrypt/src/aes.c
Original file line number Diff line number Diff line change
Expand Up @@ -16073,6 +16073,11 @@ int wc_AesGetKeySize(Aes* aes, word32* keySize)

#elif defined(WOLFSSL_AFALG)
/* implemented in wolfcrypt/src/port/af_alg/afalg_aes.c */
#define _AesEcbEncrypt(aes, out, in, sz) wc_AesEcbEncrypt(aes, out, in, sz)

Copy link
Copy Markdown

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's wc_Afalg_AesDirect, which checks only < 0 on sendmsg/read. A short transfer returns 0 while out still 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/read byte counts to equal sz, or split the XTS ECB call into socket-sized chunks.

#ifdef HAVE_AES_DECRYPT
#define _AesEcbDecrypt(aes, out, in, sz) \
wc_AesEcbDecrypt(aes, out, in, sz)
#endif

#elif defined(WOLFSSL_DEVCRYPTO_AES)
/* implemented in wolfcrypt/src/port/devcrypt/devcrypto_aes.c */
Expand Down
63 changes: 50 additions & 13 deletions wolfcrypt/src/port/af_alg/afalg_aes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.");
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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");
Expand All @@ -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;
}
Expand Down Expand Up @@ -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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 sz % WC_AES_BLOCK_SIZE is not rejected. aes.c:16403 returns BAD_LENGTH_E for a partial block, while the AF_ALG path forwards it to the kernel and surfaces WC_AFALG_SOCK_E. wc_AesEcbDecrypt (line 1037) has the same gap.

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 BAD_LENGTH_E when sz % WC_AES_BLOCK_SIZE != 0 in both wc_AesEcbEncrypt and wc_AesEcbDecrypt.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 BAD_FUNC_ARG guards to wc_AesEcbEncrypt, wc_AesEcbDecrypt, wc_AesEncryptDirect and wc_AesDecryptDirect, and reorders them ahead of the KEYUSAGE_E check, but the only test added covers AES-GCM reuse; no test passes NULL or a wrong-direction key to these entry points.

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 aes/out/in return BAD_FUNC_ARG and a wrong-direction key returns KEYUSAGE_E.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 wc_AesEcbEncrypt/wc_AesEcbDecrypt adds NULL guards but not the sz % WC_AES_BLOCK_SIZE rejection that the portable wc_AesEcbEncrypt (wolfcrypt/src/aes.c:16403) enforces. wc_Afalg_AesDirect then ignores the read() byte count, so a non-block-multiple sz returns 0 while the trailing sz % 16 bytes of out retain stale caller memory.

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 BAD_LENGTH_E when sz % WC_AES_BLOCK_SIZE != 0 in both AF_ALG ECB entry points, matching wolfcrypt/src/aes.c:16403.

return BAD_FUNC_ARG;
}

if (aes->dir != AES_ENCRYPTION) {
return KEYUSAGE_E;
}

Expand All @@ -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;
}

Expand Down
Loading
Loading