Device name pinning
The host can give a guest a fixed device name, the one Xcode's DeviceHub,
devicectl, Finder and the guest's own Settings show, and keep it from being
renamed inside the guest or from the host. The name is the VM's name:
vphone-vm hands it to vphoned after every connect, vphoned stores it in the
guest, and libdevicename.dylib in configd and lockdownd applies it.
Static analysis of the iOS 27.0 (24A435) configd and lockdownd from an
iPhone17,3 guest, 2026-10-07. Addresses are from those binaries. Run on an
iOS 27.0 guest; see Verification.
Where the name lives
The device name is System/System/ComputerName in
/private/var/preferences/SystemConfiguration/preferences.plist. Nothing reads
it from there directly. configd publishes it into the dynamic store, and every
reader goes through the store:
configd's updateConfiguration (PreferencesMonitor, statically linked into
/usr/libexec/configd; 0x100062098…) does the publishing:
SCDynamicStoreCopyMultiple(store, NULL, ["^Setup:.*"]): what the store has.SCPreferencesCopyKeyList, thenSCPreferencesGetValue(prefs, kSCPrefSystem)flattened from/intoSetup:/…keys (0x100064578), soSystem/SystembecomesSetup:/System. The current set is flattened the same way.- Keys whose value did not change are dropped; keys that disappeared are collected for removal.
- One
SCDynamicStoreSetMultiple(store, keysToSet, keysToRemove, NULL)(0x100062c04).
Renames come in through lockdownd's set_device_name (0x1000073b0):
SCPreferencesCreate("com.apple.mobile.lockdown"), SCPreferencesLock,
SCPreferencesSetComputerName(prefs, name, kCFStringEncodingUTF8),
SCPreferencesSetHostName and SCPreferencesSetLocalHostName with a sanitized
name, SCPreferencesCommitChanges, SCPreferencesApplyChanges. These are
lockdownd's only callers of the three setters. A failing setter is only
logged, and the function still commits. It has two callers:
- startup (
0x100011e4c…0x100011ec0): every time lockdownd starts it callsset_device_name(copy_device_name()), or withMarketingDeviceFamilyNamewhen there is no name, and records the name in its data ark (-DeviceName); - the lockdown
SetValue DeviceNamehandler (0x10001ed7c), which ignores the result. The host's rename (Finder,idevicename) arrives here.
The channel
The name is DeviceName in /var/db/vphone/devicename.plist, a binary
property list beside misfix.plist, mode 0644, owned by root. vphoned writes
it, replacing the file in one rename, and removes it:
device.name.set {name}stores a name (not blank, at most 255 UTF-8 bytes, no control character:VPhoneGuestDeviceNameon the host) and returns{name, changed};{name: null}removes the file;device.name.getreturns{name}. Capabilitydevice_name.- After a change vphoned applies configd's preferences unchanged
(
SCPreferencesApplyChanges) so configd publishes again, and postscom.apple.mobile.lockdown.device_name_changed. vphone-vmcallsdevice.name.setwith the VM's name (the folder holdingconfig.plist) on every connect, or with null for a VM name that breaks the rule. There is no setting.
The file survives a reboot, so from the second boot on configd's first
publication already carries the name, before lockdownd first asks for it. The
first boot after the feature arrives, and the first boot after vm rename,
start with the previous name (or none) and change when vphoned connects.
The library checks the file on each decision (stat; read again when inode,
size or modification time changed), not once per process. The value must be
1 to 255 bytes of valid UTF-8 with no control characters; a missing file, one
that does not parse, or any other value means nothing is pinned and every call
passes through.
NVRAM does not reach the guest
The first channel was an NVRAM variable, vphone-device-name, written by
vphone-vm through VZMacAuxiliaryStorage's private
_setDataValue:forNVRAMVariableNamed:error: before each boot. It never
arrived (2026-10-07, dhtest-iphone27):
IODeviceTree:/optionsin the guest had 8 properties:StartupMute,boot-breadcrumbs,fm-spstatus,user_language_enum,fm-activation-locked,boot-args,auto-boot,fm-locktype. Neither the bare name nor the Apple-GUID form was there, read from lockdownd as root.nvram.binkeeps iBoot's two 512 KiB "common" banks at0xa04000and0xa84000(headerZ\x82\x02\x00 nvram, adler32 of bank+0x14…+0x80000 at +0x10). Avphone-device-name=…entry added to the newest bank by hand, with the checksum fixed, was gone from both banks after one boot.vphone-vm's ownboot-argswrite (serial=3 debug=0x104c04) does not reach the guest either: the banks hold an emptyboot-args=, and launchd logsboot-args = serial=3 -v debug=0x2014e, the string the iBoot patch supplies.
iBoot rewrites the store with the variables it knows (its list of names is in
nvram.bin) and drops the rest, so the host cannot hand the guest anything
through NVRAM. vphoned is the channel instead.
configd: publishing the pinned name
SCDynamicStoreSetMultiple is interposed. For a call that sets or removes
anything under Setup:, which in configd is the preferences monitor,
Setup:/System is rewritten so its ComputerName is the pinned name and its
ComputerNameEncoding is UTF-8. Other entries stay. Calls that publish only
State: keys, from configd's other plugins, never read the file.
- The call sets
Setup:/System: its value is pinned. - The call leaves it out because it did not change: the store's current value
(
SCDynamicStoreCopyValue) is pinned and added if it names anything else. This covers a first boot whose preferences have no name. - The call removes it: the removal is dropped and a value holding the pinned name is set instead.
SCDynamicStoreSetValue("Setup:/System", …) is pinned the same way. No configd
caller is known to set that key directly.
A name set while the guest runs takes effect at once. vphoned's apply makes the
monitor publish even though the preferences did not change. Measured with a
tracing build (24A435): with the store holding the guest's own name, the call
sets one Setup: key and leaves Setup:/System out, and the store's value gets
the pin; with a pin in the store and none in the file any more, the call sets
Setup:/System to the preferences' value (the monitor compares with the store),
so the guest's own name returns without a reboot.
The pin is applied when configd publishes, not when it reads the
preferences. Replacing what SCPreferencesGetValue(prefs, kSCPrefSystem)
returns would reach the same flattening, but the monitor's model-change path
(sub_10006155c) reads that value and writes it back with
SCPreferencesSetValue around __SCNetworkConfigurationSaveModel, which
would commit the pinned name to disk. Applied at publication, it never
reaches preferences.plist.
Not pinned: LocalHostName and the DNS host name, which are separate keys
derived from the name when it is set; set-hostname's
__SCPreferencesCopyComputerName (0x100049fcc), which reads the file
directly to decide a DNS host name after a reverse lookup.
lockdownd: refusing renames
While a name is pinned, SCPreferencesSetComputerName returns false and sets
kSCStatusAccessError (_SCErrorSet, exported by SystemConfiguration),
whatever the name. The host name and local host name setters that follow on
the same preferences object are refused too, so the commit changes nothing.
Because set_device_name and its SetValue handler ignore the failures, a
host that renames the guest is told it worked, and the name stays.
The pinned name itself is refused as well. lockdownd calls
set_device_name(copy_device_name()) every time it starts. At boot that runs
before configd's first publication and asks for the name the preferences
already hold (seen as refused SCPreferencesSetComputerName("probe-two") while
the pin was dhtest-iphone27). A lockdownd started later, after a crash or
vphoned's udid.set/udid.clear, which stop it, reads the pinned name from the
store. A first version let a rename to the pinned name through, and one such
restart wrote ComputerName "dhtest 27 测试", HostName and LocalHostName
dhtest-27-ceshi into preferences.plist. Refusing every rename while pinned
keeps the file as the guest left it; the refusal of the pinned name is logged
like any other.
Injection
Both spawn hooks insert /usr/lib/libdevicename.dylib into
/usr/libexec/configd and /usr/libexec/lockdownd (vpIsDeviceNameTarget in
Shared/InjectionEnvironment.h). lockdownd also takes libmisfix.dylib, so
vpInsertedLibrariesFor returns every library for a path and lockdownd starts
with
DYLD_INSERT_LIBRARIES=/usr/lib/SystemHook-vphone.dylib:/usr/lib/libmisfix.dylib:/usr/lib/libdevicename.dylib.
A job launchd spawns itself goes through the launchd hook; one started through
xpcproxy goes through SystemHook. Either way it gets both libraries, and an
environment that already names some of them gains only the missing ones. On
24A435 both come through SystemHook: vphone-systemhook-spawn.log has
configd decision=inserted+devicename and lockdownd decision=inserted+misfix+devicename.
The interposes reach configd's and lockdownd's calls because both are
standalone images binding through their own imports. A call made inside the
shared cache is not rebound (see "An interpose does not cross the shared
cache" in 0_binary_patch_comparison.md). The library checks
getprogname() and acts only in configd or lockdownd.
The library ships with system-launchdaemons-boot-environment and with the
environment update. A guest that receives it from an online update starts
using it at its next boot, when configd and lockdownd are spawned again.
What the host sees, and when
- lockdown readers (
ideviceinfo -k DeviceName, Finder,idevicename) read the store on every request, so they follow a change at once. - CoreDevice (
xcrun devicectl, Xcode's device list, DeviceHub) reads the name when the device's remote service appears, near the time vphoned first answers, and keeps it. Aftervm renamethe first boot is a race: one run showed the new name, another kept the old one until the next boot. Restarting the guest's remoted to force a new handshake is not an option: launchd only starts it again with a new NCM link, and after a USB re-enumeration (udid.clear) the host's remoted failed to connect (connect(2) … Operation timed out) and CoreDevice dropped the device until the guest rebooted.
Assumptions not yet checked
- PreferencesMonitor is the only publisher of
Setup:/System. - Settings' General → About → Name goes through lockdownd (seen statically). If Settings writes the preferences itself, it is not refused, but configd still publishes the pinned name.
- iOS 26.x guests: not run yet.
Verification
Run 2026-10-07 on dhtest-iphone27 (iPhone99,11, iOS 27.0 24A435), VM name
dhtest-iphone27, then renamed to dhtest 27 测试. Logs:
/var/mobile/Library/Caches/vphone-devicename.log in the guest, [device]
lines in the VM's console log on the host.
- First boot without the file:
vm start --waitreturned 5 s after start, and the firstideviceinfo -k DeviceNameanswer a second later was alreadydhtest-iphone27; configd loggedpinned name "dhtest-iphone27"andpublished Setup:/System ComputerName "dhtest-iphone27"after vphoned connected. - Reboot with the file: configd's first publication carries the pin, before
lockdownd's start-up rename (refused), and the first lockdown answer and
devicectlshowdhtest-iphone27. idevicename "Renamed By Host"prints success;ideviceinfoanddevicectlkeepdhtest-iphone27;preferences.plistkeepsComputerName/HostName/LocalHostNameprobe-two; the log has the three refusals.vm renametodhtest 27 测试, boot: the first lockdown answer (1 s after vphoned answered) wasdhtest 27 测试; configd logged the old pin at boot, then the new one.devicectlshowed the new name in this run.device.name.set nullwhile running:ideviceinfoshowed the guest's own name 1 s later; setting the name again pinned it 1 s later. lockdownd killed while pinned came back, refused its start-up rename to the pinned name, andpreferences.plistkept its name.
Host-side checks need no guest: make -C VPhoneGuestComponents test-injection-environment test-devicename covers target matching,
lockdownd's two libraries, reading the file's contents, the publication
rewrite and the rename decision; VPhoneCoreKitTests covers the host's name
rule.