mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-10-02 08:04:32 +08:00
Move guest payloads into Contents/Resources/guest-resources
VPhone.bundle kept vphoned, the guest dylibs and the GPU compiler plugin beside the host programs in Contents/MacOS. Everything installed into the guest now lives in Contents/Resources/guest-resources: vphoned (formerly vphoned.signed), its launch daemon plist, the launchd hook, SystemHook, both camera hooks with their filters, and the GPU compiler plugin. Contents/MacOS holds only vphone-vm, vphone-cli, VPhoneEscalator and the span compatibility library. ValidateBundle.sh checks the platform of every Mach-O: iOS binaries only in guest-resources, none in Contents/MacOS, and no Mach-O elsewhere. The old Resources/guest directory and guest binaries in Contents/MacOS are rejected. codesign --verify --strict and --deep both accept the layout, and vphoned keeps its own entitlements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c238b425e1
commit
961f18c075
@@ -50,7 +50,7 @@ See `Research/` for detailed firmware pipeline, component origins, patch breakdo
|
||||
- `VPhoneGuestComponents`: guest dylibs built by Makefile from the bundle build phase.
|
||||
- `VPhoneLaunchpad`: `vphone-launchpad.app`, the workstation app that downloads and manages `VPhone.bundle` releases and drives VMs through the active bundle's `vphone-cli`, plus its SMJobBless helper `com.vphone.launchpad.helper`. Shipped separately from the bundle. Settings live only in `VPhoneLaunchpad/Configuration/*.xcconfig`; the pbxproj holds none.
|
||||
|
||||
The `VPhone` scheme puts all shipped Mach-O files in `VPhone.bundle/Contents/MacOS`. Guest configuration is in `Contents/Resources`. Xcode targets have `CODE_SIGNING_ALLOWED=NO`; the bundle build phase signs each binary ad hoc with only its own entitlements, then seals the outer bundle. `VPhoneVirtualization.entitlements` belongs to `vphone-vm`; `VPhoneDaemon.entitlements` belongs to `vphoned`. The bundle and CLI have no private entitlements.
|
||||
The `VPhone` scheme puts host programs in `VPhone.bundle/Contents/MacOS` and everything installed into the guest (vphoned, guest dylibs, their plists) in `Contents/Resources/guest-resources`. Xcode targets have `CODE_SIGNING_ALLOWED=NO`; the bundle build phase signs each binary ad hoc with only its own entitlements, then seals the outer bundle. `VPhoneVirtualization.entitlements` belongs to `vphone-vm`; `VPhoneDaemon.entitlements` belongs to `vphoned`. The bundle and CLI have no private entitlements.
|
||||
|
||||
### Key Patterns
|
||||
|
||||
|
||||
@@ -11,10 +11,15 @@ executables and resources, not a macOS app or a dynamically loaded plug-in.
|
||||
| `Contents/MacOS/vphone-cli` | Unentitled command entry point |
|
||||
| `Contents/MacOS/vphone-vm` | VM and window process; private virtualization entitlements |
|
||||
| `Contents/MacOS/VPhoneEscalator` | AMFI allowlist tool for the current VM cdhash |
|
||||
| `Contents/MacOS/vphoned.signed` | Pre-signed guest daemon payload with its own entitlements; the bundle contains no unsigned copy |
|
||||
| Guest dylibs in `Contents/MacOS` | Guest installation payloads |
|
||||
| `Contents/Resources/guest/vphoned.plist` | Guest launch daemon configuration |
|
||||
| `Contents/Resources` | Guest configuration and nonexecutable resources; no signing script or entitlement file is shipped |
|
||||
| `Contents/MacOS/libswiftCompatibilitySpan.vphone.dylib` | Swift back-deployment library for `vphone-vm` on macOS 15 |
|
||||
| `Contents/Resources/guest-resources/vphoned` | Pre-signed guest daemon with its own entitlements; the bundle contains no unsigned copy |
|
||||
| `Contents/Resources/guest-resources/*.dylib` | Guest libraries: launchd hook, SystemHook, camera hooks and the GPU compiler plugin |
|
||||
| `Contents/Resources/guest-resources/*.plist` | Guest launch daemon configuration and camera hook filters |
|
||||
| `Contents/Resources` | Localized strings; no signing script or entitlement file is shipped |
|
||||
|
||||
`Contents/MacOS` holds only programs that run on the Mac.
|
||||
`guest-resources` holds only files installed into the guest; every Mach-O in
|
||||
it is built for iOS. `ValidateBundle.sh` enforces both rules.
|
||||
|
||||
All executable payloads use ad hoc code signatures. Only the required child
|
||||
processes carry private entitlements. The bundle has no `CFBundleExecutable`,
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@ extension CryptexFilesystemPatcher {
|
||||
let launchdOgPath = target.appending(path: "/System/Library/xpc/launchd.plist")
|
||||
try FileManager.default.moveItem(at: launchdOgPath, to: launchdPath)
|
||||
|
||||
let vphonedLaunchdPlist = resources.base.appendingPathComponent("guest/vphoned.plist")
|
||||
let vphonedLaunchdPlist = resources.guestResources.appendingPathComponent("vphoned.plist")
|
||||
try FileManager.default.copyItem(
|
||||
at: vphonedLaunchdPlist,
|
||||
to: target.appending(path: "System/Library/LaunchDaemons/vphoned.plist"),
|
||||
@@ -83,7 +83,7 @@ extension CryptexFilesystemPatcher {
|
||||
/// that VM. vphoned is cross-compiled at build time now
|
||||
/// by the Xcode guest target and staged into the bundle.
|
||||
func stageVphoned(to vphonedBin: URL) throws {
|
||||
let prebuilt = try VPhoneGuestBinaries.resolve("vphoned.signed")
|
||||
let prebuilt = try VPhoneGuestBinaries.resolve("vphoned")
|
||||
try FileManager.default.copyItem(at: prebuilt, to: vphonedBin)
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -355,7 +355,7 @@ struct VPhoneCustomFirmwareInstaller {
|
||||
// Install the same signed bytes that vm launch uses for auto-update.
|
||||
// Re-signing here changes the binary hash and forces an upload and
|
||||
// daemon restart on the VM's first boot.
|
||||
let vphoned = try VPhoneGuestBinaries.resolve("vphoned.signed")
|
||||
let vphoned = try VPhoneGuestBinaries.resolve("vphoned")
|
||||
let staged = work.appendingPathComponent("vphoned")
|
||||
try fm.copyItem(at: vphoned, to: staged)
|
||||
try replace(staged, at: system.appendingPathComponent("usr/bin/vphoned"), mode: 0o755)
|
||||
@@ -364,7 +364,7 @@ struct VPhoneCustomFirmwareInstaller {
|
||||
try fm.removeItem(at: signed)
|
||||
}
|
||||
try fm.copyItem(at: staged, to: signed)
|
||||
let daemon = resources.base.appendingPathComponent("guest/vphoned.plist")
|
||||
let daemon = resources.guestResources.appendingPathComponent("vphoned.plist")
|
||||
try replace(
|
||||
daemon,
|
||||
at: system.appendingPathComponent(
|
||||
|
||||
@@ -7,6 +7,9 @@ configuration="${CONFIGURATION:?}"
|
||||
bundle="${TARGET_BUILD_DIR:?}/${FULL_PRODUCT_NAME:?}"
|
||||
macos="$bundle/Contents/MacOS"
|
||||
resources="$bundle/Contents/Resources"
|
||||
# Host programs run from Contents/MacOS. Everything installed into the guest
|
||||
# lives in guest-resources and never runs on the Mac.
|
||||
guest="$resources/guest-resources"
|
||||
|
||||
# Xcode exports the bundle target's SDK and package paths to build phases. Nested
|
||||
# xcodebuild must resolve each project's own graph, especially the iOS daemon.
|
||||
@@ -50,19 +53,20 @@ if /usr/bin/nm -u "$daemon_products/vphoned" | /usr/bin/grep -q '_swift_initBorr
|
||||
fi
|
||||
|
||||
/bin/rm -rf "$macos" "$resources"
|
||||
/bin/mkdir -p "$macos" "$resources/guest"
|
||||
/bin/mkdir -p "$macos" "$guest"
|
||||
/bin/cp "$TARGET_BUILD_DIR/vphone-vm" "$macos/vphone-vm"
|
||||
/bin/cp "$command_products/vphone-cli" "$macos/vphone-cli"
|
||||
/bin/cp "$daemon_products/vphoned" "$macos/vphoned.signed"
|
||||
/bin/cp "$amfi_products/VPhoneEscalator" "$macos/VPhoneEscalator"
|
||||
/bin/cp "$guest_products/camfix/libcamfix.dylib" "$macos/libcamfix.dylib"
|
||||
/bin/cp "$guest_products/vcamcaptured/libvcamcaptured.dylib" "$macos/libvcamcaptured.dylib"
|
||||
/bin/cp "$guest_products/launchhook/launchdhook-vphone.dylib" "$macos/launchdhook-vphone.dylib"
|
||||
/bin/cp "$guest_products/systemhook/SystemHook-vphone.dylib" "$macos/SystemHook-vphone.dylib"
|
||||
/bin/cp "$guest_products/gpu/libAppleParavirtCompilerPluginIOGPUFamily.dylib" "$macos/libAppleParavirtCompilerPluginIOGPUFamily.dylib"
|
||||
/bin/cp "$root/VPhoneDaemon/Configuration/vphoned.plist" "$resources/guest/vphoned.plist"
|
||||
/bin/cp "$guest_products/camfix/libcamfix.plist" "$resources/guest/libcamfix.plist"
|
||||
/bin/cp "$guest_products/vcamcaptured/libvcamcaptured.plist" "$resources/guest/libvcamcaptured.plist"
|
||||
/bin/cp "$daemon_products/vphoned" "$guest/vphoned"
|
||||
/bin/cp "$root/VPhoneDaemon/Configuration/vphoned.plist" "$guest/vphoned.plist"
|
||||
/bin/cp "$guest_products/launchhook/launchdhook-vphone.dylib" "$guest/launchdhook-vphone.dylib"
|
||||
/bin/cp "$guest_products/systemhook/SystemHook-vphone.dylib" "$guest/SystemHook-vphone.dylib"
|
||||
/bin/cp "$guest_products/camfix/libcamfix.dylib" "$guest/libcamfix.dylib"
|
||||
/bin/cp "$guest_products/camfix/libcamfix.plist" "$guest/libcamfix.plist"
|
||||
/bin/cp "$guest_products/vcamcaptured/libvcamcaptured.dylib" "$guest/libvcamcaptured.dylib"
|
||||
/bin/cp "$guest_products/vcamcaptured/libvcamcaptured.plist" "$guest/libvcamcaptured.plist"
|
||||
/bin/cp "$guest_products/gpu/libAppleParavirtCompilerPluginIOGPUFamily.dylib" \
|
||||
"$guest/libAppleParavirtCompilerPluginIOGPUFamily.dylib"
|
||||
|
||||
"${0:a:h}/SyncStrings.sh"
|
||||
for catalog in Localizable InfoPlist; do
|
||||
@@ -82,7 +86,7 @@ compatibility_library="$(/usr/bin/xcrun swift-stdlib-tool --print \
|
||||
/bin/rm -f "$bundle/Contents/Frameworks/libswiftCompatibilitySpan.dylib"
|
||||
|
||||
/usr/bin/codesign --force --sign - "$macos/vphone-cli"
|
||||
/usr/bin/codesign --force --sign - --entitlements "$root/VPhoneDaemon/Configuration/VPhoneDaemon.entitlements" "$macos/vphoned.signed"
|
||||
/usr/bin/codesign --force --sign - --entitlements "$root/VPhoneDaemon/Configuration/VPhoneDaemon.entitlements" "$guest/vphoned"
|
||||
/usr/bin/codesign --force --sign - "$macos/VPhoneEscalator"
|
||||
/usr/bin/codesign --force --sign - "$macos/libswiftCompatibilitySpan.vphone.dylib"
|
||||
/usr/bin/codesign --force --sign - --entitlements "$root/VPhoneExecutable/VPhoneVirtualization/Resources/VPhoneVirtualization.entitlements" "$macos/vphone-vm"
|
||||
|
||||
@@ -6,6 +6,7 @@ root="$(cd "${0:a:h}/../../.." && pwd)"
|
||||
bundle="${1:-$root/.build/XcodeBundle/Build/Products/Debug/VPhone.bundle}"
|
||||
macos="$bundle/Contents/MacOS"
|
||||
resources="$bundle/Contents/Resources"
|
||||
guest="$resources/guest-resources"
|
||||
|
||||
file_copy_spawns="$(/usr/bin/find "$root/VPhoneExecutable" "$root/VPhoneKit" \
|
||||
"$root/VPhoneDaemon" "$root/VPhoneGuestComponents" \
|
||||
@@ -19,39 +20,40 @@ file_copy_spawns="$(/usr/bin/find "$root/VPhoneExecutable" "$root/VPhoneKit" \
|
||||
|
||||
[[ -d "$bundle" ]] || { print -u2 "Missing Xcode bundle: $bundle"; exit 1; }
|
||||
|
||||
for name in vphone-vm vphone-cli VPhoneEscalator vphoned.signed \
|
||||
libswiftCompatibilitySpan.vphone.dylib libcamfix.dylib libvcamcaptured.dylib \
|
||||
launchdhook-vphone.dylib SystemHook-vphone.dylib \
|
||||
libAppleParavirtCompilerPluginIOGPUFamily.dylib; do
|
||||
[[ -f "$macos/$name" ]] || { print -u2 "Missing binary: Contents/MacOS/$name"; exit 1; }
|
||||
/usr/bin/file "$macos/$name" | /usr/bin/grep -q 'Mach-O' || {
|
||||
print -u2 "Not a Mach-O: $name"
|
||||
require_signed_macho() {
|
||||
local file="$1"
|
||||
[[ -f "$file" ]] || { print -u2 "Missing binary: ${file#$bundle/}"; exit 1; }
|
||||
/usr/bin/file "$file" | /usr/bin/grep -q 'Mach-O' || {
|
||||
print -u2 "Not a Mach-O: ${file#$bundle/}"
|
||||
exit 1
|
||||
}
|
||||
/usr/bin/codesign --verify "$macos/$name" || {
|
||||
print -u2 "Invalid signature: $name"
|
||||
/usr/bin/codesign --verify "$file" || {
|
||||
print -u2 "Invalid signature: ${file#$bundle/}"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
for name in vphone-vm vphone-cli VPhoneEscalator libswiftCompatibilitySpan.vphone.dylib; do
|
||||
require_signed_macho "$macos/$name"
|
||||
done
|
||||
for name in vphoned launchdhook-vphone.dylib SystemHook-vphone.dylib libcamfix.dylib \
|
||||
libvcamcaptured.dylib libAppleParavirtCompilerPluginIOGPUFamily.dylib; do
|
||||
require_signed_macho "$guest/$name"
|
||||
done
|
||||
for name in vphoned.plist libcamfix.plist libvcamcaptured.plist; do
|
||||
[[ -f "$guest/$name" ]] || { print -u2 "Missing guest configuration: $name"; exit 1; }
|
||||
done
|
||||
|
||||
for name in vphoned vphone-app VPhoneAMFIAllow vphone-archive icli vpregister vphone-ask-for-permission; do
|
||||
[[ ! -e "$macos/$name" ]] || { print -u2 "Obsolete binary: $name"; exit 1; }
|
||||
for name in vphoned vphoned.signed vphone-app VPhoneAMFIAllow vphone-archive icli vpregister \
|
||||
vphone-ask-for-permission libcamfix.dylib libvcamcaptured.dylib launchdhook-vphone.dylib \
|
||||
SystemHook-vphone.dylib libAppleParavirtCompilerPluginIOGPUFamily.dylib; do
|
||||
[[ ! -e "$macos/$name" ]] || { print -u2 "Obsolete binary: Contents/MacOS/$name"; exit 1; }
|
||||
done
|
||||
for name in guest scripts; do
|
||||
[[ ! -e "$resources/$name" ]] || { print -u2 "Obsolete directory: Contents/Resources/$name"; exit 1; }
|
||||
done
|
||||
|
||||
[[ -f "$resources/guest/vphoned.plist" ]] || {
|
||||
print -u2 "Missing guest configuration: vphoned.plist"
|
||||
exit 1
|
||||
}
|
||||
[[ ! -e "$resources/scripts" ]] || {
|
||||
print -u2 "Obsolete scripts directory in bundle"
|
||||
exit 1
|
||||
}
|
||||
|
||||
/usr/bin/codesign --verify --strict "$bundle"
|
||||
/usr/bin/codesign --verify "$macos/vphone-vm"
|
||||
/usr/bin/codesign --verify "$macos/vphone-cli"
|
||||
/usr/bin/codesign --verify "$macos/VPhoneEscalator"
|
||||
/usr/bin/codesign --verify "$macos/vphoned.signed"
|
||||
|
||||
[[ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundlePackageType' "$bundle/Contents/Info.plist")" == "BNDL" ]] || {
|
||||
print -u2 "The product is not a generic bundle"
|
||||
@@ -61,11 +63,23 @@ if /usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$bundle/Contents/Info
|
||||
print -u2 "The container must not declare an executable"
|
||||
exit 1
|
||||
fi
|
||||
# Host programs live in Contents/MacOS and guest payloads in guest-resources.
|
||||
# The build platform keeps an iOS binary from landing among the host programs.
|
||||
while IFS= read -r file; do
|
||||
if /usr/bin/file "$file" | /usr/bin/grep -q 'Mach-O' && [[ "$file" != "$macos/"* ]]; then
|
||||
print -u2 "Mach-O outside Contents/MacOS: $file"
|
||||
exit 1
|
||||
fi
|
||||
/usr/bin/file "$file" | /usr/bin/grep -q 'Mach-O' || continue
|
||||
platform="$(/usr/bin/vtool -show-build "$file" 2>/dev/null | /usr/bin/awk '$1 == "platform" {print $2; exit}')"
|
||||
case "$file" in
|
||||
"$macos/"*)
|
||||
[[ "$platform" != IOS ]] || { print -u2 "iOS binary in Contents/MacOS: ${file#$bundle/}"; exit 1; }
|
||||
;;
|
||||
"$guest/"*)
|
||||
[[ "$platform" == IOS ]] || { print -u2 "Non-iOS binary in guest-resources: ${file#$bundle/}"; exit 1; }
|
||||
;;
|
||||
*)
|
||||
print -u2 "Mach-O outside Contents/MacOS and guest-resources: ${file#$bundle/}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done < <(/usr/bin/find "$bundle/Contents" -type f)
|
||||
bundle_entitlements="$(/usr/bin/codesign -d --entitlements - --xml "$bundle" 2>/dev/null || true)"
|
||||
[[ "$bundle_entitlements" != *'com.apple.private.virtualization'* ]] || {
|
||||
@@ -81,7 +95,7 @@ vm_entitlements="$(/usr/bin/codesign -d --entitlements - --xml "$macos/vphone-vm
|
||||
print -u2 "Guest daemon entitlements leaked into vphone-vm"
|
||||
exit 1
|
||||
}
|
||||
daemon_entitlements="$(/usr/bin/codesign -d --entitlements - --xml "$macos/vphoned.signed" 2>/dev/null)"
|
||||
daemon_entitlements="$(/usr/bin/codesign -d --entitlements - --xml "$guest/vphoned" 2>/dev/null)"
|
||||
[[ "$daemon_entitlements" == *'com.apple.CommCenter.fine-grained'* &&
|
||||
"$daemon_entitlements" != *'com.apple.private.virtualization'* ]] || {
|
||||
print -u2 "vphoned has the wrong entitlements"
|
||||
|
||||
@@ -20,6 +20,6 @@ Metal compilation, leaving the host VM window black even while vphoned connects.
|
||||
`main.mm` is the compiler-plugin reimplementation from
|
||||
[0xjohnnydev's metal-patch](https://github.com/0xjohnnydev/0xjohnnydev.github.io/blob/main/blog/assets/metal-patch/main.mm).
|
||||
`make -C VPhoneGuestComponents gpu` builds and ad-hoc signs it for iPhoneOS arm64e.
|
||||
The Xcode app build stages it in `Contents/MacOS`.
|
||||
The Xcode bundle build stages it in `Contents/Resources/guest-resources`.
|
||||
`fw prepare` copies that dylib alongside the
|
||||
firmware-sourced GPU driver before exposing the complete restore tree.
|
||||
|
||||
@@ -98,9 +98,13 @@ public struct VPhoneResources: Sendable {
|
||||
base.appendingPathComponent("scripts")
|
||||
}
|
||||
|
||||
/// Files installed into the guest. Nothing here runs on the Mac.
|
||||
public var guestResources: URL {
|
||||
base.appendingPathComponent("guest-resources")
|
||||
}
|
||||
|
||||
public var vphoned: URL {
|
||||
let bundled = base.deletingLastPathComponent()
|
||||
.appendingPathComponent("MacOS/vphoned.signed")
|
||||
let bundled = guestResources.appendingPathComponent("vphoned")
|
||||
if FileManager.default.fileExists(atPath: bundled.path) {
|
||||
return bundled
|
||||
}
|
||||
@@ -109,7 +113,7 @@ public struct VPhoneResources: Sendable {
|
||||
}
|
||||
|
||||
public var gpuCompilerPlugin: URL {
|
||||
Self.siblingExecutable("libAppleParavirtCompilerPluginIOGPUFamily.dylib")
|
||||
guestResources.appendingPathComponent("libAppleParavirtCompilerPluginIOGPUFamily.dylib")
|
||||
}
|
||||
|
||||
// MARK: - Cache dirs
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// VPhoneGuestBinaries.swift — where the prebuilt iOS binaries live.
|
||||
//
|
||||
// vphoned is cross-compiled on the build machine and shipped in the bundle.
|
||||
// The bundle carries the signed binary for installation and host auto-update.
|
||||
// Guest payloads are cross-compiled on the build machine and shipped in the
|
||||
// bundle's guest resources. The bundle carries the signed vphoned for
|
||||
// installation and host auto-update.
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -20,17 +21,18 @@ public enum VPhoneGuestBinaries {
|
||||
}
|
||||
}
|
||||
|
||||
/// `Contents/MacOS` in the bundle, `.build/guest` in a dev tree.
|
||||
/// `Contents/Resources/guest-resources` in the bundle, `.build/guest` in a
|
||||
/// dev tree.
|
||||
///
|
||||
/// Both are derived from `VPhoneResources.base`, which is already the
|
||||
/// running image's own location rather than anything on `PATH` — so this
|
||||
/// finds the binaries that were built beside this one, not whatever else is
|
||||
/// on the machine.
|
||||
public static func directories() -> [URL] {
|
||||
let base = VPhoneResources.resolve().base
|
||||
let resources = VPhoneResources.resolve()
|
||||
return [
|
||||
base.deletingLastPathComponent().appendingPathComponent("MacOS"),
|
||||
base.appendingPathComponent(".build/guest"),
|
||||
resources.guestResources,
|
||||
resources.base.appendingPathComponent(".build/guest"),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ struct ResourcesTests {
|
||||
let exe = "/Applications/VPhone.bundle/Contents/MacOS/vphone-cli"
|
||||
let r = VPhoneResources.resolve(executablePath: exe)
|
||||
#expect(r.base.path == "/Applications/VPhone.bundle/Contents/Resources")
|
||||
#expect(r.guestResources.path == "/Applications/VPhone.bundle/Contents/Resources/guest-resources")
|
||||
}
|
||||
|
||||
@Test func `dev layout walks up to project root`() throws {
|
||||
|
||||
Reference in New Issue
Block a user