Deterministic syscall fault injection for Ruby programs using Linux seccomp user notifications.
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.
- 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
- 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.
Add Brittle to your Gemfile:
gem "brittle"Then install it with Bundler:
bundle installOr install it directly:
gem install brittleClone 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 doctorInject 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 1The target reports Errno::ENOSPC, Brittle classifies it as expected, and a JSON journal is
written under results/.
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 resultResult lines use OK: or ERR:. A harness with a target-specific oracle may instead emit
EXPECTED:, CORRUPT:, or SWALLOWED:.
Run one deterministic injection:
brittle run harness/file_write.rb --inject write --errno ENOSPC --at 1Use sweep to test every matching occurrence in a range:
brittle sweep harness/logger.rb \
--syscall write --errno ENOSPC --fd-path /brittle- --at 1..20Selectors 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 1Every 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-....jsonVerdicts are expected, crash, swallowed, corrupt, leak, or hang.
- Brittle starts the harness as a filtered child process and keeps the supervisor unfiltered.
Brittle.arm!andBrittle.disarm!issue marker calls that delimit the operation under test.- The supervisor applies scope and occurrence rules only while the harness is armed.
- A matching call receives the configured errno or return value; every other call continues.
- Brittle records the injection and resulting process, descriptor, and artifact state in a journal.
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
endFault 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. Forwrite, the reported prefix is not written, so this is not a faithful partial-write emulator. Such journals sayrealistic: false.- An injected
EINTRhas 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.
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_catalogBug reports and pull requests are welcome at https://github.com/ydah/brittle.
Brittle is available under the MIT License.