Skip to content

About

Linux process-forensics benchmark for rebuilding historical file-descriptor tables from strace, /proc semantics, forks, descriptor sharing, renames, and IPC.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FD Table Rebuild

A Linux systems-forensics project for reconstructing historical process file-descriptor tables from an strace recording.

The project models a batch job whose processes were traced while running. At several checkpoints, the job was frozen and its /proc/<pid>/fd and /proc/<pid>/fdinfo state was captured.

The objective is to replay the system-call history and reproduce the descriptor state that the Linux kernel exposed at each checkpoint.

Overview

A file descriptor is not simply:

descriptor number → pathname

A correct reconstruction needs to account for several layers of kernel state:

process
   ↓
descriptor table
   ↓
file descriptor
   ↓
open file description
   ↓
filesystem object / pipe / socket

Different descriptors can share the same open file description, which means they may also share:

  • file position
  • O_APPEND
  • O_NONBLOCK

At the same time, descriptor-specific state such as close-on-exec remains associated with the descriptor entry itself.

Repository Structure

fd-table-rebuild/
├── environment/
├── solution/
├── tests/
├── instruction.md
└── task.toml

instruction.md

Defines the trace inputs, checkpoint semantics, descriptor fields, and required output format.

environment/

Contains the reproducible runtime and captured trace data.

solution/

Contains the reference reconstruction implementation.

tests/

Contains the independent verifier.

task.toml

Defines task metadata and execution configuration.

Input Evidence

The primary evidence is:

/app/data/trace.txt

which contains the system-call trace.

Checkpoint times are provided in:

/app/data/checkpoints.json

Additional capture conventions and initial state are documented in:

/app/data/NOTES.md

The solver must reconstruct descriptor state from these sources rather than inspecting a running copy of the original job.

File-Descriptor State

For every live process at each checkpoint, the reconstruction reports every open descriptor.

Each descriptor contains six values:

target
pos
mode
append
nonblock
cloexec

target

The name that /proc/<pid>/fd/<fd> would expose.

For regular files this is the current absolute path.

If a file no longer has a filesystem name while a process still holds it open, the target is represented with:

 (deleted)

Pipes and sockets use the naming convention defined by the capture notes.

pos

The current file position reported by fdinfo.

For pipes and sockets:

pos = 0

mode

The descriptor access mode:

r
w
rw

append

Whether O_APPEND is enabled.

nonblock

Whether O_NONBLOCK is enabled.

cloexec

Whether the descriptor will be closed during a successful exec.

Open File Descriptions

One of the key Linux concepts in this project is the distinction between a file descriptor and an open file description.

For example:

fd 5 ──┐
       ├── shared open file description
fd 9 ──┘
              │
              ├── file position
              ├── O_APPEND
              └── O_NONBLOCK

Operations on one descriptor can therefore affect another descriptor when both reference the same underlying open file description.

This commonly happens through:

  • fork
  • descriptor duplication
  • descriptor transfer between processes

Descriptor Duplication

The replay must account for descriptor duplication operations such as:

dup
dup2
dup3
fcntl(F_DUPFD...)

A duplicated descriptor normally refers to the same open file description as its source.

That distinction is critical when reconstructing offsets and file status flags.

Processes and Threads

Threads and processes do not always have independent descriptor state.

Threads within a process can share a descriptor table, while process creation can either copy or share descriptor-related state depending on the operation involved.

The reconstruction therefore tracks thread-group and process lifetime rather than treating every traced task ID as an independent process.

Threads are not emitted as separate processes in the final artifact.

Fork and Exec

Process creation and execution affect descriptor state differently.

Relevant behavior includes:

fork / clone
    ↓
inherit descriptor references

exec
    ↓
remove descriptors marked close-on-exec

Correct replay must preserve these lifetime transitions.

Descriptor Passing

Unix-domain sockets can transfer descriptors between processes using SCM_RIGHTS.

The receiving process obtains a new descriptor, but the underlying open file description can still be shared with the sender.

This means file position and status changes can propagate across processes even when their descriptor numbers differ.

Pipes and Sockets

The trace also includes IPC descriptors such as:

  • pipes
  • Unix socket pairs

