From cca50408ada16e65ba18c00da6ad35840b67cded Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 5 Nov 2026 11:28:46 -0800 Subject: [PATCH 1/5] qat: validate DH inputs before offload and reject a zero or one shared secret --- wolfcrypt/src/dh.c | 20 +++++++++++++ wolfcrypt/src/port/intel/quickassist.c | 28 +++++++++++------- wolfcrypt/test/test.c | 40 ++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/wolfcrypt/src/dh.c b/wolfcrypt/src/dh.c index d0099b82ead..ab966d78a68 100644 --- a/wolfcrypt/src/dh.c +++ b/wolfcrypt/src/dh.c @@ -2446,6 +2446,26 @@ static int wc_DhAgree_Async(DhKey* key, byte* agree, word32* agreeSz, pBits = mp_unsigned_bin_size(&key->p) * 8; if (pBits == 768 || pBits == 1024 || pBits == 1536 || pBits == 2048 || pBits == 3072 || pBits == 4096) { + /* same checks as wc_DhAgree_Sync */ + if (mp_count_bits(&key->p) < DH_MIN_SIZE) { + WOLFSSL_MSG("DH prime smaller than DH_MIN_SIZE"); + return WC_KEY_SIZE_E; + } + if (mp_iseven(&key->p) == MP_YES) { + return MP_VAL; + } + #ifdef WOLFSSL_VALIDATE_FFC_IMPORT + if (wc_DhCheckPrivKey(key, priv, privSz) != 0) { + WOLFSSL_MSG("wc_DhAgree wc_DhCheckPrivKey failed"); + return DH_CHECK_PRIV_E; + } + #endif + /* Always validate peer public key (2 <= y <= p-2) per SP 800-56A */ + if (wc_DhCheckPubKey_ex(key, otherPub, pubSz, NULL, 0) != 0) { + WOLFSSL_MSG("wc_DhAgree wc_DhCheckPubKey failed"); + return DH_CHECK_PUB_E; + } + ret = wc_mp_to_bigint(&key->p, &key->p.raw); if (ret == MP_OKAY) ret = IntelQaDhAgree(&key->asyncDev, &key->p.raw, diff --git a/wolfcrypt/src/port/intel/quickassist.c b/wolfcrypt/src/port/intel/quickassist.c index 9e0981a76c2..e7bdb9cd22a 100644 --- a/wolfcrypt/src/port/intel/quickassist.c +++ b/wolfcrypt/src/port/intel/quickassist.c @@ -4443,23 +4443,31 @@ static void IntelQaDhAgreeCallback(void *pCallbackTag, CpaStatus status, } /* count leading zeros */ - while (out[idxTrim] == 0 && idxTrim < pOut->dataLenInBytes) { + while (idxTrim < pOut->dataLenInBytes && out[idxTrim] == 0) { idxTrim++; } pOut->dataLenInBytes -= idxTrim; - /* return data and trim leading zeros */ - if (dev->qat.out && (dev->qat.out != pOut->pData || idxTrim > 0)) { - XMEMMOVE(dev->qat.out, &out[idxTrim], pOut->dataLenInBytes); + /* The shared secret must not be 0 or 1 (SP 800-56A 5.7.1.1). */ + if (pOut->dataLenInBytes == 0 || + (pOut->dataLenInBytes == 1 && out[idxTrim] == 1)) { + WOLFSSL_MSG("IntelQaDhAgreeCallback: invalid shared secret"); + ret = MP_VAL; } + else { + /* return data and trim leading zeros */ + if (dev->qat.out && (dev->qat.out != pOut->pData || idxTrim > 0)) { + XMEMMOVE(dev->qat.out, &out[idxTrim], pOut->dataLenInBytes); + } - /* return final length */ - if (dev->qat.outLenPtr) { - *dev->qat.outLenPtr = pOut->dataLenInBytes; - } + /* return final length */ + if (dev->qat.outLenPtr) { + *dev->qat.outLenPtr = pOut->dataLenInBytes; + } - /* mark event result */ - ret = 0; /* success */ + /* mark event result */ + ret = 0; /* success */ + } } (void)opData; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 9fabf2da026..b9054a3e0bc 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -45420,6 +45420,39 @@ static wc_test_ret_t dh_agree_ct_test(DhKey* key, DhKey* key2, exit_dh_agree_ct: return ret; } +#ifndef WOLFSSL_KCAPI_DH +/* wc_DhAgree rejects an invalid peer public value, whichever backend it uses. */ +static wc_test_ret_t dh_agree_bad_pub_test(DhKey* key, const byte* priv, + word32 privSz, word32 pubSz, byte* y, byte* agree, word32 bufSize) +{ + wc_test_ret_t ret; + word32 agreeSz; + word32 ySz; + int i; + + for (i = 0; i < 3; i++) { + /* 0, 1, and a value larger than the prime. A generated public key + * can be shorter than the prime, so fill the whole buffer, which + * holds at least the prime, for that one. */ + ySz = (i == 2) ? bufSize : pubSz; + XMEMSET(y, (i == 2) ? 0xFF : 0x00, ySz); + if (i == 1) + y[ySz - 1] = 1; + + agreeSz = bufSize; + PRIVATE_KEY_UNLOCK(); + ret = wc_DhAgree(key, agree, &agreeSz, priv, privSz, y, ySz); + PRIVATE_KEY_LOCK(); + #if defined(WOLFSSL_ASYNC_CRYPT) + ret = wc_AsyncWait(ret, &key->asyncDev, WC_ASYNC_FLAG_NONE); + #endif + if (ret != WC_NO_ERR_TRACE(DH_CHECK_PUB_E)) + return WC_TEST_RET_ENC_I(i); + } + + return 0; +} +#endif #endif /* !WC_NO_RNG && (!HAVE_FIPS || FIPS_VERSION_GE(7,0)) && !HAVE_SELFTEST */ #if defined(WOLFSSL_DH_EXTRA) && !defined(NO_FILESYSTEM) && \ @@ -45746,6 +45779,13 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t dh_test(void) priv2, privSz2, pub2, pubSz2, agree, agree2, DH_TEST_BUF_SIZE); if (ret != 0) ERROR_OUT(ret, done); + +#ifndef WOLFSSL_KCAPI_DH + ret = dh_agree_bad_pub_test(key, priv, privSz, pubSz, agree2, agree, + DH_TEST_BUF_SIZE); + if (ret != 0) + ERROR_OUT(ret, done); +#endif #endif /* !WC_NO_RNG && (!HAVE_FIPS || FIPS_VERSION_GE(7,0)) && !HAVE_SELFTEST */ /* Test DH key import / export */ From 03ae0927fb2c5302aabe33d52a4b0feaa43a7e31 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 5 Nov 2026 11:28:46 -0800 Subject: [PATCH 2/5] qat: fix the carry index when incrementing RSA prime candidates --- wolfcrypt/src/port/intel/quickassist.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/port/intel/quickassist.c b/wolfcrypt/src/port/intel/quickassist.c index e7bdb9cd22a..ea2904ca93d 100644 --- a/wolfcrypt/src/port/intel/quickassist.c +++ b/wolfcrypt/src/port/intel/quickassist.c @@ -1158,13 +1158,14 @@ int IntelQaGenPrime(WC_ASYNC_DEV* dev, WC_RNG* rng, byte* primeBuf, /* if rollover occurred increment high order bytes */ /* increment by 1 does not affect odd/even */ int j; + primeData[byteCheck] = 0x01; for (j = primeSz - 2; j >= 0; j--) { - if (primeData[i] != 0xFF) { - primeData[i] += 1; + if (primeData[j] != 0xFF) { + primeData[j] += 1; break; } else { - primeData[i] = 0; + primeData[j] = 0; } } } From b4fc41d4e381bd306aecec05793f9b86d92f3f10 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 5 Nov 2026 11:28:46 -0800 Subject: [PATCH 3/5] qat: fix DRBG chunk sizes and buffers for requests over 64KB --- wolfcrypt/src/port/intel/quickassist.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/wolfcrypt/src/port/intel/quickassist.c b/wolfcrypt/src/port/intel/quickassist.c index ea2904ca93d..38e8a644767 100644 --- a/wolfcrypt/src/port/intel/quickassist.c +++ b/wolfcrypt/src/port/intel/quickassist.c @@ -4786,21 +4786,19 @@ int IntelQaDrbg(WC_ASYNC_DEV* dev, byte* rngBuf, word32 rngSz) /* chunk into LAC_DRBG_MAX_NUM_OF_BYTES (0xFFFF) */ while (ret == 0 && idx < rngSz) { /* setup operation data */ - gen = rngSz - gen; + gen = rngSz - idx; if (gen > 0xFFFF) gen = 0xFFFF; + /* the buffer is freed when the previous chunk completes */ pOut->dataLenInBytes = gen; - if (idx == 0 && pOut->pData == NULL) { - pOut->pData = XREALLOC(rngBuf, gen, dev->heap, + if (pOut->pData == NULL) { + pOut->pData = (Cpa8U*)XMALLOC(gen, dev->heap, DYNAMIC_TYPE_ASYNC_NUMA); if (pOut->pData == NULL) { ret = MEMORY_E; goto exit; } } - else { - XMEMCPY(pOut->pData, &rngBuf[idx], gen); - } opData->sessionHandle = dev->qat.op.drbg.handle; opData->lengthInBytes = gen; From 955573c2c645c23b845d22a5a73c798bf6de95ba Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 5 Nov 2026 15:22:28 -0800 Subject: [PATCH 4/5] qat: document building the driver on Linux 7.0 and recovering from fragmented memory --- wolfcrypt/src/port/intel/README.md | 48 ++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/wolfcrypt/src/port/intel/README.md b/wolfcrypt/src/port/intel/README.md index d73f6a0bc06..7660a22415b 100644 --- a/wolfcrypt/src/port/intel/README.md +++ b/wolfcrypt/src/port/intel/README.md @@ -61,6 +61,53 @@ cd ../wolfssl make ``` +An Intel QAT 8970 adapter appears as three C62x devices, as in the output above. + +### After a kernel upgrade + +The QAT driver is built out of tree, so it has to be rebuilt and reinstalled for each new kernel. Until then `systemctl status qat` still reports `active`, but its log shows `modprobe: FATAL: Module usdm_drv not found`, `adf_ctl status` cannot open `/dev/qat_adf_ctl`, and wolfCrypt prints `Running without async`. + +### Building QAT 4.28 on Linux 7.0 + +Linux 7.0 removed two things the QAT 4.28 driver uses. Linux 6.14 still has both. + +* `iommu_domain_alloc()` is gone. In `quickassist/qat/drivers/crypto/qat/qat_common/qdm.c`, `qdm_init()` allocates the domain for an IOMMU-mapped PCI device instead: + +```c +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 18, 0) + { + struct pci_dev *pdev = NULL; + + domain = NULL; + while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pdev))) { + if (device_iommu_mapped(&pdev->dev)) { + domain = iommu_paging_domain_alloc(&pdev->dev); + pci_dev_put(pdev); + if (IS_ERR(domain)) + domain = NULL; + break; + } + } + } +#elif LINUX_VERSION_CODE < KERNEL_VERSION(3, 2, 0) +``` + +* Kbuild no longer reads `EXTRA_CFLAGS`, so the utility modules cannot find their headers. Pass the flags as `ccflags-y`. The driver itself sets its own `ccflags-y`, so build it first without the override: + +```sh +./configure --enable-legacy-algorithms +(cd quickassist/qat && make) +make quickassist-all 'ccflags-y=$(EXTRA_CFLAGS)' KBUILD_MODPOST_WARN=1 +make adf-ctl-all +sudo make qat-driver-install quickassist-install adf-ctl-install \ + qat-conf-install qat-fw-install qat-service-files-install \ + qat-service-install +``` + +`qat_api.ko`, the in-kernel crypto API module, reports an unresolved `__SCK__WARN_trap` symbol. The QAT service does not load it and user-space crypto does not need it. + +`qat-conf-install` moves the existing `/etc/_dev*.conf` files to `/etc/qat_conf_backup/` before copying new ones from `build/`, which this build does not populate. The devices then stay `down` and the service log shows `No GENERAL section found`. Back up the configuration files first and copy them back, or copy the defaults from `quickassist/utilities/adf_ctl/conf_files/`, then `sudo systemctl restart qat`. + ## Usage @@ -126,6 +173,7 @@ ls /sys/kernel/debug/qat_c6xx_/ # heartbeat, fw_co Common causes seen in practice: - Hugepage mismatch: `usdm_drv` loaded with `max_huge_pages` > 0 but `HugePages_Total` is smaller (or 0) -- every QAT process then fails its SAL memory init. Either reserve enough hugepages at boot (GRUB `default_hugepagesz=2M hugepagesz=2M hugepages=N`) or reload `usdm_drv` with `max_huge_pages=0` (non-hugepage contiguous memory). Runtime `sysctl vm.nr_hugepages=N` is unreliable once memory is fragmented. +- Fragmented memory without hugepages: processes fail with `ioctl_alloc_slab ... ioctl call for mem allocation failed` and `Lac_MemPoolCreate() - : Unable to allocate contiguous chunk of memory`, although plenty of memory is free. The last columns of `/proc/buddyinfo` (the largest free blocks) are 0 or close to it. Dropping caches and compacting memory restores them without a reboot: `sudo bash -c 'sync; echo 3 > /proc/sys/vm/drop_caches; echo 1 > /proc/sys/vm/compact_memory'`. - Orphan rings: `dmesg` shows `Process ... exit with orphan rings`. A QAT process exited (often killed by a test timeout) without an orderly `wolfAsync_HardwareStop`/`icp_sal_userStop`, leaking instance rings. These accumulate (especially under `make -j` test runs) until the device's instance pool is exhausted and new processes can no longer init. Recover with a full driver-stack reload (`systemctl stop qat; rmmod usdm_drv qat_c62x intel_qat; modprobe qat_c62x; modprobe usdm_drv; systemctl start qat`) or a reboot. Always shut down QAT-using processes cleanly; avoid `make -j` (see make check above). - PCIe Advanced Error Reporting errors (`UESta`/`CESta` flags set) or `dmesg` heartbeat/RAS/reset messages indicate a real firmware hang or bus error; with `AutoResetOnError = 0` (default in the device conf) the device stays down until a manual `adf_ctl reset`. Set `AutoResetOnError = 1` in `/etc/_dev*.conf` for self-healing. From b9d0b2b5327882985348c28c7e4ab6c272fc49e1 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 4 Nov 2026 16:12:27 -0800 Subject: [PATCH 5/5] qat: do not free the caller's buffer when making a DMA copy in IntelQaRealloc --- wolfcrypt/src/port/intel/quickassist_mem.c | 14 ++++--- wolfcrypt/test/test.c | 48 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/wolfcrypt/src/port/intel/quickassist_mem.c b/wolfcrypt/src/port/intel/quickassist_mem.c index f58f6d3fa5c..69e34b4c5dc 100644 --- a/wolfcrypt/src/port/intel/quickassist_mem.c +++ b/wolfcrypt/src/port/intel/quickassist_mem.c @@ -551,6 +551,9 @@ void* IntelQaRealloc(void *ptr, size_t size, void* heap, int type byte allocNew = 1; int newIsNuma = -1, ptrIsNuma = -1; size_t copySize = 0; + /* The port asks for a DMA copy of a buffer the caller keeps */ + int isCopy = (type == DYNAMIC_TYPE_ASYNC_NUMA || + type == DYNAMIC_TYPE_ASYNC_NUMA64); #ifndef USE_QAE_THREAD_LS int ret = pthread_mutex_lock(&g_memLock); @@ -593,8 +596,9 @@ void* IntelQaRealloc(void *ptr, size_t size, void* heap, int type else #endif { - /* use existing pointer and increment counter */ - header->count++; + /* use existing pointer, with a reference for a copy */ + if (isCopy) + header->count++; newPtr = origPtr; allocNew = 0; } @@ -619,10 +623,8 @@ void* IntelQaRealloc(void *ptr, size_t size, void* heap, int type copySize = size; XMEMCPY(newPtr, ptr, copySize); - if (ptrIsNuma == 0) { - /* old pointer is a qae-headed non-NUMA buffer (software - * fallback) -- free it. Caller-owned (ptrIsNuma == -1) and - * NUMA (== 1) buffers are handled by their own paths. */ + if (!isCopy && ptrIsNuma >= 0) { + /* normal REALLOC: free old pointer */ _qaeMemFree(ptr, heap, type #ifdef WOLFSSL_DEBUG_MEMORY , func, line diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index b9054a3e0bc..3e37eddb823 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -38777,6 +38777,54 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t memory_test(void) } #endif +#if defined(WOLFSSL_ASYNC_CRYPT) && defined(HAVE_INTEL_QA) + /* A DMA copy made with XREALLOC leaves the caller's buffer allocated. */ + { + byte *b = (byte*)XMALLOC(12, HEAP_HINT, + DYNAMIC_TYPE_CIPHER); + byte *c = NULL; + if (b == NULL) + return WC_TEST_RET_ENC_ERRNO; + XMEMSET(b, 0xA5, 12); + c = (byte*)XREALLOC(b, 16, HEAP_HINT, + DYNAMIC_TYPE_ASYNC_NUMA); + if (c == NULL) { + XFREE(b, HEAP_HINT, DYNAMIC_TYPE_CIPHER); + return WC_TEST_RET_ENC_ERRNO; + } + if (c == b || XMEMCMP(c, b, 12) != 0) + ret = WC_TEST_RET_ENC_NC; + XMEMSET(b, 0x5A, 12); + if (c != b) + XFREE(c, HEAP_HINT, DYNAMIC_TYPE_ASYNC_NUMA); + XFREE(b, HEAP_HINT, DYNAMIC_TYPE_CIPHER); + if (ret != 0) + return ret; + } + /* Any other XREALLOC is a normal realloc, even for a type the device can + * use, and the old buffer is released. */ + { + byte *b = (byte*)XMALLOC(16, HEAP_HINT, DYNAMIC_TYPE_PUBLIC_KEY); + byte *c = NULL; + int k; + if (b == NULL) + return WC_TEST_RET_ENC_ERRNO; + XMEMSET(b, 0xA5, 16); + c = (byte*)XREALLOC(b, 64, HEAP_HINT, DYNAMIC_TYPE_PUBLIC_KEY); + if (c == NULL) { + XFREE(b, HEAP_HINT, DYNAMIC_TYPE_PUBLIC_KEY); + return WC_TEST_RET_ENC_ERRNO; + } + for (k = 0; k < 16; k++) { + if (c[k] != 0xA5) + ret = WC_TEST_RET_ENC_NC; + } + XFREE(c, HEAP_HINT, DYNAMIC_TYPE_PUBLIC_KEY); + if (ret != 0) + return ret; + } +#endif + if (ret == 0) { /* This test is only interesting on embedded R/O Flash systems */ if (const_byte_ptr_test(const_byte_array, &j) != CBPTR_EXPECTED) {