How Checkbox runs jobs¶
Checkbox does more than start the command in a job definition. Before running it, Checkbox prepares an execution context with the required working directory, user and environment. It then starts the command, reading its output and exit code.
Checkbox has two ways to create this execution context: the legacy subprocess-based runner and the systemd-based runner. They have the same goal, but they differ in strategy, affecting isolation and, when Checkbox is installed as a snap, confinement.
Why job isolation matters¶
A test job is not necessarily a short-lived, well-behaved command. It can put the system under pressure, change process attributes or leave child processes behind. These effects should remain local to the job. Otherwise, one job can affect the Checkbox agent or change the conditions under which later jobs run.
A cgroup is a Linux mechanism for organising processes and applying resource accounting and controls to them. Giving each job its own cgroup establishes a clear boundary between the test workload and Checkbox itself. It also lets systemd manage the job as a unit rather than as an ordinary child process of the agent.
The subprocess-based runner¶
The subprocess-based runner starts the job as a child process of the Checkbox agent. This is a simple execution model, but the job remains in the agent’s cgroup and inherits many process attributes from it.
Sharing a cgroup means that a job can taint the execution context used by Checkbox. For example, processes left by one job can remain associated with the same cgroup in which later jobs run. Additionally systemd processes may not distinguish between the job and the agent, given they are in the same cgroup, leading to actions taken on the job affecting the agent as well (for example, OOMKill)
Snap confinement adds another limitation. A subprocess starts inside both the Checkbox snap’s mount namespace and its AppArmor sandbox. If that subprocess invokes an application from another snap, the application does not enter its normal security context as this is effectively not supported. The resulting security context of this action is effectively undefined behaviour but in most cases results in the very permissive Checkbox AppArmor profile being applied, so the other snap’s permissions are not enforced as intended.
flowchart LR
subgraph checkbox[Checkbox execution context]
agent[Checkbox agent]
child[Child subprocess]
command[Job shell]
agent -->|Spawn with job context| child
child --> command
command -->|exit status and output| child
child --> agent
end
The systemd-based runner¶
The systemd-based runner delegates process creation to plz-run. plz-run
is a tool similar to systemd_run, it uses systemd to create a transient
unit for the job. The resulting service is not a child of the Checkbox agent
and receives its own cgroup.
For a non-root job, the transient unit uses the system-login PAM
configuration. This establishes the user session through PAM rather than
merely changing the process user within the Checkbox process tree.
flowchart TB
subgraph checkbox[Checkbox execution context]
agent[Checkbox agent]
request["plz-run"]
agent --> request
end
request -->|D-Bus StartTransientUnit<br/>Job context| systemd[systemd]
systemd --> unit[Transient unit]
subgraph job[Job execution context]
unit --> pam["PAM session<br/>(non-root jobs)"]
pam --> command[Job shell]
end
command -->|exit status and output| unit
unit --> systemd
systemd --> request
request --> agent
On a classic installation, this provides the process and cgroup isolation. In a strict snap installation, this also crosses the Checkbox AppArmor boundary. The transient unit runs outside the Checkbox AppArmor sandbox, while entering the Checkbox snap’s mount namespace so that commands and content supplied by the snap remain valid.
Running outside the Checkbox AppArmor profile does not mean bypassing all system permissions. The job still runs as its selected user and remains subject to normal permissions and any security policy applied to programs it starts. When invoking a snap app, snapd can now correctly apply that snap’s own namespace, AppArmor profile and interface permissions. This is why calls to other snaps behave correctly, rather than inheriting the Checkbox snap’s confinement.
Comparing the runners¶
Property |
Subprocess-based runner |
Systemd-based runner |
|---|---|---|
Process relationship |
Job is a child of Checkbox |
Job is an independent systemd transient unit |
Cgroup |
Shared with Checkbox |
Dedicated |
User |
Only the user changes |
Uses PAM sessions for non-root jobs |
Checkbox snap namespace |
Shared with Checkbox |
Shared with Checkbox |
AppArmor profile |
Shared with Checkbox |
None |
Invoking other snaps |
Undefined behaviour - Mostly inherited from Checkbox |
Normal |
Defaults¶
The systemd-based runner is the default runner for snaps. The legacy
subprocess-based runner can be re-enabled by setting the feature flag
systemd_based_job_runner to
False.