Linux System Programming · intermediate · ~20 min

/proc forensics — deep dive

Build defensive tooling that parses `/proc` fields safely and reliably.

Overview

Why parsing matters

If you build defender tooling on Linux, parsing /proc is the foundation. (/proc is a virtual filesystem the kernel exposes; reading its text files lets you inspect running processes.)

What robust parsing means

  • Cap the length of each line you read.
  • Validate the shape of every field before trusting it.
  • On a parse failure, default to unknown rather than 0.

Why it matters

Tools like osquery, falco, auditd, and essentially every Linux DFIR (Digital Forensics and Incident Response) tool read /proc.

A bug in one of those parsers becomes a missed detection.

Core concepts

Field stability

Not all /proc fields are equally dependable across kernel versions:

  • Stable: VmRSS, VmPeak, Threads, Pid, PPid, Uid, Name.
  • Less stable: Cgroup, NSpgid, SigQ.
  • Volatile: anything carrying a timestamp.

The per-PID race

A PID (process ID) can disappear between the moment you open /proc/PID/ and the moment you read a file inside it.

Always handle ENOENT (the "no such file" error) on every read after the first.

Pentester mindset

A defender's tool that crashes on a fast-cycling PID is itself a vulnerability: a denial-of-defense.

To prove your tool is robust, test it against a process that churns rapidly, for example:

bash -c 'while true; do true; done'

Defensive coding habits

  • Cap line length.
  • Use sscanf with explicit width specifiers.
  • Always check the return value.

Syntax notes

man 5 proc is the canonical reference. It is large but well indexed.

Lesson

Scope of this lesson

The recipes lesson showed how to use /proc. This lesson is for engineers writing the forensic tools themselves.

When you build those tools:

  • Cap line lengths.
  • Handle short reads.
  • Parse fields robustly.
  • Refuse anything that does not match the documented format.

Code examples

FILE fp = fopen("/proc/self/status", "r"); / read this process's status file / char line[512]; / fixed, bounded line buffer / long vm_rss_kb = -1; / -1 means "not found / unknown" / while (fgets(line, sizeof line, fp)) / read one line at a time / if (sscanf(line, "VmRSS: %ld kB", &vm_rss_kb) == 1) break; / match field, stop on success */ fclose(fp);

Line by line

Reading VmRSS line by line

FILE *fp = fopen("/proc/self/status", "r");   /* read this process's status file */
char line[512];                                /* fixed, bounded line buffer       */
long vm_rss_kb = -1;                            /* -1 means "not found / unknown"   */
while (fgets(line, sizeof line, fp))            /* read one line at a time          */
    if (sscanf(line, "VmRSS: %ld kB", &vm_rss_kb) == 1) break;  /* match field, stop on success */
fclose(fp);
  • fopen opens the status file for reading.
  • line[512] bounds how much of any single line we accept.
  • vm_rss_kb = -1 is the "unknown" default, not 0.
  • fgets reads one bounded line per iteration.
  • sscanf returns 1 only when the field matched; we then stop.

Common mistakes

  • Trusting that /proc fields are stable across kernel versions. They are not always.

Debugging tips

Diff your tool's output against a known-good baseline:

ps -o pid,rss,vsz,comm

Memory safety

  • Lines are bounded by the buffer; cap the length explicitly anyway.
  • Some /proc files reject pread. Always read them sequentially with fgets.

Real-world uses

osquery, falco, auditd, htop, monitorix, and effectively every observability agent.

Practice tasks

  1. Parse VmRSS from /proc/self/status.
  2. Iterate over all PIDs and extract comm and state.
  3. Detect when a PID disappears mid-read.

Summary

  • Deep /proc work = robust parsing + race awareness + capped reads.
  • Default to unknown, never 0, on a parse failure.
  • Handle ENOENT: any PID can vanish mid-read.

Practice with these exercises