Inspiration
Code that talks to a device has error paths: a short read, an EAGAIN, a read that takes a second too long. Real devices rarely fail on demand, so those paths are rarely run before they matter. On QNX a driver is a resource manager, an ordinary process that owns a pathname, and the pathname space lets a second process attach in front of the first. That is enough to build a test tool that needs no change to the application and no change to the driver.
What it does
qshim attaches to the pathname of a running resource manager (for example /dev/fakesensor) with _RESMGR_FLAG_BEFORE. New open() calls of unmodified applications resolve to qshim, and qshim forwards every request to the real driver, which keeps running.
- Fault injection. A scenario file is a list of one-line rules, such as
on read nth=3 do short 6oron read nth=4 do delay 1500. Actions: an errno, a delay, a short read, end of file, corrupted or replaced data. Rules can match by operation, devctl command, client pid, and count, and can be seeded for repeatable random faults. - Record. Every request and its answer is written to a JSON Lines trace.
- Replay.
qshim -R trace.jsonlserves the pathname from the trace alone, with the driver stopped. - Control device.
/dev/qshim/<name>reports counters and acceptspause,resume,clear, and new rules at run time. - Host tools (Python, standard library only): a scenario lint and a trace report that matches each injected fault to the scenario line that caused it.
The demo is a small thermostat client with three bugs. Three one-line scenarios expose a false frost alarm from a short read, an early exit on a single EAGAIN, and a control loop stalled by one slow read. A fixed version survives all three. The same session is then replayed with the driver stopped.
How we built it
- Target: QNX SDP 8.0, QNX 8.0.0 x86_64 under QEMU. Toolchain:
qcc -Vgcc_ntox86_64. Host: WSL2 Ubuntu. The build and test steps are in the README. - About 2,000 lines of C for the target, plus the Python host tools.
Where QNX is used (files in the repository):
src/main.c:dispatch_create(),resmgr_attach()with_RESMGR_FLAG_BEFORE, and athread_poolthat serves requests.src/backend.c: for each client open,ConnectAttach_r()to the real driver's channel and an_IO_CONNECTmessage sent withMsgSend_r(), so that qshim holds its own connection to the driver per open.src/passthru.c:iofunchandlers that forward read, write, devctl and stat withresmgr_msgread()andMsgSendv_r(); devctl payloads are forwarded without interpreting them.src/fault.candsrc/passthru.c: unblock handling. A client that is interrupted while qshim is delaying its request is released, and an unblock during a driver call is passed on to the driver.src/control.c: a second resource manager for the control device.policy/qshim.example.txtandtest/secpol/: a security policy fragment. Under a process type with noallow_attachrule for the path, the kernel refuses the attach and qshim exits with status 2 and names the missing rule.
Challenges we ran into
- A proxy that only forwards messages on one shared connection breaks per-open state in the driver. qshim opens the driver once per client open by sending the connect message itself.
- A delay injected by qshim must not make the client unkillable, so the delay has to cooperate with the kernel's unblock pulse.
- A refusal by the security policy arrives as
EACCES, while a missingpathspaceability arrives asEPERM. qshim reports both with a hint.
Accomplishments that we're proud of
- The application and the driver in the demo are the same binaries with and without qshim.
- Checked on the target under emulation: 123 target checks, 20 security policy checks, 17 demo checks, and 88 host unit tests.
- A seven-step codelab (
codelab/index.mdin the repository) that builds a transparent_RESMGR_FLAG_BEFOREproxy from an empty file, with the code of every step and a script that builds and runs each step on the target (32 checks).
What we learned
The resource manager model makes interposition a matter of pathname resolution order. No kernel module and no preload library is involved; the tool is one more process, and the security policy decides whether it may attach.
What's next
- Larger payloads in replay (reads above 256 bytes are truncated in the trace today).
- Tests on hardware and on aarch64. The target ran under emulation (QEMU without KVM), so the times shown in the video are not latency measurements.
- Clients that run as non-root or under restricted process types have not been tested yet.