Files
vphone-cli/Research/Guest/roothide_bootstrap_base.md
T
LakrandClaude Opus 5.5 0f9a949bd6 Fix RootHide /dev, sshd and sudo loader links (#519, #520)
#519: vphoned wrote jbroot/dev as the link text /rootfs/dev. The kernel
resolves link text, not vroot paths, so it dangled: every shell failed on
/dev/null and sshd never started. It is now /dev, an existing
/rootfs/dev link is replaced, and rootfs -> / is created.

#520: Irisin unpacks packages without RootHide dpkg's hook, so nothing
linked .jbroot in deeper package directories, and sudo could not load
libsudo_util from usr/libexec/sudo.
- vphoned walks the bootstrap for directories holding Mach-O files and
  links each one, at install, at startup, and one second after the root's
  Library/dpkg changes. That pass also runs the base steps that waited for
  pwd_mkdb or ssh-keygen, so sshd has host keys once openssh is installed.
- The spawn hooks follow the executable's LC_RPATH and LC_LOAD_DYLIB
  entries inside the root and link each dependency's directory, within a
  fixed bound. SystemHook does the same for TweakLoader before its dlopen.
- launchd starts xpcproxy and bootstrap daemons through posix_spawnp, which
  the launchd hook now interposes. SystemHook is chain-loaded into every
  child whatever its environment; DISABLE_TWEAKS and safe mode only keep
  ElleKit out.

The installer moves to Daemon/Bootstrap, with RootHide and rootless code
in their own folders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:26:03 +09:00

6.0 KiB

RootHide bootstrap base

Observation

Irisin's RootHide bootstrap unpacks packages but nothing builds the parts a jailbreak's bootstrap installer normally creates. On the 26.4 vphone (jbroot /var/containers/Bundle/Application/.jbroot-000114514191980C), none of them existed:

  • iGhostVT stayed on "Starting…". Its Ghostty config goes under /tmp, and var/tmp → ../tmp pointed at nothing. libghostty only logs the failed write.
  • Tools under vroot could not open /dev/null.
  • id root and id mobile failed with no such user: Invalid argument, while group lookups worked.
  • OpenSSH reset every connection before its banner: there were no host keys, and ssh-keygen could not create any because getpwuid(0) failed.

Procursus tools link libiosexec, whose ie_getpw* replace libc's user lookups. They read the Berkeley databases (/etc/pwd.db, and /etc/spwd.db for root) rather than the text files; icli's account set-password edits spwd.db for the same reason. This is inferred from the failure, not from libiosexec's source. Group lookups still read /etc/group. Running pwd_mkdb -p /etc/master.passwd under vroot fixed every user lookup.

A root/private/etc → ../etc link was tried and had no effect: libroothide does not wrap the getpw* family.

Ownership

vphoned creates what a bootstrap installer creates; Irisin keeps what packages and their maintainer scripts create (for example the update-alternatives links, which Irisin's Bootstrap Install runs). None of the items below is a package file, so reinstalling packages does not bring them back.

Implementation

GuestIrisinInstaller.ensureRootHideBase(root:) runs right after ensureRootHideLinks in the RootHide install path and in refreshBootstrapOnStartup. Paths are physical; root/x is /x under vroot.

Item Created as
root/tmp directory 1777, 0:0
root/var, root/etc directory 0755, 0:0, when missing
root/var/root directory 0700, 0:0
root/var/tmp link ../tmp
root/dev link /dev; an existing link whose text is exactly /rootfs/dev is replaced
root/rootfs link /, the vroot bridge to the real root
root/etc/passwd, root/etc/group copies of /private/etc/…, 0644, 0:0
root/etc/master.passwd copy of /private/etc/master.passwd, created 0600, 0:0
root/etc/pwd.db, root/etc/spwd.db root/usr/sbin/pwd_mkdb -p /etc/master.passwd, then 0644 and 0600; checked with root/usr/bin/id root when present
root/etc/ssh/ssh_host_*_key root/usr/bin/ssh-keygen -A, after the databases

Link text is read by the kernel, not by vroot. vroot shows the real root as /rootfs, but a link stored as /rootfs/dev resolves to the physical /rootfs/dev, which does not exist. vphoned wrote exactly that before #519, and nothing created root/rootfs either. Every shell then printed /dev/null: Directory nonexistent and sshd never started. On 26.6.2 in iGhostVT, a fresh tab still printed the error with dev → /rootfs/dev plus rootfs → /, and stopped printing it with dev → /dev. Irisin writes package links the same way: its linkText turns /rootfs/x into /x.

Rules:

  • A missing item is created; an existing item is left alone whatever its type, owner or mode. A root/tmp made by hand keeps its owner. The one exception is the dangling dev → /rootfs/dev earlier vphoned wrote.
  • The databases are rebuilt only when either is missing or older than master.passwd, so a password changed with passwd survives a restart.
  • Host keys are generated only when root/etc/ssh exists (openssh is installed) and a default key is missing. -A never replaces a key.
  • vphoned runs pwd_mkdb, id and ssh-keygen by physical path. Each loads libroothide through the .jbroot link ensureRootHideLinks seeds beside it, so its arguments are vroot paths.
  • On a first install the bootstrap has no pwd_mkdb or ssh-keygen yet. Those steps are reported under deferred in the install result. They run one second after the next package operation rewrites the root's Library/dpkg, or on the next vphoned start, so sshd has host keys as soon as openssh is installed.
  • Any other failure names the path. At install it rolls the install back like the other RootHide steps; at startup it is logged.

Open issues

  • PAM. With accounts and keys in place, the bootstrap's sshd (OpenSSH 9.2, UsePAM yes) failed with PAM: initialisation failed, although root/etc/pam.d/sshd and every module it names exist; with -o UsePAM=no, password login worked. The modules in root/usr/lib/pam load libroothide through @loader_path/.jbroot, and that directory had no link. ensureRootHideLinks now seeds usr/lib/pam/.jbroot → ../../../.jbroot (#515). On an existing RootHide VM, startup recreated the link and root SSH worked with public-key and password authentication. A fresh restore with the package's unmodified sshd_config has not been checked yet. vphoned never edits sshd_config, the package's conffile.
  • Alternatives links such as /usr/bin/pager only exist when maintainer scripts ran. Check whether Irisin's Bootstrap Install creates them on a fresh vphone; if it does not, that is an Irisin bug.

Validation

On a freshly restored vphone, with Irisin installed through vphoned, after Irisin's Bootstrap Install and one restart, without manual changes:

# under vroot, in iGhostVT or over ssh
ls -ld /tmp /var/tmp /var/root        # 1777 dir, link, 0700 dir
ls /dev /rootfs/private                # both list the real root's entries
: > /dev/null && echo dev-ok
id root && id mobile                  # both resolve
ls -l /etc/pwd.db /etc/spwd.db /etc/ssh/ssh_host_*_key
  • iGhostVT opens a shell.
  • root/usr/lib/pam/.jbroot resolves to the root, and from the Mac ssh mobile@<guest address> logs in with the package's sshd_config (UsePAM yes).
  • Change a password with passwd, restart, and confirm it still works and that the startup log reports nothing created.