Audit Methodology
Research Question
Which kernel subsystems accept `splice()’d data and then modify it in place without verifying page ownership?
Step 1: Map the Data Flow
For each subsystem, trace what happens to a page after splice().
The splice/sendfile zero-copy paths are where page cache references leak into subsystem buffers.
Key kernel functions to trace:
# Find all splice_read / splice_write implementations
# These are the entry points where page cache pages get handed to subsystems
grep -rn 'splice_read\|splice_write\|\.splice_read\s*=' net/ drivers/ crypto/ \
--include='*.c' | grep -v '^Binary'
# Find in-place modifications on skb frags -- the Dirty Frag pattern
# skb_frag_page() returns the page; any write to it without copying is suspect
grep -rn 'skb_frag_page\|skb_frag_address' net/ --include='*.c'
# Find sg_set_page / sg_set_buf in crypto paths -- the CopyFail pattern
# These set up scatterlist entries pointing to potentially shared pages
grep -rn 'sg_set_page\|sg_set_buf' crypto/ --include='*.c'
Follow the page through the subsystem’s processing. The critical question: does it encrypt/transform in-place on the original page, or does it allocate a new page first?
Step 2: Check for the Guard
The correct pattern is copy before modifying. Look for the absence of these guards:
// SAFE -- subsystem copies before writing
skb_cow_data() // Copy-on-write for skb data
get_page() + copy // Explicit page copy
alloc_page() + memcpy // New page allocation
skb_copy() // Full skb copy
These indicate in-place modification — suspect:
// SUSPECT -- modifying potentially shared pages
sg_set_page(sg, page, ...) // Then crypto transform on scatterlist
skb_frag_page(frag) // Then write through returned pointer
page_address(page) // Then direct write
kmap(page) + write // Then modify mapped page
If a subsystem obtains a page reference via splice and then calls a crypto transform, checksum, or compression function on it without an intervening copy — that is a candidate vulnerability.
Step 3: Build a Trigger
If you find a suspect path, the test is straightforward:
-
Create a file with known contents (populates page cache)
-
splice()that file’s data into the suspect subsystem’s socket/interface -
Trigger the subsystem’s processing (send, encrypt, hash)
-
mmap()the original file and check if contents changed in memory
// Minimal test skeleton (pseudocode)
int fd = open("testfile", O_RDONLY);
int sock = socket(AF_ALG, ...); // or AF_INET for network subsystems
// Plant page cache reference into subsystem buffer
splice(fd, NULL, sock, NULL, len, 0);
// Trigger processing -- subsystem performs crypto/transform
// ...
// Verify: did the original file's page cache change?
void *map = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
// Compare map contents against original -- if different, you found it
Step 4: Verify and Document
Before reporting, confirm:
-
The modification persists in the page cache (visible via
mmap()or re-read) -
The modification is controllable (attacker influences what bytes are written)
-
The path is reachable from an unprivileged user (directly or via user namespaces)
-
The affected file can be security-sensitive (
/etc/passwd, SUID binary, etc.)
Document the full data flow: splice() call → subsystem entry → page reference → in-place modification → page cache corruption.
This is what the kernel security team needs in a disclosure.