These do not behave like ordinary seekable files.

Their positions remain zero, while status flags such as non-blocking mode can still matter.

File Renames

An open descriptor refers to the file object, not permanently to the pathname used when it was opened.

For example:

open("/work/current.log")
        ↓
descriptor references file object
        ↓
rename("/work/current.log", "/work/archive.log")
        ↓
same descriptor now resolves to
/work/archive.log

A reconstruction that permanently stores the original open pathname will therefore be incorrect.

Deleted Files

An open file can remain accessible after its directory entry is removed.

For example:

open("result.dat")
      ↓
fd 7

unlink("result.dat")
      ↓
fd 7 remains valid

The corresponding /proc target is represented as a deleted file while the open reference remains alive.

Working Directories

Relative path resolution also depends on process working-directory state.

Calls such as:

chdir
openat

must therefore be interpreted using the directory context that existed when the operation occurred.

Status and Descriptor Flags

The project distinguishes open-file-description status from per-descriptor flags.

For example:

O_APPEND
O_NONBLOCK

belong to the shared open file description.

By contrast:

FD_CLOEXEC

belongs to an individual file descriptor.

Mixing these two levels can produce descriptor tables that look plausible while still disagreeing with Linux kernel behavior.

Historical Replay

The task is fundamentally a state-replay problem.

Conceptually:

initial process state
        ↓
system call
        ↓
descriptor mutation
        ↓
process creation / exit
        ↓
filesystem mutation
        ↓
IPC / descriptor transfer
        ↓
checkpoint
        ↓
snapshot descriptor tables

The reconstruction continues across the full trace, emitting state whenever a requested checkpoint is reached.

Output

The solver writes:

/app/output/fd_tables.json

The output has one top-level key:

{
  "checkpoints": {}
}

Each checkpoint maps live process IDs to their descriptor tables.

A simplified example is:

{
  "checkpoints": {
    "K01": {
      "412": {
        "0": {
          "target": "/dev/null",
          "pos": 0,
          "mode": "r",
          "append": false,
          "nonblock": false,
          "cloexec": false
        }
      }
    }
  }
}

Process IDs and descriptor numbers are represented as JSON object keys.

Exited processes are omitted, and threads are not listed separately.

The complete schema is defined by instruction.md.

System Calls and Behaviors

The project involves reasoning about Linux operations such as:

open / openat
close
close_range
dup / dup2 / dup3
fcntl
ioctl
pipe / pipe2
socketpair
read / write
lseek
rename
unlink
chdir
fork / clone / vfork
exec
exit
sendmsg / recvmsg

A reconstruction does not necessarily need to implement every Linux syscall in existence—only the semantics required by the supplied capture.

Why the Task Is Challenging

The trace records system calls, while the required output describes kernel state.

Those are related but not identical.

A correct solution must infer the persistent state produced by operations over time:

syscall history
      ↓
kernel-object relationships
      ↓
process lifetime
      ↓
descriptor-table state
      ↓
checkpoint snapshot

Several descriptor numbers can be correct while their shared position, flags, path, or lifetime is still wrong.

Technical Areas

This project exercises:

  • Linux system calls
  • POSIX process semantics
  • strace
  • /proc
  • file descriptors
  • open file descriptions
  • descriptor inheritance
  • IPC
  • Unix-domain sockets
  • SCM_RIGHTS
  • process and thread lifetimes
  • filesystem rename/unlink behavior
  • event replay
  • systems forensics
  • Python

Validation

The reconstruction is checked against descriptor tables captured directly from the kernel at the requested checkpoints.

Correctness includes:

live processes
descriptor numbers
targets
positions
access modes
append state
non-blocking state
close-on-exec state

The task expects exact discrete state rather than approximate numeric results.

Goal

FD Table Rebuild demonstrates how historical process state can be reconstructed from a syscall trace when the original processes no longer exist.

The core principle is:

Replay kernel object relationships, not just descriptor numbers and path strings.

License

No license is currently specified.

About

Linux process-forensics benchmark for rebuilding historical file-descriptor tables from strace, /proc semantics, forks, descriptor sharing, renames, and IPC.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages