fix: audit fix — avoid latent stdout pipe deadlock in run_command (v0.8.3)
Piping stdout without draining while blocking in child.wait() risks deadlock if a command writes more than one OS pipe buffer (~64 KB on Linux). Current callers (systemctl, niri msg, loginctl) stay well under that, but the structure was fragile. stdout is now discarded; stderr continues to be captured for error reporting.
This commit is contained in:
parent
a47fdff1dd
commit
13b5ac1704
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -616,7 +616,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "moonset"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"gdk-pixbuf",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "moonset"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
edition = "2024"
|
||||
description = "Wayland session power menu with GTK4 and Layer Shell"
|
||||
license = "MIT"
|
||||
|
||||
@ -2,6 +2,13 @@
|
||||
|
||||
Architectural and design decisions for Moonset, in reverse chronological order.
|
||||
|
||||
## 2026-04-24 – Audit fix: avoid latent stdout pipe deadlock in run_command (v0.8.3)
|
||||
|
||||
- **Who**: ClaudeCode, Dom
|
||||
- **Why**: Audit found that `run_command` piped the child's `stdout` but never drained it, then called blocking `child.wait()`. A child writing more than one OS pipe buffer (~64 KB on Linux) would block on `write()` while the parent blocked in `wait()` — classic pipe deadlock, broken only by the 30 s SIGKILL timeout. Current callers (`systemctl`, `niri msg`, `loginctl`) do not emit that much output, but the structure was fragile and would bite on any future command or changed behaviour.
|
||||
- **Tradeoffs**: stdout is now fully discarded. If a future caller needs stdout, it will have to drain it concurrently with `wait()` (separate reader thread).
|
||||
- **How**: Replace `.stdout(Stdio::piped())` with `.stdout(Stdio::null())` in `run_command`. `stderr` stays piped — it is drained after `wait()`, which is safe because `wait()` already reaped the child and no further writes can occur.
|
||||
|
||||
## 2026-03-31 – Fourth audit: release profile, GResource compression, lock stderr, sync markers
|
||||
|
||||
- **Who**: ClaudeCode, Dom
|
||||
|
||||
@ -40,7 +40,9 @@ fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(),
|
||||
log::debug!("Power action: {action} ({program} {args:?})");
|
||||
let mut child = Command::new(program)
|
||||
.args(args)
|
||||
.stdout(Stdio::piped())
|
||||
// stdout is discarded — piping without draining would deadlock if a
|
||||
// command ever wrote more than one OS pipe buffer before wait() returned.
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|e| PowerError::CommandFailed {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user