Summary
crates/kernel/src/syscalls/fs_ops.rs (commit 5680ed4, on branch busybox-init) sets the console TTY's foreground process group to the opener's pgid on every open(\"/dev/console\"):
```rust
let descriptor = if let Some(dev) = node.char_device()
&& crate::io::uart::is_console_char_device(&dev)
{
let caller_pgid = self.current_process.with_lock(|p| p.pgid());
crate::io::tty_device::console_tty()
.lock()
.set_fg_pgid(caller_pgid);
FileDescriptor::Tty(crate::io::tty_device::console_tty().clone())
} else {
...
};
```
This was introduced while swapping Solaya's Rust init for busybox init. Busybox's `console::respawn` child calls `setsid()` before opening `/dev/console`, so dash's job-control startup saw `fg_pgid != getpgrp()` and self-stopped via SIGTTIN. The hack unblocked the bring-up but is coarser than the correct fix.
Problem
Any process — not just a session leader, not just root — can open `/dev/console` and silently take over the foreground process group. The currently-interactive shell then receives SIGTTIN/SIGTTOU on its next read/write and stops. There is no permission check, no ownership check, and no requirement that the opener be a session leader without a controlling tty.
Correct fix
Implement `TIOCSCTTY` (ioctl on a tty fd) with the Linux semantics:
- Caller must be a session leader (`getsid(caller) == getpid(caller)`).
- Caller must not already have a controlling tty.
- The tty must not already be the controlling tty of another session (unless the `force` arg is set and caller has CAP_SYS_ADMIN).
- On success, the tty becomes the caller's session's controlling tty and the foreground process group is set to the caller's process group.
Then remove the blanket `set_fg_pgid` from `do_openat`'s console branch. Busybox already calls `ioctl(fd, TIOCSCTTY, 0)` after opening `/dev/console`, so once TIOCSCTTY is implemented, the inittab flow keeps working without the hack.
Related
Mentioned in the existing deferred-work tracker #250 (item 5, TTY/UART decoupling) but deserves its own issue because it's a security-relevant regression, not just a refactor.
Created by Claude Code
Summary
crates/kernel/src/syscalls/fs_ops.rs(commit 5680ed4, on branchbusybox-init) sets the console TTY's foreground process group to the opener's pgid on everyopen(\"/dev/console\"):```rust
let descriptor = if let Some(dev) = node.char_device()
&& crate::io::uart::is_console_char_device(&dev)
{
let caller_pgid = self.current_process.with_lock(|p| p.pgid());
crate::io::tty_device::console_tty()
.lock()
.set_fg_pgid(caller_pgid);
FileDescriptor::Tty(crate::io::tty_device::console_tty().clone())
} else {
...
};
```
This was introduced while swapping Solaya's Rust init for busybox init. Busybox's `console::respawn` child calls `setsid()` before opening `/dev/console`, so dash's job-control startup saw `fg_pgid != getpgrp()` and self-stopped via SIGTTIN. The hack unblocked the bring-up but is coarser than the correct fix.
Problem
Any process — not just a session leader, not just root — can open `/dev/console` and silently take over the foreground process group. The currently-interactive shell then receives SIGTTIN/SIGTTOU on its next read/write and stops. There is no permission check, no ownership check, and no requirement that the opener be a session leader without a controlling tty.
Correct fix
Implement `TIOCSCTTY` (ioctl on a tty fd) with the Linux semantics:
Then remove the blanket `set_fg_pgid` from `do_openat`'s console branch. Busybox already calls `ioctl(fd, TIOCSCTTY, 0)` after opening `/dev/console`, so once TIOCSCTTY is implemented, the inittab flow keeps working without the hack.
Related
Mentioned in the existing deferred-work tracker #250 (item 5, TTY/UART decoupling) but deserves its own issue because it's a security-relevant regression, not just a refactor.
Created by Claude Code