Hole Punching - dev-shm

#race-conditions#toctou
Vuln Classestoctou
Requirestoctou-window
Givesdeterministic-race-win

Core Idea

The target file here is /dev/shm. This is a tmpfs directory which does not store it’s file in disk but rather in page caches. These file in this tmpfs has two separate concepts:

  1. Logical file size — the range of offsets that belong to the file.
  2. Physical backing —RAM pages backed by the virtual address space

What is a Hole?

A hole is a range inside a file that exists logically but has no physical storage backing.

Logical file:

0 KB       4 KB       8 KB       12 KB
│----------│----------│----------│
  DATA       HOLE       HOLE       DATA
Physical backing:

offset 0 KB     → physical block/page
offset 4 KB     → NULL (hole)
offset 8 KB     → NULL (hole)
offset 12 KB    → physical block/page

Reading from a hole returns zeroes, even though no physical blocks/pages are allocated.


What is Hole Punching?

Hole punching removes the physical storage backing a specific range of a file without changing the logical file size.

fallocate(fd,FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,offset,length);

After the operation:

  • File size remains the same.
  • The specified range no longer consumes physical storage.
  • Reads from that range return zeroes.

Why Does Hole Punching Exist?

It allows applications to reclaim storage without rewriting or shrinking the entire file.

Consider a log file that is being written into for months, for instance

10 GB file

[ January ] [ February ] [ March ]
    3 GB        3 GB       4 GB

If January and February are no longer needed:

Before:

physical storage = 10 GB
logical size     = 10 GB

Punch the first 6 GB:

After:

[       HOLE       ][ March ]

physical storage ≈ 4 GB
logical size     = 10 GB

March remains at the same logical offset.


Why Not Just Write Zeroes?

Writing zeroes:

[000000000000][DATA]

still consumes physical storage. Slower !!


Hole Punching in tmpfs (/dev/shm)

/dev/shm is usually backed by tmpfs.

For a disk filesystem:

file offset → filesystem mapping → disk block

For tmpfs:

file offset → shmem/tmpfs mapping → RAM page

Therefore, a hole in tmpfs means:

file offset → no physical RAM page

Step 1 — Create the File

ftruncate(fd, 64 * 1024 * 1024);

The logical file size becomes:

64 MB

But physical RAM pages may not yet be allocated.

offset 0      → NULL
offset 4096   → NULL
offset 8192   → NULL

Step 2 — Allocate Backing

fallocate(fd, 0, 0, 64 * MB);

Now the tmpfs file has backing pages.

Step 3 — Punch the Hole

fallocate(fd,
    FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
    0,
    64 * MB);

The backing pages are removed.

offset 0      → NULL
offset 4096   → NULL
offset 8192   → NULL

However, the file still has a logical size of 64 MB.


How to use ?