- Rust 65.8%
- Shell 34.2%
| apparmor | ||
| etc | ||
| lainos-sandbox-wrap | ||
| openrc-isolation-layer-02 | ||
| profiles | ||
| rc-sandbox | ||
| architecture.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| openrc-isolation-backup.sh | ||
| openrc-security-status.sh | ||
| README.md | ||
openrc-isolation
OpenRC service isolation stack for lainOS Layer 03.
Provides systemd-equivalent service containment (ProtectSystem=, PrivateTmp=,
capability bounding, resource limits, syscall filtering) on native OpenRC using
four composed kernel primitives:
- bwrap ~ mount/PID/network namespace isolation and filesystem containment
- cgroup-v2 ~ resource accounting and limits (memory, CPU, processes)
- seccomp-bpf ~ per-service syscall allowlisting with caching
- Landlock LSM ~ path-scoped access enforcement that survives a namespace escape
No fork of OpenRC. No compatibility layer. No systemd code. Just declarative
variables in /etc/conf.d/<service> translated into kernel primitives by
userspace tooling built for this purpose.
Components
| Component | Location | Purpose |
|---|---|---|
rc-sandbox |
/usr/libexec/rc-sandbox |
Rust binary invoked by OpenRC runscripts ~ checks rc_sandbox opt-out, sets up cgroups, constructs and execs into the bwrap command line |
lainos-sandbox-wrap |
/usr/libexec/lainos-sandbox-wrap |
Rust binary that runs inside the namespace ~ applies Landlock, sets no_new_privs, drops capabilities, loads seccomp filter |
| Seccomp profiles | /etc/lainos/seccomp/*.list |
Flat syscall allowlists (lainos-base, lainos-network, lainos-privileged) |
openrc-security-status |
/usr/bin/openrc-security-status |
Verification script that checks all isolation layers at runtime (maintained separately from this repo) |
Both rc-sandbox and lainos-sandbox-wrap are Rust binaries built from a
shared Cargo workspace. No shell scripts or interpreted code exist in the
isolation chain between OpenRC and the target service.
Build
Workspace (both binaries)
cargo build --release --target x86_64-unknown-linux-musl
Run from the workspace root. This produces both target/x86_64-unknown-linux-musl/release/rc-sandbox
and target/x86_64-unknown-linux-musl/release/lainos-sandbox-wrap in one pass.
Both binaries are statically linked with musl libc: rc-sandbox for
consistency and reduced runtime dependencies, and lainos-sandbox-wrap
because it runs inside the sandbox before shared libraries are guaranteed
to be reachable.
Layout
openrc-isolation/
├── Cargo.toml # workspace root
├── rc-sandbox/
│ ├── Cargo.toml
│ └── src/main.rs
├── lainos-sandbox-wrap/
│ ├── Cargo.toml
│ └── src/
│ ├── main.rs
│ └── landlock.rs
└── profiles/
├── lainos-base.list
├── lainos-network.list
└── lainos-privileged.list
Dependencies
libc~ low-level syscalls (prctl,capset,landlock_create_ruleset)libseccomp~ Rust bindings to the systemlibseccomplibrary for seccomp-bpfcaps~ pure-Rust capability handling (no C linkage, minimal overhead)anyhow~ error handling in both binaries
Release Profile
[profile.release]
lto = true # Link-time optimization ~ reduces size, improves performance
panic = "abort" # Smaller binary, no unwinding overhead
strip = true # Removes debug symbols for smaller binary
Defined once in the workspace root Cargo.toml and shared by both members.
These settings produce small, predictable binaries suitable for
security-critical components.
Installation (Gentoo)
This repository is used as the source for the app-lainos/openrc-isolation
ebuild in the ::lainos overlay, built via cargo.eclass (not Meson).
Ebuild Example
# Copyright 2026 Grayson Giles <amnesia@lainos.net>
# Distributed under the terms of the GNU General Public License v3
EAPI=8
CRATES="
libc@0.2.169
libseccomp@0.3.0
caps@0.5.5
anyhow@1.0.95
"
inherit cargo
DESCRIPTION="lainOS Layer 03 OpenRC service isolation stack: rc-sandbox, lainos-sandbox-wrap, seccomp profiles"
HOMEPAGE="https://lainos.net"
SRC_URI="$(cargo_crate_uris ${CRATES})"
LICENSE="GPL-3"
SLOT="0"
KEYWORDS="~amd64"
RDEPEND="
sys-apps/bubblewrap
sys-libs/libseccomp
"
DEPEND="${RDEPEND}"
BDEPEND="
sys-libs/libseccomp
"
S="${WORKDIR}/${P}"
src_unpack() {
cargo_src_unpack
mkdir -p "${S}" || die
cp -r "${FILESDIR}"/. "${S}/" || die
}
src_compile() {
cd "${S}" || die
cargo_src_compile --target x86_64-unknown-linux-musl
}
src_install() {
cd "${S}" || die
exeinto /usr/libexec
newexe "target/x86_64-unknown-linux-musl/release/rc-sandbox" rc-sandbox
newexe "target/x86_64-unknown-linux-musl/release/lainos-sandbox-wrap" \
lainos-sandbox-wrap
insinto /etc/lainos/seccomp
doins "profiles/lainos-base.list"
doins "profiles/lainos-network.list"
doins "profiles/lainos-privileged.list"
}
pkg_postinst() {
elog "OpenRC Service Isolation Stack installed."
elog ""
elog "To sandbox a service, edit its runscript and set:"
elog " command=\"/usr/libexec/rc-sandbox\""
elog " command_args=\"/usr/bin/target binary args\""
elog ""
elog "Then configure isolation in /etc/conf.d/<service>:"
elog " rc_private_tmp=\"YES\""
elog " rc_protect_system=\"STRICT\""
elog " rc_seccomp_profile=\"lainos-network\""
elog ""
elog "To verify isolation, run:"
elog " doas openrc-security-status"
}
openrc-security-status ships as its own package and is not installed by
this ebuild.
Usage
Sandboxing a Service
By default, a runscript pointed at rc-sandbox is sandboxed. To sandbox a
service, edit its OpenRC runscript (e.g., /etc/init.d/dnsmasq):
command="/usr/libexec/rc-sandbox"
command_args="/usr/sbin/dnsmasq --keep-in-foreground ${dnsmasq_args}"
Then configure isolation in /etc/conf.d/<service>:
# Opt out of sandboxing entirely (default: unset = sandboxed)
#rc_sandbox="NO"
# Private /tmp
rc_private_tmp="YES"
# Hide /home and /root
rc_protect_home="YES"
# Read-only /usr and /boot
rc_protect_system="STRICT"
# PID namespace: default is a new one (requires foreground mode).
# Set "NO" for services needing host PID visibility (e.g. dhcpcd, D-Bus).
rc_unshare_pid="YES"
# Capabilities to keep (comma-separated, empty means drop all)
rc_capability_bounding_set="CAP_NET_BIND_SERVICE,CAP_NET_RAW"
# Memory limit (e.g., "512M")
rc_memory_max="256M"
# CPU quota (e.g., "50%")
rc_cpu_quota="50%"
# Seccomp profile (lainos-base, lainos-network, lainos-privileged)
rc_seccomp_profile="lainos-network"
# Allow host network access (omit for full network isolation)
rc_network_access="YES"
# Landlock read-only paths (colon-separated)
rc_landlock_ro="/etc/dnsmasq.conf:/usr/share/dnsmasq"
# Landlock read-write paths (colon-separated)
rc_landlock_rw="/var/lib/dnsmasq:/run/dnsmasq"
Verification
Run the verification script (from the separate openrc-security-status
package) to check that all isolation layers are active:
doas openrc-security-status
Example output:
dnsmasq: namespace OK cgroup OK seccomp OK Landlock OK AppArmor OK
If any layer is missing, the script flags it as a warning or failure.
Security Considerations
Execution Order (Do Not Reorder)
The execution order in lainos-sandbox-wrap is security-critical:
- Landlock ~ applied against the sandboxed filesystem view
- no_new_privs ~ blocks setuid/setgid escalation
- Capability drop ~ before seccomp load (capset/prctl syscalls may be blocked afterward)
- seccomp-bpf ~ loaded last; once active, all subsequent syscalls are filtered
Reordering these steps can undermine isolation.
rc-sandbox has its own ordering requirement: it must exec into bwrap
immediately after cgroup setup, with no fork and no work after, so that
signals from OpenRC's supervisor (TERM on rc-service <n> stop) land on
bwrap directly rather than on an intermediate process that might not
forward them.
Failure Behavior
| Layer | Failure Mode | Rationale |
|---|---|---|
| bwrap (namespace) | Hard failure | Primary containment layer |
| cgroup-v2 | Soft failure (warning) | Hardening layer |
| Landlock | Soft failure (warning) | Backstop layer |
| seccomp-bpf | Hard failure | Primary containment layer |
| no_new_privs | Hard failure | Closes setuid-based seccomp bypass |
PID Namespace and Foreground Mode
Services sandboxed with the default rc_unshare_pid="YES" run in a new PID
namespace, which breaks host-visible PID files. These services must run in
the foreground (--keep-in-foreground, --RunAsDaemon 0, or equivalent),
with OpenRC tracking the rc-sandbox/bwrap process directly. Services
that genuinely require host PID visibility (e.g. dhcpcd, D-Bus policy)
set rc_unshare_pid="NO" ~ a workload-compatibility declaration, not a
containment downgrade; every other layer still applies.
AppArmor Integration
AppArmor profiles exist for both services and sandboxing components
(rc-sandbox, lainos-sandbox-wrap, and bwrap). All profiles are written
manually ~ aa-genprof and aa-logprof do not work reliably inside user
namespaces.
Development Status
This repository is complete for lainOS Layer 03.
rc-sandbox:rc_sandboxopt-out checkrc-sandbox: cgroup-v2 limit writingrc-sandbox: bwrap command line construction and execlainos-sandbox-wrap:no_new_privslainos-sandbox-wrap: capability droplainos-sandbox-wrap: Landlock ruleset constructionlainos-sandbox-wrap: seccomp profile parsing, compilation, and caching- AppArmor profiles: manual authoring
- Empirical verification: OpenRC cgroup-v2 ownership on target version
- Empirical verification: bwrap signal forwarding under
rc-service stop
openrc-security-status is tracked and versioned separately from this repo.
License
GPL-3.0
Repository
Forgejo: https://forgejo.lain.rocks/lainOS/openrc-isolation
Website: https://lainos.net