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:

  1. Create a file with known contents (populates page cache)

  2. splice() that file’s data into the suspect subsystem’s socket/interface

  3. Trigger the subsystem’s processing (send, encrypt, hash)

  4. 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.