Project

brittle

0.0
The project is in a healthy, maintained state
Brittle injects errno responses into selected Linux syscalls using seccomp user notifications.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 3.3
 Project Readme

brittle

Deterministic syscall fault injection for Ruby programs using Linux seccomp user notifications.

Gem Version Downloads CI Ruby Version Linux License

Features · Installation · Quick Start · Usage · How It Works · Development


Brittle makes real syscalls fail at deterministic points so Ruby error paths can be exercised without replacing application code with mocks. It is built on seccomp-notify.

Warning

Brittle deliberately breaks its target process. Use it only in isolated development and test environments, never in production.

Features

  • Inject an errno or synthetic return value into selected syscalls
  • Arm and disarm around only the operation under test
  • Select exact occurrences, every nth call, or a seeded probability
  • Scope calls by file descriptor path, opened path, or destination port
  • Sweep occurrence ranges to find failure boundaries
  • Record output, artifacts, fd counts, and verdicts in reproducible JSON journals

Requirements

  • Linux 5.5 or newer
  • x86_64 or aarch64
  • Ruby 3.2 or newer
  • A container or host that permits seccomp user notifications

Linux 5.0 can return synthetic errno values, but Brittle also needs continue! to pass unmatched syscalls through, which requires Linux 5.5.

Installation

Add Brittle to your Gemfile:

gem "brittle"

Then install it with Bundler:

bundle install

Or install it directly:

gem install brittle

Quick Start

Clone the repository and build the isolated Linux environment:

git clone https://github.com/ydah/brittle.git
cd brittle
docker build -f Dockerfile.dev -t brittle-dev .
bin/dev bundle exec exe/brittle doctor

Inject ENOSPC into the first armed write from the included file harness:

bin/dev bundle exec exe/brittle run \
  harness/file_write.rb --inject write --errno ENOSPC --at 1

The target reports Errno::ENOSPC, Brittle classifies it as expected, and a JSON journal is written under results/.

Writing a Harness

Harnesses call Brittle.arm! immediately before the operation under test and always call Brittle.disarm! afterward. Startup, requires, and result output therefore pass through without fault injection.

require "brittle/marker"

result = nil
Brittle.arm!
begin
  File.write(ENV.fetch("BRITTLE_SANDBOX") + "/out.txt", "hello")
  result = "OK:out.txt:5"
rescue => error
  result = "ERR:#{error.class}:#{error.message}"
ensure
  Brittle.disarm!
end
puts result

Result lines use OK: or ERR:. A harness with a target-specific oracle may instead emit EXPECTED:, CORRUPT:, or SWALLOWED:.

Usage

Run one deterministic injection:

brittle run harness/file_write.rb --inject write --errno ENOSPC --at 1

Use sweep to test every matching occurrence in a range:

brittle sweep harness/logger.rb \
  --syscall write --errno ENOSPC --fd-path /brittle- --at 1..20

Selectors and scopes can be combined as needed:

# Multiple exact occurrences
brittle run harness/file_write.rb --inject write --errno EDQUOT --at 3,7,11

# Every fifth matching call
brittle run harness/file_write.rb --inject write --errno ENOSPC --every 5

# Seeded probability
brittle run harness/file_write.rb \
  --inject write --errno ENOSPC --probability 0.1 --seed 42

# Path and destination scopes
brittle run harness/file_write.rb --inject openat --errno EMFILE --path /brittle-
brittle run harness/net_http.rb --inject connect --errno ECONNREFUSED --port 8080

# Synthetic successful return without executing the syscall
brittle run harness/syswrite_loop.rb --inject write --return-value 10 --at 1

Journals

Every run writes a JSON journal under results/. Journals include the scenario, environment, injection events, stdout/stderr, artifact sizes and SHA-256 hashes, fd counts, and verdict. They can be summarized or converted back into a command:

brittle report results/
brittle reproduce results/file_write-write-ENOSPC-1-....json

Verdicts are expected, crash, swallowed, corrupt, leak, or hang.

How It Works

  1. Brittle starts the harness as a filtered child process and keeps the supervisor unfiltered.
  2. Brittle.arm! and Brittle.disarm! issue marker calls that delimit the operation under test.
  3. The supervisor applies scope and occurrence rules only while the harness is armed.
  4. A matching call receives the configured errno or return value; every other call continues.
  5. Brittle records the injection and resulting process, descriptor, and artifact state in a journal.

Scenario DSL

The same matching rules are available to Ruby callers:

scenario = Brittle.scenario do
  inject :write, errno: Errno::ENOSPC, at: 3, when_fd_path: /brittle-/
  inject :connect, errno: Errno::ECONNREFUSED, port: 8080, every: 1
end

Reality checks

Fault injection identifies candidates; it does not prove that a result is an upstream bug. Re-run every finding under the real failure condition before reporting it. The checks under verify/ demonstrate this workflow.

  • allow!(n) reports success without performing the syscall. For write, the reported prefix is not written, so this is not a faithful partial-write emulator. Such journals say realistic: false.
  • An injected EINTR has no accompanying signal. Brittle marks that combination as unrealistic.
  • Pointer-based path and port scopes are for test targeting, not security decisions; target memory may change before a continued syscall executes.

See NOTES.md for measurements and the first real-condition finding.

Further Reading

Development

bundle install
bundle exec rake

# Linux integration tests
docker build -f Dockerfile.dev -t brittle-dev .
bin/dev bundle exec rake

# Short catalog check; omit AT_RANGE for the full 1..20 sweep
AT_RANGE=1..2 bin/dev script/sweep_catalog

Contributing

Bug reports and pull requests are welcome at https://github.com/ydah/brittle.

License

Brittle is available under the MIT License.