Repository navigation
[FEATURE] Allocate container console PTY from container's private devpts instance to prevent host /dev/pts leakage` #369
Description
Problem Description
Currently, when Droidspaces initializes a container console (e.g., in src/daemon.c / src/terminal.c), the PTY pair is allocated from the host's mount namespace by opening /dev/ptmx on the host side before or during container startup.
On Android, this causes two noticeable issues:
- Host
/dev/ptsPollution & Detection Leakage:
A slave PTY node (e.g.,/dev/pts/2) is created under the host's/dev/ptsdirectory withuid=0 (root)and SELinux contextu:object_r:devpts:s0. On Android, normal apps (untrusted_app, UID >= 10000) own their respective PTYs, and ADB shell sessions belong toAID_SHELL (2000). A persistentuid=0PTY slave on the host's/dev/ptsnever occurs naturally on stock Android and immediately exposes the presence of a running root daemon/container to runtime environment detectors (flagged asAbnormal Permission: Found ROOT PTY). - Namespace Isolation & Security:
Sharing the host'sdevptsinstance with the container violates standard Linux container isolation practices and historically exposes containers to terminal injection boundary issues (e.g.,TIOCSTIconcerns if FDs are ever inherited across contexts).
Proposed Solution (Container-Private devpts)
Following industry-standard container runtimes (runc, LXC, systemd-nspawn), the console PTY should be allocated strictly from the container's own isolated devpts instance:
1. Mount a private devpts instance inside the container's Mount Namespace
Inside the container child process (after CLONE_NEWNS), mount a dedicated devpts with newinstance and bind-mount/symlink /dev/pts/ptmx to /dev/ptmx:
// 1. Mount isolated devpts instance (gid=5 is standard 'tty' group)
mount("devpts", "/dev/pts", "devpts", MS_NOSUID | MS_NOEXEC, "newinstance,ptmxmode=0666,mode=0620,gid=5");
// 2. Ensure /dev/ptmx points to the isolated instance for in-container apps (sshd, tmux, etc.)
mount("/dev/pts/ptmx", "/dev/ptmx", NULL, MS_BIND, NULL);
// Or symlink("/dev/pts/ptmx", "/dev/ptmx") after unlinking the old node2. Allocate the PTY from the private instance & obtain master_fd on the host
Note: To guarantee the kernel allocates from the new instance, we must open the ptmx node belonging to that specific devpts mount (/dev/pts/ptmx).
There are three clean ways for the host daemon (droidspacesd) to obtain the master_fd while keeping the slave node (/dev/pts/0) strictly inside the container:
-
Approach A (Simplest code change - Direct
/procpath traversal):
Once the child process finishes mounting its private/dev/pts(synchronized via a pipe/eventfd), the host daemon can directly open the child's privateptmxfrom the host side:snprintf(ptmx_path, sizeof(ptmx_path), "/proc/%d/root/dev/pts/ptmx", child_pid); int host_master_fd = open(ptmx_path, O_RDWR | O_NOCTTY | O_CLOEXEC);
Why this works: Opening
/dev/pts/ptmxthrough/proc/<pid>/rootresolves the dentry inside the container's mount namespace. The slave node/dev/pts/0is created exclusively inside the container, while the host daemon immediately gets themaster_fdwithout needing IPC FD-passing! -
Approach B (Industry Standard -
socketpair+SCM_RIGHTS):
The container child opens/dev/pts/ptmxlocally, sets up/dev/pts/0as its controlling terminal (setsid()+TIOCSCTTY), and sends themaster_fdback to the host daemon over a pre-establishedAF_UNIXsocketpair()usingSCM_RIGHTS. (100% kernel version compatibility). -
Approach C (Modern Syscall -
pidfd_getfdon Kernel 5.6+):
The child opens/dev/pts/ptmx, and the host daemon duplicates the descriptor usingpidfd_open()+pidfd_getfd().
Benefits
- Zero Host Pollution: The host's
/dev/ptsremains 100% clean and untainted—no root-owned slave nodes are ever created on the host. - Native Stealth: Completely eliminates
Found ROOT PTYdetections at the architectural root cause, without needing kernel-level LKM hooks or VFSgetattrspoofing. - Standards Compliance: Aligns Droidspaces with the official Linux kernel
devptsmulti-instance container specification.
Activity
[-][FEATURE/Security] Allocate container console PTY from container's private devpts instance to prevent host /dev/pts leakage`[/-][+][FEATURE] Allocate container console PTY from container's private devpts instance to prevent host /dev/pts leakage`[/+]fix(security): allocate the boot console from the container's devpts
ravindu644 commentedon Oct 23, 2026
thanks for the detailed writeup. you're right that there was a root-owned pty on the host, but the two halves you proposed were already in the tree:
the actual leak was the boot console. start opened the console pty on the host before forking the monitor and bind-mounted that host /dev/pts/N over the container's /dev/console before pivot_root, so every running container pinned a root-owned slave in the host devpts for its whole life. that's the old pre-4.0 LXC model.
fixed in 3b6759c on dev. init now allocates the console itself from the devpts it just mounted, binds it over /dev/console by fd and hands the master+slave pair up to the monitor over a socketpair, the way LXC 4+ does it. foreground start, internal reboots and hw_access mode all still work, tested on a 5.15 phone. an idle container now leaves nothing in the host's /dev/pts.
two things worth knowing:
will be in the next release.