• 5.5.3-26 b584bc9ff4

    amnesia released this 2026-07-17 13:22:37 +02:00 | 2 commits to main since this release

    protocol7-core-5.5.3-26 ~ Changelog

    Security Hardening ~ Complete Filesystem Isolation (lainos-dbus-bridge and lainos-notifyd)

    Both daemons now implement the full set of systemd filesystem-sandboxing equivalents:

    • ProtectSystem=strict equivalent: private mount namespace (unshare(CLONE_NEWNS)) with the root filesystem remounted read-only (MS_BIND | MS_RDONLY), preceded by an MS_PRIVATE remount to prevent propagation back to the real, shared namespace.
    • PrivateTmp=true equivalent: a fresh, size-limited (16MB), nosuid/nodev tmpfs mounted over /tmp, invisible to and from the real system /tmp.
    • ProtectHome=true equivalent: /home and /root replaced with empty, read-only, zero-size tmpfs mounts, hiding all user data from either daemon regardless of discretionary file permissions.
    • PrivateDevices=true equivalent: the real devtmpfs (thousands of device nodes) replaced with a minimal tmpfs containing only /dev/null, /dev/zero, /dev/urandom, /dev/random.

    All isolation steps run while still root, before privilege drop, since unshare()/mount()/mknod() require CAP_SYS_ADMIN (lost immediately after) and are intentionally absent from the seccomp whitelist. All failures on the read-only-root and /tmp//dev steps are fatal; /home//root mount failures are logged and treated as non-fatal (a configuration difference, not an isolation failure, e.g. on a system without a separate /root).

    Verification (both daemons, on real rc-service-managed production processes):

    • All four mounts confirmed active via direct /proc/<pid>/mounts inspection, correctly layered over the real, live mounts (visible in the same output for comparison) rather than replacing them system-wide.
    • Full dfuzzer 2.6 regression pass on lainos-dbus-bridge after each incremental addition ~ Exit status: 0, all methods/properties PASS throughout.
    • Valgrind memcheck on both daemons, at each checkpoint ~ 0 errors, 0 leaks throughout; reachable-memory figures traced via backtrace inspection at each step, confirmed to be pure libdbus-internal bookkeeping (not application code) whenever the byte count shifted between runs.
    • lainos-notifyd exercised with real traffic (a live Tor service restart, generating genuine sd_notify messages) while under Valgrind's supervision at the final checkpoint.
    • Functional D-Bus smoke test (CanReboot) confirmed correct throughout.
    • Binary hashes (md5sum) verified matching between the tested builds and the actual production binaries for both daemons, at the final checkpoint.

    This completes the filesystem-isolation work for both daemons. Combined with the seccomp hardening (5.5.3-24) and capability bounding-set clearing (5.5.3-25), protocol7-core's two most exposed daemons now have privilege drop, syscall restriction, capability restriction, and filesystem isolation all implemented and verified, matching the full breadth of systemd's per-service sandboxing model as hardened by Whonix without depending on systemd itself.

    Downloads
  • 5.5.3-25 ac1d375f8b

    amnesia released this 2026-07-17 12:25:51 +02:00 | 3 commits to main since this release

    protocol7-core-5.5.3-25 ~ Changelog

    Security Hardening

    • lainos-notifyd and lainos-dbus-bridge: explicitly cleared the Linux capability bounding set (CapBnd) during privilege drop, via a prctl(PR_CAPBSET_DROP, ...) loop over all capabilities, called before setuid() while CAP_SETPCAP is still held.

    Background: prompted by an external review of the security analysis document, which asked a precise, fair question this document had not previously answered: does the privilege drop clear Linux capabilities, or only UID/GID? Rather than answer from kernel-semantics reasoning alone, this was checked directly via /proc/<pid>/status on the live, running daemons.

    Finding: setuid()'s standard kernel behavior already cleared the effective and permitted capability sets (CapEff/CapPrm both confirmed zero, directly, before any change was made). The capability bounding set (CapBnd) is a separate mechanism, not cleared by setuid() alone, and still showed the full root bounding set ~ a ceiling on capabilities the process could regain, for example via a later execve of a file with capabilities set. Not a fix for a live vulnerability (effective/permitted capabilities were already correctly zero), but a real, previously-unverified residual, closed as defense-in-depth.

    Fix: added a prctl(PR_CAPBSET_DROP, cap, 0, 0, 0) loop (0 through CAP_LAST_CAP) to both daemons' drop_privileges(), positioned before the existing setgid()/setuid() calls. Requires <sys/prctl.h> and <linux/capability.h>, added to both files.

    Verification:

    • Re-checked via /proc/<pid>/status post-fix on both daemons ~ all five capability fields (CapInh, CapPrm, CapEff, CapBnd, CapAmb) now read zero, confirmed on the actual running production processes, not just a test build.
    • Full dfuzzer 2.6 regression pass on lainos-dbus-bridge ~ Exit status: 0, all methods/properties PASS; functional D-Bus smoke test (CanReboot) confirmed correct.
    • Valgrind memcheck on both daemons (seccomp-bypass debug-build methodology, matching established practice) ~ 0 errors, 0 leaks; reachable-memory baselines match prior releases exactly (19,837 bytes/79 blocks for dbus-bridge; consistent figures for notifyd under real exercised traffic from a live Tor service restart) ~ confirming the capability change introduced no functional or memory-safety regression.
    • Binary hashes (md5sum) re-confirmed matching between the tested build and the actual production binaries running on the system.

    Note on process: this is the second real finding in this project surfaced by someone other than the primary author reviewing the security documentation itself, rather than the code directly ~ the security analysis document's own precision and willingness to state open questions plainly is what made this question askable in the first place.

    Downloads
  • 5.5.3-24 34617b4a06

    amnesia released this 2026-07-15 04:38:10 +02:00 | 4 commits to main since this release

    protocol7-core-5.5.3-24 ~ Changelog

    Note: The three hardening techniques addressed in this release ~ restricting allowed socket address families, preventing ASLR-personality changes, and restricting namespace creation ~ were prompted by reviewing how Whonix/Kicksecure hardens its own systemd services (RestrictAddressFamilies, LockPersonality, RestrictNamespaces), and manually reimplementing those same protections in protocol7-core's daemons via libseccomp, since there's no systemd here to enforce them through a config directive.


    Security Hardening (lainos-dbus-bridge)

    • Added a redundant, defense-in-depth caller-identity check to Reboot/PowerOff D-Bus methods via manual, logic-focused code review (conducted 2026-07-09, a category of review distinct from fuzzing/memory-safety testing). The D-Bus system bus's default <policy context="default"> allows send_destination from any local process with no UID restriction, so this check adds an explicit authorization layer at the application level. In practice, this was never exploitable: the daemon runs as nobody after privilege drop, and openrc-shutdown itself refuses to run without root regardless of caller ~ so the missing check never allowed an actual reboot/poweroff. The fix closes the gap at an earlier point in the call chain rather than relying solely on that downstream protection.
    • Fix: added an explicit caller-UID check before the fork, using dbus_bus_get_unix_user() to resolve the sender's real UID via the trusted bus daemon, rejecting any caller that is neither the active session user nor root with a proper org.freedesktop.DBus.Error.AccessDenied reply.
    • Seccomp interaction: dbus_bus_get_unix_user() requires libdbus's internal blocking-call machinery, which invokes clone(). Added clone to the seccomp whitelist, scoped via SCMP_CMP_MASKED_EQ to the specific CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD flag pattern libdbus's worker-thread creation uses, rather than an unconditional allow.
    • Scope note: this review covered lainos-dbus-bridge only. Reboot/PowerOff are the only two Manager-interface methods with real, irreversible system-level effects; all other action methods either have no real effect (Suspend is a documented no-op, Hibernate/HybridSleep return NotSupported) or are read-only. Session/User-interface methods (Lock, Unlock, Terminate, etc.) are not implemented in handle_method() at all and correctly return UnknownInterface.

    Security Hardening (lainos-notifyd and lainos-dbus-bridge)

    • Restricted socket() to AF_UNIX only, via seccomp argument filtering (SCMP_CMP(0, SCMP_CMP_EQ, AF_UNIX)). Both daemons only ever communicate over Unix domain sockets (/run/systemd/notify for notifyd, the D-Bus system bus for dbus-bridge); this closes off AF_INET/AF_INET6/AF_NETLINK/raw sockets and any other address family the syscall would otherwise permit. Equivalent to systemd's RestrictAddressFamilies=AF_UNIX, as hardened by Whonix.
    • Added the missing #include <sys/socket.h> to lainos-dbus-bridge.c, required for the AF_UNIX constant used by the new restriction (notifyd already had it via an existing include).

    Security Review (no code changes required)

    • Confirmed LockPersonality and RestrictNamespaces equivalents, as hardened by Whonix, are already effectively enforced in both daemons under the existing default-deny (SCMP_ACT_KILL) seccomp policy: personality(), unshare(), and setns() were never present in either daemon's syscall whitelist, meaning they were already fully blocked. The one clone() allowance (added for the authorization fix above) remains narrowly scoped, not general namespace creation.

    Verification

    • lainos-dbus-bridge: full dfuzzer 2.6 regression pass ~ Exit status: 0, all methods/properties PASS, real (non-bypassed) seccomp filter active. Valgrind memcheck ~ 0 errors, 0 leaks (definite/indirect/possible); 19,837 bytes still reachable in 79 blocks, consistent with the pre-fix baseline (no new leak). Manual allow-path and deny-path authorization tests both passed correctly. Functional D-Bus smoke test (CanReboot) confirmed working.
    • lainos-notifyd: Valgrind memcheck under real, exercised traffic (Tor daemon restart, generating genuine sd_notify messages) ~ 0 errors, 0 leaks, daemon survived without crash.
    • Both daemons compile cleanly against the new restrictions (only the pre-existing, previously-triaged unused variable 'path' warning in lainos-dbus-bridge.c, unrelated to this change).

    Security Verification (lainos-init)

    • Previously verified only by manual code review, not automated testing. lainos-init is one-shot (single execution per login, called by greetd), but unlike lainos-ghost-units (a genuinely input-free daemon), it reads real, environment-controlled input (P7_CMD, P7_COMPOSITOR, P7_SESSION_TYPE) and uses it to decide what binary to execlp() ~ a real attack surface that "one-shot" alone does not make safe.

    • Built a libFuzzer harness (fuzz_compositor_is_safe.c) against the actual, unmodified compositor_is_safe() function ~ the gatekeeper deciding whether an environment-supplied compositor value is allowed to reach execlp(). Ran 7,332,678 executions under AddressSanitizer with an RSS-limited, time-boxed configuration ~ zero crashes, zero memory-safety findings.

    • Manually tested a set of semantically adversarial inputs the coverage-guided fuzzer is not guaranteed to prioritize: path traversal (../../../etc/passwd), command-injection-style payloads (sway; rm -rf /), shell substitution (sway$(whoami)), a raw shell path (/bin/sh), and flag-injection-style input (-e). All correctly rejected with no crash.

    • Built a second libFuzzer harness (fuzz_path_construction.c) verifying the exact snprintf-based overflow-detection pattern used for XDG_RUNTIME_DIR/DBUS_SESSION_BUS_ADDRESS path construction, with an explicit invariant check (abort() on any case where the overflow check reports success but the resulting buffer is actually out-of-bounds or non-terminated). Ran 1,769,984 executions ~ zero crashes, invariant held in all cases.

    • preserve_critical_env() reviewed and confirmed to have no fuzzable logic of its own (unconditional getenv/setenv passthrough, no parsing or branching on value content); any risk from preserved variable values lies in downstream consumers outside protocol7-core's scope, not in this function.

    • This confirms compositor_is_safe()'s whitelist-or-verified-absolute-path validation and the path-construction overflow checks both hold under sustained fuzzing and targeted adversarial testing, closing the gap between "reviewed by inspection" and "verified by testing" for this daemon's real external-input paths.

    Known gap, unchanged: lainos-ghost-units confirmed to take no external input at all (fixed array of 8 literal paths, no arguments/environment/file parsing), and is not prioritized for fuzzing on that basis.

    Downloads
  • v5.5.3-22 4d79577cc2

    v5.5.3-22 Stable

    amnesia released this 2026-07-09 22:25:34 +02:00 | 5 commits to main since this release

    protocol7-core-5.5.3-22

    Security Hardening

    • lainos-notifyd: Replaced strncpy with snprintf for the addr.sun_path assignment. Not an exploitable issue as written (fixed-length string literal into a pre-zeroed buffer, well within bounds) ~ flagged by semgrep's insecure-use-string-copy-fn rule as a false positive, hardened anyway for defense-in-depth and reviewer clarity.

    Security Verification

    • Valgrind memcheck run against lainos-notifyd and lainos-dbus-bridge (via dedicated, non-shipped debug builds with seccomp temporarily disabled through an env-var guard) ~ 0 errors, 0 leaks (definite/indirect/possible) on both, independently cross-validating existing ASAN/dfuzzer/libFuzzer results.
    • cppcheck + semgrep static analysis run against all four daemons (lainos-dbus-bridge.c, lainos-notifyd.c, lainos-init.c, lainos-ghost-units.c) ~ zero errors, zero real security findings. One semgrep finding investigated and confirmed a false positive (see above). A handful of cosmetic style notes (const-correctness, one dead variable in lainos-init.c, one dead variable in lainos-dbus-bridge.c already known from compiler warnings) ~ no functional or security impact.

    Documentation

    • Security analysis document (protocol7-core-security-analysis.md) updated to include Valgrind and static analysis results.
    Downloads
  • v5.5.3-21 f0ff30dab7

    v5.5.3-21 Stable

    amnesia released this 2026-07-05 18:49:46 +02:00 | 11 commits to main since this release

    protocol7-core 5.5.3-21 (2026-07-05)

    lainos-ghost-units ~ syslog-ng ordering fix

    Root cause: lainos-ghost-units creates /run/systemd/system as part of its
    AUR-compatibility ghost directories. syslog-ng's system() source macro calls
    sd_booted() which checks for /run/systemd/system at parse time. If ghost-units
    ran before syslog-ng started, syslog-ng assumed it was on a real systemd host and
    expanded system() to systemd-journal() instead of unix-dgram("/dev/log").
    No journald exists on Protocol 7, so /dev/log was never created and all syslog
    calls silently went nowhere.

    Fix: Added after syslog-ng to lainos-ghost-units.initd depend() block.
    Since both services are in the boot runlevel, OpenRC has no implicit ordering
    between them -- this constraint was missing. after (not need) enforces ordering
    only when syslog-ng is going to start anyway, without forcing it as a hard dependency.

    Verification:

    • /dev/log confirmed created by syslog-ng before ghost-units runs
    • logger -p daemon.info confirmed reaching /var/log/daemon.log
    • lainos-notifyd messages confirmed appearing in daemon.log with sender uid/pid

    lainos-notifyd v4.6 and v4.7

    v4.6 ~ security/audit pass:

    • FIX: removed SA_RESTART from SIGTERM/SIGINT handlers ~ recv() was transparently
      restarted by the kernel after a signal, so shutdown_flag was never checked until
      another datagram arrived. Daemon would hang on SIGTERM indefinitely if idle.
    • ADDED: switched recv() to recvmsg() with SO_PASSCRED so each message is logged
      with the sender's real uid/pid (SCM_CREDENTIALS) instead of trusting
      unauthenticated content blindly
    • ADDED: MSG_TRUNC detection ~ oversized datagrams logged as truncated instead of
      silently clipped
    • ADDED: control-character sanitization on logged message bodies to prevent
      syslog log-line injection via embedded newlines/escapes

    v4.7 ~ shutdown unlink fix:

    • FIX: removed shutdown-time unlink() of the socket path. After drop_privileges(),
      the process runs as nobody but /run/systemd is root-owned 0755 ~ unlink()
      requires write permission on the containing directory, not the file. This call
      always failed silently (return value was never checked). Startup-time unlink()
      (run as root, before bind()) already handles stale-socket cleanup. Removed
      unlink/unlinkat from seccomp whitelist accordingly, tightening the filter.

    Fuzz Testing (lainos-notifyd)

    libFuzzer harness (prior session):

    • Standalone harness targeting parse_and_log() and sanitize_for_log() in isolation
    • Compiled with clang -fsanitize=fuzzer,address,undefined
    • Seeded with every message-type prefix, boundary lengths, and injection attempts
    • 2,000,000 iterations ~ zero crashes

    Manual fuzz script (2026-07-05):

    • Inputs: empty string, READY=1, 65536-byte payload, null bytes, 4096-byte STATUS,
      MAINPID=0, MAINPID=99999999, WATCHDOG=1, 1024 bytes random binary, embedded newline injection
    • notifyd status: started (survived all inputs)
    • MSG_TRUNC correctly fired on oversized payloads (over 8191 bytes, dropping)
    • Log injection sanitized ~ embedded newlines replaced with dots in daemon.log
    • All message types logged correctly with sender uid/pid attribution via SCM_CREDENTIALS
    • READY=1 confirmed: [notify uid=1000 pid=9454] READY=1 (service ready)

    Structural changes

    • lainos-net-init removed ~ dead code, no callers
    • lainos-audio-init removed ~ now a separate optional package (lainos-audio-init)
      in protocol_7_repo; lainos-init calls it via double-fork if present, fails
      gracefully if not installed
    • lainos-init reverted to v4.6 ~ single responsibility (session init only,
      no audio orchestration)

    ISO changes

    • syslog-ng daemon facility enabled in overlay (was commented out)
    • notify group added to usermod in shellprocess-final ~ required for notifyd
      socket access without doas
    • lainos-notifyd runlevel corrected to default (depend() requires dbus which
      is not available in sysinit)
    Downloads
  • v5.5.3-19 18f5f2af84

    v5.5.3-19 Stable

    amnesia released this 2026-07-05 01:23:04 +02:00 | 12 commits to main since this release

    protocol7-core 5.5.3-19 (2026-07-04)

    Current state (as of 5.5.3-19)

    • lainos-notifyd: v4.7, fuzzed clean and verified end-to-end. Runlevel placement
      (sysinit-only) confirmed durable across a clean rebuild/reinstall/reboot cycle. No
      known issues.
    • seatd: confirmed default-only, matching documented architecture.
    • lainos-ghost-units: audited, no code changes needed. Boot-ordering fix shipped
      and confirmed working across reboot.
    • lainos-audio-init / lainos-init: patched and compile-checked. NOT yet verified
      end-to-end on a real login cycle ~ pending test.
    • lainos-net-init: removed from the package. Still outstanding: doc corrections in
      project-status.md, iteration-log.md, and new-master-reference.md still describe it
      as active/present.
    • net.ipv4.tcp_timestamps: recommended change to 0 for consistency with existing
      anti-fingerprinting posture ~ NOT yet applied to
      /etc/sysctl.d/99-lainos-hardening.conf.
    • lainos-dbus-bridge: unchanged this session ~ already fuzz tested clean in a
      prior session (dfuzzer 2.6 + ASan against the full org.freedesktop.login1
      interface, see 5.5.3-12 entry).

    Fixes

    protocol7-core.install ~ fixed incomplete lainos-notifyd runlevel correction from -18

    Root cause: The -18 fix added lainos-notifyd to sysinit correctly, but rc-update add
    is purely additive ~ it never removed the stale default entry left over from earlier
    manual/script-added state. The duplicate persisted through -18 despite the fix looking
    correct in isolation.
    Fix: Added an explicit rc-update del "$svc" default before the rc-update add "$svc" sysinit call in the lainos-notifyd case branch, so the script self-corrects regardless
    of prior state on every future upgrade, not just this one.

    Verification

    • Clean reboot after rebuild/reinstall: rc-update show confirms lainos-notifyd is
      sysinit-only, seatd is default-only
    • Both services started correctly with no manual rc-update intervention required
    • /dev/log confirmed present and receiving log lines with no manual fix needed
    • lainos-notifyd confirmed running v4.7 (strings check against installed binary)

    protocol7-core 5.5.3-18 (2026-07-04)

    protocol7-core.install ~ lainos-notifyd runlevel misassignment

    Root cause: post_install()'s runlevel-registration loop unconditionally routed
    lainos-notifyd through the generic case fallthrough, which adds every non-boot
    service to default. This directly contradicts notifyd's own depend() (before seatd)
    once seatd is correctly single-homed in default ~ placing notifyd in default too gives
    no ordering guarantee against seatd within the same runlevel.
    Fix: Added an explicit lainos-notifyd case routing it to sysinit instead of the
    generic fallthrough. (Incomplete as shipped ~ see -19: didn't clean up the pre-existing
    stale default entry, so the duplicate persisted until -19.)

    seatd ~ reverted incorrect manual runlevel change

    Earlier in the session, seatd had been removed from default and left boot-only, based
    on reading its depend() block alone without checking the project's documented runlevel
    architecture (session handoff doc), which places seatd in default. Restored to
    default-only.


    protocol7-core 5.5.3-17 (2026-07-04)

    lainos-ghost-units.initd ~ shipped in a real pkgrel

    Rebuild to ship the after syslog-ng depend() fix written into -16's source tree but
    not yet built/deployed as its own pkgrel. No other changes.
    Root cause (originally found and fixed in source at -16): ghost-units creating
    /run/systemd/system before syslog-ng starts causes syslog-ng's sd_booted() check to
    falsely detect a systemd host and skip binding /dev/log entirely ~ breaking all system
    logging, not just notifyd's.


    protocol7-core 5.5.3-16 (2026-07-04)

    lainos-net-init ~ removed

    Dropped from source=, build(), and package() in the PKGBUILD. Confirmed dead code:
    never invoked by any init script, udev rule, or session hook across every prior
    version in the repo history. Sysctl hardening it attempted is fully covered, and more
    completely, by /etc/sysctl.d/99-lainos-hardening.conf via OpenRC's native sysctl
    service.

    lainos-audio-init ~ stale-socket race in PipeWire readiness check

    Root cause: wait_for_socket() only checked that a filesystem node existed at the
    expected socket path, not that it was live. A socket file left behind by a crashed
    prior instance would report "ready" instantly, before the newly-spawned pipewire had
    bound anything, causing wireplumber to launch against a dead socket.
    Fix: unlink any stale socket file before spawning pipewire, so existence
    afterward genuinely means the new instance created it. Bumped to v4.5.

    lainos-init ~ wired up lainos-audio-init for the first time

    Root cause: lainos-audio-init has been built and shipped since v4.4 but nothing
    on the system ever invoked it. lainos-init only sets environment and execs the
    compositor; no fork ever happened before that exec. Sessions were running on bare
    ALSA with no PipeWire/WirePlumber/Pulse-compat layer at all.
    Fix: Added a double-fork launcher (launch_audio_init()) called before both
    compositor execlp() calls (X11 and Wayland branches), so the launcher doesn't zombie
    under whatever process ends up in lainos-init's PID after exec. Bumped to v4.7.


    protocol7-core 5.5.3-15 (2026-07-04)

    Package integrity

    Bumped provides= to systemd=1:999 (epoch 1) for permanent systemd>=X dependency
    satisfaction regardless of upstream version.

    lainos-ghost-units.initd ~ added to package

    Confirmed tracked in source= / package() as of this rebuild.


    protocol7-core 5.5.3-14 (2026-07-04)

    Root cause: After drop_privileges(), the process runs as nobody, but /run/systemd
    is root-owned 0755 ~ unlink() there always failed on permissions. The v4.6 seccomp fix
    (below) only stopped the process being killed by SIGSYS; it didn't fix the underlying
    EACCES, which was silently ignored since the return value was never checked.
    Fix: Removed the shutdown-time unlink() entirely. Startup-time unlink() (root,
    before bind()) already handles stale-socket cleanup on next start, making the
    shutdown-time call both broken and redundant. Removed unlink/unlinkat from seccomp
    accordingly, tightening the filter back down.


    protocol7-core 5.5.3-13 (2026-07-04)

    lainos-notifyd v4.6 ~ signal handling, seccomp, and message-handling hardening

    Root cause 1 (shutdown hang): SA_RESTART on the SIGTERM/SIGINT handlers meant
    recv() was transparently restarted by the kernel after a signal, so shutdown_flag was
    never checked until another datagram arrived. The daemon could hang indefinitely on
    stop if no more messages came in.
    Fix: Removed SA_RESTART from the signal handlers.
    Root cause 2 (seccomp kill on shutdown): The shutdown path called unlink() on the
    socket after the seccomp filter (default SCMP_ACT_KILL) was loaded, killing the
    process with SIGSYS instead of a clean exit.
    Fix: Added unlink/unlinkat to the seccomp whitelist. (Superseded at -14 once the
    actual permissions root cause was found ~ see above.)

    Added

    • Switched recv() to recvmsg() with SO_PASSCRED ~ every logged message now carries
      the sender's real uid/pid (SCM_CREDENTIALS) instead of trusting unauthenticated
      message content
    • MSG_TRUNC detection ~ oversized datagrams are logged as truncated and dropped
      instead of silently clipped
    • Control-character sanitization on logged message bodies, closing a syslog
      log-line injection path via embedded newlines

    Verification

    • parse_and_log()/sanitize_for_log() (the message-parsing path handling untrusted
      external bytes) fuzzed with a libFuzzer harness (clang-18, ASan+UBSan), seeded with
      every message-type prefix, boundary lengths, empty input, and a log-injection
      attempt
    • 2,000,000 executions, coverage plateaued (fully explored), zero crashes, zero leaks
    • This code path is unchanged in v4.7 (-14), so the result still applies ~ the -14
      fixes were control-flow bugs (signal handling, permissions), not parsing bugs,
      which is why fuzzing wouldn't have caught them and doesn't need re-running
    • test-notifyd.sh: all checks passing on live hardware (clean shutdown timing,
      socket lifecycle, credential attribution, truncation handling, log-injection
      sanitization)

    Downloads
  • v5.5.3-12 be643d375c

    v5.5.3-12 Stable

    amnesia released this 2026-07-04 04:21:19 +02:00 | 18 commits to main since this release

    protocol7-core 5.5.3-12 (2026-07-03)

    lainos-dbus-bridge ~ dfuzzer audit, AddressSanitizer, and hardening

    Root cause: Systematic pointer type bug throughout the file ~ char[] and char[N]
    variables were being passed as &var to libdbus functions (dbus_message_append_args,
    dbus_message_iter_append_basic) which expect const char **. This caused libdbus to
    abort internally when called with certain inputs, crashing the bridge.

    Crashes found and fixed via dfuzzer:

    • GetSession ~ crashed on malformed string input; fixed by returning hardcoded
      /org/freedesktop/login1/session/1 (correct for single-user system)
    • GetSeat ~ same crash, same fix; returns hardcoded /org/freedesktop/login1/seat/seat0
    • GetUser ~ crashed when called with UID 0; fixed by ignoring caller-supplied UID
      and returning path based on runtime_uid (dynamically detected at startup)
    • ListSessions ~ crashed due to char[] pointer type bug on sid, uname, seat variables
    • ListSeats ~ same pointer type bug on seat variable
    • ListUsers ~ same pointer type bug on uname variable
    • RuntimePath property ~ crashed due to &runtime_path passed directly to libdbus;
      fixed by assigning to const char *rp = runtime_path before passing &rp

    Additional fixes from 5.5.3-5:

    • Removed setuid/setgid/setresuid/setresgid/setgroups from seccomp whitelist
    • Added execve and wait4 to seccomp whitelist (fixes Reboot/PowerOff D-Bus path)
    • Replaced atoi with strtoul in /etc/passwd UID parsing
    • Removed debug fprintf calls from main()

    Risk Context

    The vulnerability was real but the practical danger was low for several compounding reasons:

    Access requirements: To trigger the crash, an attacker needs either a malicious package installed with user consent, or an existing shell running as the logged-in user. Neither is a remote attack vector. An attacker who already has user-level code execution has far more direct ways to cause damage than crashing a session daemon.

    No session interruption: When lainos-dbus-bridge crashes, the desktop session continues running normally. Sway, greetd, and all running applications are unaffected. Only new applications launched after the crash that query logind for the first time would see an issue, and most fail gracefully. The user's work is not interrupted.

    Instant recovery: doas rc-service lainos-dbus-bridge restart fully restores the bridge. No reboot required.

    No escalation path: The bridge runs as nobody with a tight seccomp filter. Even if the crash were somehow turned into code execution rather than a simple abort, the attacker lands in a nobody context with a restricted syscall surface ~ nothing they didn't already have as the logged-in user. There is no privilege escalation path through the bridge.

    The fix still matters: Correct software handles bad input gracefully. The systematic pointer type bug could theoretically have had more serious consequences in untested code paths. And the testing process that found it demonstrates that Protocol 7 takes correctness seriously ~ the code that ships has been put through a real testing process, not just written and deployed.

    Verification

    • dfuzzer 2.6 run against org.freedesktop.login1 ~ Exit status: 0
    • AddressSanitizer + dfuzzer run ~ Exit status: 0, no memory errors detected
    • All methods and properties PASS across Manager, Session, and User interfaces
    • Reboot and PowerOff skipped as destructive (confirmed working ~ Reboot via D-Bus
      caused actual system reboot during testing, confirming the execve fix works)
    • Bridge confirmed running as nobody after reboot
    • seccomp filter confirmed active (/proc//status unreadable even with doas)
    • System boots and desktop session starts correctly after package install
    Downloads
  • v5.5.3-5 ef7c0b1f80

    v5.5.3-5 Stable

    amnesia released this 2026-07-04 02:53:19 +02:00 | 19 commits to main since this release

    protocol7-core 5.5.3-5 (2026-07-03)

    lainos-dbus-bridge security fixes

    • Removed setuid, setgid, setreuid, setregid, setresuid, setresgid, setgroups
      from seccomp whitelist ~ privilege drop completes before seccomp is applied
      so these syscalls serve no purpose post-drop and were unnecessary attack surface
    • Added execve and wait4 to seccomp whitelist ~ fixes silently broken
      Reboot/PowerOff D-Bus methods (fork/exec path was blocked by seccomp)
    • Replaced atoi with strtoul + error checking in /etc/passwd UID parsing ~
      prevents silent fallback to UID 0 on malformed entries
    • Removed all debug fprintf calls from main() ~ 11 lines of stderr output
      on every startup that were never cleaned up from development

    Verification

    • Daemon confirmed running as nobody after reboot:
      ps -o user,pid,comm -p $(pgrep -f lainos-dbus-bridge) returned USER=nobody
    • seccomp filter confirmed active:
      /proc/<pid>/status unreadable even with doas ~ seccomp blocking the read
    • D-Bus query methods confirmed working:
      CanReboot, CanPowerOff, CanSuspend all return "yes" via dbus-send
    • System boots and desktop session starts correctly after package install
    • No regressions observed in wlogout, polkit, seatd, or greetd
    Downloads
  • v5.5.3 b61a9c03a2

    amnesia released this 2026-06-26 01:36:00 +02:00 | 26 commits to main since this release

    Protocol 7 Core ~ Changelog

    v5.5.3 (2026-06-25)

    protocol7-core 5.5.3-1 ~ lainos-dbus-bridge init script fix

    Fixed incorrect PID file path causing false "crashed" status in rc-status.

    Problem:
    OpenRC tracked the service PID at /run/lainos-dbus-bridge.pid, but the daemon wrote its PID file to /run/openrc/lainos-dbus-bridge.pid. This mismatch caused rc-status to report [ crashed ] even though the daemon was running correctly. Steam and other D-Bus clients would fail to launch until a manual rc-service lainos-dbus-bridge restart.

    Fix:

    • Changed pidfile from /run/lainos-dbus-bridge.pid to /run/openrc/lainos-dbus-bridge.pid
    • Increased start_stop_daemon_args --wait from 100 to 500 ms, giving the daemon adequate time to fork and write its PID before OpenRC checks status

    Result:
    lainos-dbus-bridge now reports [ started ] correctly after boot. No manual restart required. Steam and other logind-dependent applications launch normally.

    Downloads
  • v5.5 2c37d59539

    amnesia released this 2026-06-25 00:50:51 +02:00 | 30 commits to main since this release

    Protocol 7 Core ~ Changelog

    v5.5 (2026-06-24)

    v5.5 is a hardening and reliability release. Here's what changed and why it matters:

    Security

    Binary hardening flags — every compiled binary now builds with -pie -fstack-protector-strong -D_FORTIFY_SOURCE=2. In v5.4 the binaries had no stack protection or position-independent hardening, making them easier targets for memory corruption exploits.

    Root check re-hardened in lainos-init — v5.4 had the root check removed with a comment saying it "prevents login loop." v5.5 hard-fails on root unless P7_ALLOW_ROOT=1 is explicitly set, closing a silent path to root sessions if greetd/PAM misconfigures.

    P7_CMD compositor whitelist — v5.4 exec'd whatever P7_CMD contained without validation. v5.5 validates against a known-safe compositor list before exec, blocking arbitrary command injection through the environment.

    drop_privileges() fallback — v5.4 silently returned without dropping privileges if the nobody user didn't exist. v5.5 falls back to UID/GID 65534 unconditionally.

    Ghost directory permissions07550750. These are placeholder dirs; world-execute was unnecessarily permissive.

    Reliability

    D-Bus socket readiness — v5.4 used a fixed 500ms blind wait. v5.5 polls /run/dbus/system_bus_socket every 100ms up to 3 seconds, connecting the moment the socket is ready. Eliminates the timing race on any hardware.

    Audio zombie fix — v5.4's spawn_daemon() never called waitpid() on the intermediate child, creating a zombie process on every audio daemon spawn. v5.5 reaps it immediately.

    Signal handling in lainos-notifyd — v5.4 had no signal handler, so SIGTERM killed the process mid-recv() without cleanup, leaving the socket file behind. v5.5 handles SIGTERM/SIGINT cleanly and unlinks the socket on exit.

    Dynamic wireless interface detection — v5.4 hardcoded wlan0. v5.5 enumerates /sys/class/net/ and checks uevent for DEVTYPE=wlan, working correctly on any hardware regardless of interface naming.

    Cleanliness

    Debug artifacts removed — v5.4 wrote /tmp/lainos-init.debug on every single login and had fprintf(stderr) debug calls throughout lainos-dbus-bridge. Both removed in v5.5.

    ldconfig removed from install hook — leftover from when the mock shared libraries existed. No-op in v5.4, removed in v5.5.

    rc-update del in pre_remove — v5.4 only stopped services on removal, leaving stale runlevel symlinks behind. v5.5 deregisters them properly.

    makedepends corrected — v5.4 listed dbus and libseccomp in both depends and makedepends. v5.5 keeps them only in depends where they belong as runtime dependencies.

    Downloads