Build and stage x64 and x86 NVIDIA shims through the existing solution and deploy them through P9 and the guest agent. Include active AMD runtime packages and prefer native OpenGL and Vulkan on NVIDIA, AMD, and Intel guests.
App Sandbox 0.1.4
- **Windows 11 (ARM64) VMs on macOS.** On an Apple Silicon Mac, App Sandbox can
now create and run Windows 11 ARM guests -- previously a macOS host could only
run macOS guests. The Windows guest gets its own display window, keyboard and
mouse, sound, clipboard (including copying files to and from your Mac),
one-click SSH, and networking, and is built from a Windows 11 ARM64 ISO you
provide. It runs on a self-contained virtual-machine engine bundled with the
app, and the guest agent and drivers are downloaded (code-signed) when you
create the VM, so no test-signed components ship inside App Sandbox.
- **Non-ASCII usernames and passwords now work when creating a VM through the
scriptable API.** Characters outside plain ASCII (for example Chinese) arrive
from the API JSON-escaped; App Sandbox stored the escape sequences literally
instead of decoding them, so a VM created via the API with such a credential
would reject the login. They are now decoded correctly. (The desktop app was
unaffected.)
- **The virtual display now recovers if it gets disabled in the guest.** The
guest agent kept trying to restart the App Sandbox display driver -- which
can't re-enable a disabled device -- so the picture never came back; it is now
re-enabled and recovers.
- Various stability and bug fixes across the Windows and macOS components.
- **Windows VMs on macOS are not GPU-accelerated.**
- **x64 (emulated) apps in ARM Windows VMs don't get the OpenGL fix** -- the Mesa
driver shipped on ARM is ARM64-only; native ARM64 apps get full GPU OpenGL.
(carried over from 0.1.2)
- **ARM Linux (Ubuntu 26.04 LTS) guests do not work -- on ARM, Windows is the
only working guest** (Ubuntu 26.04's arm64 kernel churns BPF JIT memory and
never reaches the desktop). (carried over from 0.1.2)
win2ntfs.c was the standalone harness that validated the WIM->NTFS apply;
that logic now lives (with real per-file SIDs) in win_disk.c. AppSandboxSHM_producer.c
was the ivshmem bring-up bandwidth POC whose host-side reader (shm-test) is gone;
the production data path is asb_transport over the AppSandboxSHM driver. Neither
file was compiled by any target or included anywhere.
build-arm64.cmd and deploy-arm64.cmd were manual dev-VM helpers (cl build
of the guest binaries + hand-copy to C:\Windows\AppSandbox); the production
pipeline builds via msbuild on AppSandbox.sln and never referenced them.
- Delete the TODO/ planning + runbook docs, and fix the four code/README comments
that pointed at them (asb_transport.c/.h; the tools/ivshmem and vendor/qemu-ivshmem
READMEs now point at the vendored QEMU directly).
- THIRD-PARTY-NOTICES.md: add the bundled QEMU stack (QEMU GPL-2.0; GLib +
gettext/libintl LGPL-2.1; PCRE2 BSD; EDK2/TianoCore BSD-2-Clause-Patent) and
NetKVM (BSD-3) -- shipped/staged by the app but previously absent from the
top-level notices. The QEMU entry cross-references the bundled
vendor/qemu-ivshmem/LICENSES/NOTICE.md + WRITTEN_OFFER.txt. A usage audit
confirmed every listed dependency is still in use, so nothing was removed.
Post-review fixes and release prep for the win-on-mac feature.
* asb_shm_layout.h: fix a region-sizing bug -- the AGENT (ch1) slot stride
(0x20070) exceeded ASB_AGENT_REGION_SIZE (0x20000) by 112 bytes, so the agent
host->guest ring overran into the adjacent SSH (ch7) region and corrupted its
slot state under sustained traffic. Enlarge the AGENT region to 0x21000
(downstream offsets cascade) and add per-region compile-time guards
(n_slots*stride <= region_size) so this class of overlap fails the build.
Host/publisher-side only -- the guest reads geometry from the published directory.
* headless.m: GET /display readiness now includes idd_ready for a Windows guest,
matching the open precondition (asb_core_mac.m) and the Windows host
(asb_vm_idd_ready). Before, the Mac readiness was running && agent_online only,
so it could report ready before the VDD was up and a poll-then-open would
spuriously 409 display_not_ready. macOS guests are unchanged.
* iso-patch-mac.m (stage): wipe the plaintext password buffer on the
--shadowhash-file read-failure path, matching the sibling --kcpassword-file path.
* iso_patch_mac.m: point the signed Windows guest payload at the v0.1.4 release.
* vm_lifecycle.py: raise the Windows build-stall budget to 3600 s for the
signal-less first-boot window under concurrent multi-VM load.
* tools/headless-api/README.md: document Windows guests on macOS hosts (platform
matrix, host-fixed line, requirements heading, osType field).
ensure_vdd_running() detected a disabled AppSandboxVDD and ran `devcon restart`,
which reports success but cannot clear CONFIGFLAG_DISABLED on a device in
CM_PROB_DISABLED -- so the device snapped straight back to disabled and the agent
looped forever (detect disabled -> restart -> still disabled, every 20s). Only
`devcon enable` (DICS_ENABLE) clears the explicit-disable bit.
Now: ENABLE a disabled device; keep RESTART for a stuck-but-enabled ("problem") one.
All three guest configs now obfuscate/hash the account credential in the daemon
and hand the disk-applier child only the non-plaintext result -- the plaintext
never crosses the process boundary, mirroring the Windows-on-Windows model.
* Win-on-Mac: the daemon generates unattend.xml / setup.cmd / SetupComplete.cmd
in-process via the shared asb_provision_* (base64 of UTF-16LE(pass+"Password"),
the same encoder the Windows backend uses) and passes build-windows only
--prov-dir; the plaintext --pass argv is gone. The child still detects the
install.wim language and reports LANG:, and the daemon re-generates unattend.xml
in-process with the detected tag -- the exact mirror of vhdx_create_thread.
* Mac-on-Mac: the salted PBKDF2-SHA512 ShadowHash + kcpassword are computed in
the daemon via the new shared tools/provision/mac_account_hash.{h,m}; the stage
child takes --shadowhash-file / --kcpassword-file. --user-password-file remains
a direct-CLI fallback.
* Verified end-to-end on live Windows + macOS guests with a 12-character mixed
Chinese/English password (ValidateCredentials / dscl -authonly), exercising the
UTF-16 surrogate path.
Also in this checkpoint of accumulated win-on-mac work:
* agent: report_idd_status re-runs on the device-check loop (force-on-connect +
on-change) so display readiness tracks the live, self-healed VDD state instead
of a single one-shot sample.
* disk builder (tools/iso-patch-mac/engine): WIM reader gains the install-image
default-language reader behind the language auto-detect; win_disk locale/apply
refinements.
* display + transport: idd_display Metal render + clipboard-reader paths;
asb_ivshmem_transport slot-reclaim hardening.
* housekeeping: remove the dev-only transport test harness (tools/transport/test/*);
update headless-api test scripts; drop changelog-style comments.
The Mac host now sources the entire Windows guest payload via JIT download + cache --
no bundled/embedded fallback, so zero test-signed binaries ship in the app.
- iso-patch-mac.m (build-windows): --signed-payload-zip is now required (its extraction
failing is a hard error, not a fallback); new --netkvm-zip. Extract the signed zip in
full -- the Windows-built zip uses backslash separators that forward-slash unzip
include-patterns never match (unzip exits 11 and stages nothing); a full extract makes
Info-ZIP rewrite them into real resources/ + drivers/. Stage our VDD/VAD/SHM from the
signed payload and NetKVM from its own zip (best-effort). Drop --payload/--devcon.
- iso_patch_mac.{h,m}: buildWindowsDiskWithISO drops payloadDir/devconExe, takes a
required signedPayloadZip + optional netkvmZip. Add ensureNetkvmZipCached (pulls the
vendored netkvm-arm64.zip from the public repo, cached like the IPSW).
- asb_core_mac.m: remove windows_payload_directory()/devcon_exe_path(); the create flow
fails if the signed payload download fails (JIT-only). NetKVM + OpenSSH best-effort.
- Remove the "Embed agent_win" Xcode phase and git rm tools/agent_win (the test-signed
EXEs + drivers). The app bundle embeds zero Windows test-signed binaries.
Fixes the staging bug where the backslash zip silently fell back to the bundled
WDKTestCert drivers, which failed DriverStore import (0xE0000243), so the agent never
came online.
The macOS host now pulls the signed Windows release zip (EV-signed agent EXEs +
MS-attestation-signed drivers, incl. the ivshmem AppSandboxSHM) and stages those
into the guest, instead of the bundled test-signed tools/agent_win payload.
- iso_patch_mac.m: ensureSignedWinPayloadZipCached mirrors ensureOpenSSHMsiCached
-- a single atomic writeToFile of the release zip to a version-named file under
the support dir (sibling of VMs/), race-safe by the same single atomic write.
Returns the cached zip path; the build extracts it.
- iso-patch-mac.m (build-windows): new --signed-payload-zip. When given, the CLI
selectively unzips resources/appsandbox-*.exe + drivers/* into its own per-build
temp and prefers them over --payload (so concurrent creates never collide);
derives binDir/drvDir/devconPath from it. Falls back to --payload on any problem.
The signed drivers carry no .cer, so SetupComplete's `if exist` certutil steps
no-op; devcon comes from the zip's drivers/.
- iso_patch_mac.{h,m}: buildWindowsDiskWithISO threads signedPayloadZip and makes
--payload conditional (a nil bundle can't crash the args array).
- asb_core_mac.m: start_windows_build_flow caches the zip off-main and passes it;
bundled payload remains the fallback. NetKVM is still not downloaded.
The bundled test-signed payload stays embedded as a fallback for now; dropping the
Xcode "Embed agent_win" phase (to fully remove test-signed binaries from the app)
is a follow-up once the download URL is durable. Mac target not compiled here.
Provisioning unification (R5):
- disk_util.c delegates generate_unattend_vhdx / generate_vhdx_setup_cmd /
generate_vhdx_setupcomplete to the shared tools/provision/win_provision.c, and
stages AppSandboxSHM + NetKVM (existence-gated) in generate_vhdx_manifest.
- Compile win_provision.c into AppSandboxCore.vcxproj. Win->Win output is
equivalent (the SHM/NetKVM install steps are if-exist no-ops on a PC host).
UTF-8 / non-ASCII credentials:
- win_provision.c: encode the unattend <Password> as true UTF-16LE (was a UTF-8
byte zero-extend that corrupted non-ASCII/CJK passwords); ASCII unchanged. Plus
case-insensitive locale-tag lookup and ComputerName truncation by UTF-8 code
points (never splitting a multibyte char).
- webview2_bridge.c json_get_string: decode JSON escapes (\uXXXX, \" \ ...) so
non-ASCII values from the headless SDK (json.dumps/ensure_ascii) are no longer
stored as literal \uXXXX text. Validated end-to-end: a CJK password now sets
correctly and Windows authenticates against it.
- disk_util.c wrapper: check the WideCharToMultiByte conversions.
Also included in the working tree:
- Reconcile user-mode .vcxproj platform toolset v145 -> v143 (VS2022).
- WINDOWS_AGENT_BRIEF.md: correct stale driver-name/symbol/devcon references.
- Rename the ivshmem guest driver asb_ivshmem -> AppSandboxSHM everywhere (files, INF
service/binary/catalog + display strings, device-interface GUID symbol with the GUID
value preserved, driver source, transport, provisioning, payload, iso-patch stage list).
QEMU's ivshmem device id and the Mac host transport (asb_ivshmem_transport.*) keep names.
- Build the driver through the VS pipeline like VDD/VAD: AppSandboxSHM.vcxproj (KMDF) +
Package/AppSandboxSHMPackage.vcxproj (Inf2Cat + TestSign + post-build copy into
bin\Release[-ARM64]\drivers), registered in AppSandbox.sln; add to submission-config.json
+ make-release.ps1 for MS attestation. INF gets a .10.0...22000 (Win11) TargetOSVersion
decoration (required by DIRID 13 / PnpLockdown).
- Install the SHM PCI driver with devcon update (not devcon install, which fabricates a
phantom ROOT devnode on a real PnP hwid) -- same pattern as NetKVM.
- Fix SetupComplete.cmd: a literal (...) in an echo inside a cmd ( ) block desynced cmd's
paren matcher and silently skipped every driver install; removed it.
- iso-patch: per-build -shadow ISO attach so concurrent VM builds get independent devices.
- Reconcile all user-mode .vcxproj to the v145 (VS 2026) toolset.
- Transport/display: guest liveness beacon + h2g-stall reclaim, zero-copy Metal render of
the IDD display, clipboard-file paste chown for the root daemon.
- Refresh the committed tools/agent_win payload; update the windows-on-mac integration plan.
Run Windows 11 ARM64 guests on macOS over QEMU+HVF, with an ivshmem
shared-memory transport carrying every guest channel (agent control,
display/VDD, input, audio, clipboard, SSH, 9P) - no guest networking
required.
- tools/transport: one guest transport API, two backends (PC AF_HYPERV,
Mac ivshmem); SPSC ring with an acquire-barrier fix for SSH integrity
- tools/ivshmem: our own KMDF ivshmem BAR-mapping driver (no third party)
- src/backend_mac: Mac-host ivshmem transport, QEMU VM lifecycle, IDD
display consumer, agent/SSH proxies, ISO->disk patcher, Windows create
- tools/agent + channel EXEs + tools/vdd: wired to asb_transport
- tools/agent_win: staged guest payload (interim, pending JIT pull)
- vendor/qemu-ivshmem: vendored QEMU 11.0.1 fork (ivshmem-plain) + LGPL
source mirror for the self-contained app bundle
- tools/sign/make-release.ps1: use the host-native ARM64 MSBuild so the
WDK driver ApiValidator runs (the x86 validator fails on ARM64)
### Added
- **Headless mode with a scriptable HTTP/JSON API (Windows + macOS).** Launch
App Sandbox with `--headless` to run a single-owner background daemon exposing
a loopback HTTP/JSON API that mirrors the GUI: create, start, shut down, edit,
and delete VMs; manage snapshots, branches, and templates (Windows); read SSH
connection info; subscribe to a live event stream; and open or close a VM's
display window -- all from a script. Ships a dependency-free Python SDK,
runnable examples, and a cross-platform test suite (bundled in the release
package under headless-api/, alongside the app). Launching normally (no
`--headless`) is unchanged.
- **One-click SSH key login.** With the SSH server enabled, the new "Deploy SSH
key" option installs an App Sandbox key into the guest (Windows, Linux, and
macOS) so the GUI's SSH button and the SDK connect without a password.
### Fixed
- **Excessive GPU usage while viewing a Windows VM.** The display window was
redrawing as fast as it could -- presenting ~2,100 frames a second and holding
the host GPU near 90% even on a static screen. It now redraws only when the
picture changes (plus a small safety refresh), dropping to the real frame rate
(~30-60 fps) with no added latency.
- **A graceful shutdown could report a false error.** On a busy guest, a
shutdown that actually succeeded could surface as a failure; shutdown and
restart now report success once the request is delivered and confirm it by the
guest exiting.
- **The headless daemon now cleans up its VMs when it exits**, instead of
leaving them running with no owner.
- **macOS: text fields no longer autocorrect or autocapitalize** VM names,
usernames, paths, and snapshot names as you type.
- Various stability and bug fixes across the Windows, macOS, and Linux
components.
### Known issues
- **Create VMs one at a time.** Creating several at once can race on the
disk-image build/staging step, causing a build to fail and the VM to be
removed; create them sequentially.
- **x64 (emulated) apps in ARM Windows VMs don't get the OpenGL fix** -- the
Mesa driver shipped on ARM is ARM64-only; native ARM64 apps get full GPU
OpenGL. (carried over from 0.1.2)
- **ARM Linux (Ubuntu 26.04 LTS) guests do not work -- on ARM, Windows is the
only working guest** (Ubuntu 26.04's arm64 kernel churns BPF JIT memory and
never reaches the desktop). (carried over from 0.1.2)
Both release packagers now place a headless-api/ folder next to the app at the
zip root (AppSandbox.exe / AppSandbox.app + headless-api/), so a user who
installs from a release gets the Python SDK, examples, and tests without cloning
the repo. The folder holds asb.py, README.md, examples/ and tests/ -- no tools/
parent, and __pycache__/*.pyc are excluded.
Windows (make-release.ps1): stage the folder into the explicit allowlist just
before zipping, and add it to the required-artifacts tripwire.
macOS (make-release-mac.sh): publish_package stages the already-stapled .app
plus the SDK and archives the staging dir contents (ditto -c -k without
--keepParent) so both land at the zip root; ditto copies the bundle verbatim,
preserving its signature and notarization staple.
The SDK is added only at the final packaging step, after the binaries and
drivers are signed and (macOS) the app is notarized and stapled.
Bring macOS to parity with the Windows display feature: open/close/poll the VM's
display window over the API (POST/DELETE/GET /v1/vms/{name}/display, plus
displayOpen on every status object), so a script can show the same view the GUI's
Connect button does. Validated live on Apple silicon.
The GUI already creates the per-VM VZVirtualMachineView window on the Running
transition (gated off under --headless); the daemon now opens it on an explicit
request after two gates:
- A-gate: asb_mac_have_gui_session() (CGSessionCopyCurrentDictionary /
kCGSessionOnConsoleKey) -- a daemon with no console Aqua session (launchd /
SSH) refuses with 409 no_display instead of spawning an invisible window;
- readiness: running && agent_online. macOS has no frame channel or display
driver (the view binds the in-process VM framebuffer directly), so there is
nothing to probe -- unlike the Windows/Linux idd_status latch.
New core API asb_mac_open_display / asb_mac_close_display (asb_core_mac.m); new
codes BACKEND_ERR_NO_DISPLAY / BACKEND_ERR_NOT_READY (asb_types.h).
run_headless swaps the bare CFRunLoopRun() for [NSApp run] (Accessory policy)
ONLY in a console GUI session -- the window needs the window server; otherwise it
keeps the bare CFRunLoop so the daemon still runs headless over SSH/launchd. Both
loops drain the main dispatch queue, so every VZ op and the SIGINT/SIGTERM sources
are unaffected; cleanup_and_exit exit()s directly, so no quit-path change. The
spike the design flagged as unverifiable without a Mac was confirmed here: VZ
main-queue dispatches still fire under [NSApp run], and windows open, take focus,
and deliver windowWillClose:.
Close tracking avoids a mid-close use-after-free: VzDisplayWindow gains a
userClosed flag set in windowWillClose:; displayOpen reads display && !userClosed,
and open_display reaps a self-(X)-closed window on the next request (off the WM
close stack), mirroring the Windows reap. showDisplay also calls
activateIgnoringOtherApps: so the Accessory daemon's window comes to the front.
tests: vm_lifecycle.py now runs the display open/close check on macOS too (it
self-skips on 409 no_display or a Linux guest whose agent predates idd_status).
README documents the macOS display support (matrix row, the console-session
A-gate, and the running && agentOnline readiness).
Remove tools/headless-api/PLAN.md and display-plan.md: these were the design /
decision logs for the headless port and the display feature, both now shipped and
validated. Their dangling references in README.md and headless.m comments are
cleaned up in the same change.
edit() honors ramMb/cpuCores/gpuMode/networkMode on a stopped VM (name is fixed at create, other keys ignored; 409 if running); snap_take() requires the VM stopped (409 vm_running).
display-plan.md: the Linux display half is now implemented and validated (confirmed on fresh Linux VMs built from the pushed branch, where the agent's idd_status latches readiness and the display opens).
README.md: note that VMs should be created sequentially -- concurrent creates can race on the VHDX-build prefetch/staging and a build may fail (iso-patch.exe exit 1) and be removed.
Add POST/DELETE/GET /vms/{name}/display plus the open_display/close_display/display_status/display_ready SDK methods, so a script can open and close a VM's display window like the GUI's Connect button.
Readiness is a latched flag, not a frame-channel probe: connecting to the guest frame service is itself becoming the display's single consumer, so polling it would steal the slot and blank the display. The guest agent reports the display driver state over the agent channel (idd_status) and the host latches it as idd_ready; asb_vm_idd_ready = running && agent_online && idd_ready. The Linux agent reports idd_status:ok once the user session is up (Mutter compositing).
The display window is daemon-owned (per-VM table keyed by unique_id), gated on a visible window station, and self-tears-down on the X button; displayOpen is read from the live window state, so it is honest however the window closed.
tools/headless-api/display-plan.md: design for opening/closing a VM display window
through the headless API (POST/DELETE /v1/vms/{name}/display). Windows is implementable
(vm_display_idd_create is self-contained); macOS is design-only and unvalidated (it
swaps the daemon run loop to AppKit and needs a Mac to verify). Access model: bearer
token for auth + a runtime "can this session render a window" gate; no loopback/peer
detection.
asb.py: resolve the support directory, daemon launch hint, and SSH key path per
platform (%ProgramData%\AppSandbox on Windows, ~/Library/Application Support/
AppSandbox on macOS) so the same SDK drives either daemon from its host.json.
tests: the suite is now capability-gated and runs on either host. helpers.py
adds IS_MAC, the per-platform VM/disk paths, the macOS IPSW location, a key-auth
login helper (chmod on mac), and wait_progress -- a stall-based wait that watches
the VM's reported progress/status instead of a fixed deadline, so a slow install
or IPSW download doesn't false-fail. run_all.py launches the macOS suite as two
concurrent VMs (one downloading a fresh IPSW, one from cache) sized 4096 MB / 2
cores / 64 GB, with a leftover sweep and IPSW housekeeping. The lifecycle and
host/validation tests assert 501 for snapshots/templates on macOS and run the
Windows-only template build only where supported.
docs: README gains a platform-support matrix (which guest OSes and host features
each platform supports) and the macOS sudo/SUDO_USER launch model alongside the
Windows instructions. PLAN.md records the macOS port, the Windows
cleanup-on-exit fix, the guest-agent threading fix, and the startup fail-fast /
GUI polish.
The macOS guest agent ran ssh_deploy_key inline on its single command socket.
On a cold first boot the find_primary_user lookup (getpwent/OpenDirectory) can
take several seconds, during which the agent could not service heartbeats or
replies and the host would tear the connection down -- the key deploy reported
as failed even though it eventually succeeded.
Run the deploy on a detached worker thread (spawn_deploy_key) so the accept loop
keeps responding. send_line takes a mutex so a worker's reply can't interleave
its bytes with the loop's writes on the shared socket, and a connection
generation counter (g_conn_gen, bumped on every connection start/end) lets a
worker tell its connection is gone -- and the fd possibly reused by a new client
-- and suppress a stale write via send_line_gen. deploy_ssh_key now returns
failure (reports ssh_key_failed) when the chown of authorized_keys fails instead
of falsely reporting success.
Headless mode: `appsandbox.exe --headless` runs a single-owner daemon that hosts
the core over a loopback HTTP/JSON API (http.sys) with a host.json discovery
file. A dependency-free Python SDK, examples, and a self-contained regression
suite live under tools/headless-api/ (PLAN.md = design rationale).
The API mirrors the GUI: create/start/shutdown/stop/delete/edit, snapshots and
branches (incl. base branches via the new asb_snap_base_branch_count), templates,
sshInfo, an SSE /v1/events stream, GUI-mirrored input validation, a startup
fail-fast (admin / Win11 build / VirtualMachinePlatform), and a single-instance
mutex shared with the GUI so only one of GUI/headless runs at a time.
SSH key deploy: sshDeployKey (requires sshEnabled) has the core generate an
AppSandbox ed25519 keypair once and the guest agent write the public key into
authorized_keys -- Windows administrators_authorized_keys (+ ACL), Linux/macOS
~/.ssh -- reporting a new ssh_key_deployed status (sshState 4). The GUI SSH
button and the SDK use the key for password-less login. Verified end-to-end on
Windows.
The Linux guest builds its agent/driver source from this branch's GitHub repo;
asb_core.c points the prefetch at "headless-api" (revert to "main" before merge,
see PLAN.md).
Deploy Mesa's standalone opengl32 trio (opengl32.dll + gallium_wgl.dll + z-1.dll,
the gallium d3d12 driver + WGL frontend) system-wide into Windows GPU-PV guests,
so apps get hardware OpenGL 4.6 via D3D12 (WARP software 4.6 when there is no GPU).
OpenCL (OpenCLOn12), Vulkan (Dozen) and dxil.dll are unchanged.
- tools/win-mesa-gl: vendored prebuilt trio for x64 + arm64 (Mesa 26.1.2,
EV-signed) plus build-mesa.cmd, aarch64-cross.txt, sign-mesa.ps1, and README.
- AppSandbox.vcxproj: stage the build's-arch trio into resources\win-mesa-gl
per-config, alongside the WebView2Loader copy.
- asb_core.c: prepare_gl_layers_share stages the trio into the AppSandbox.GlLayers
Plan 9 share.
- agent.c: gl_provision deploys the trio into the guest's System32 (take ownership,
rename the in-use opengl32 aside, copy in; re-applied every boot to self-heal),
and keeps the dxil.dll staging + Khronos OpenCL/Vulkan ICD registrations.
- Remove the appsandbox_gl_shim project (AppSandbox.sln, tools/gl-shim).
- .gitignore: un-ignore the vendored prebuilt Mesa binaries.
- SIGNING.md: drop the shim; the Mesa trio is pre-signed via sign-mesa.ps1.
iso-patch builds an arm64 Ubuntu VHDX: the grub/shim/EFI, apt, dxcore and
mesa paths are selected from the build arch at compile time via the new
target_arch.h, and the arm64 serial console is ttyAMA0 (PL011) not ttyS0.
x64 behavior is unchanged. (Windows-on-ARM support landed in the previous
commit.)
Known issue: an arm64 Ubuntu 26.04 LTS guest boots very slowly on this
platform and is not usable in practice yet — Windows is the supported
arm64 guest for now. The 26.04 generic kernel is built
CONFIG_BPF_JIT_ALWAYS_ON (+RODATA_FULL), so systemd's constant BPF churn
JIT-allocs and frees executable memory, and each free issues a broadcast
cross-CPU TLB/cache-maintenance op (TLBI ..IS). arm64 has no Hyper-V
paravirt TLB flush, so the hypervisor traps every one and the cost scales
with vCPU count; the host's experimental Insider Hyper-V services them
slowly. Fewer vCPUs or a stable Hyper-V build mitigate it.
Also trim the previous commit's ARM code comments down to the essential
architectural differences.
Build, EV-sign, Microsoft attestation-sign, and package AppSandbox for
Windows on ARM with feature parity to x64 except the vTPM and guest-state
isolation (neither is supported by ARM Windows HCS). Target architecture is
resolved at compile time (ASB_IS_ARM64 in disk_util.h); the supported pairing
is x64 build/host/guest and ARM64 build/host/guest, so one flag drives every
architecture decision. x64 behavior is unchanged throughout.
Runtime:
- hcs_vm.c: omit SecuritySettings (vTPM + GuestStateOnly isolation) on ARM;
Secure Boot is retained.
- disk_util.c: emit the arch token in every autounattend/unattend
processorArchitecture attribute; on ARM, bypass the Win11 Setup hardware
checks via the HKLM\SYSTEM\Setup\LabConfig keys across all VM-creation
paths; select the arch-correct OpenSSH Server MSI.
- d3dlayers.c / agent.c: resolve, download, and stage the arm64 D3D mapping
layers for GPU-PV; stage the Vulkan ICD manifest under the arch-neutral
name dzn_icd.json.
Build and release pipeline:
- AppSandbox.sln: add the missing AppSandboxPackage Release|ARM64 Build.0 so
the VS GUI actually runs sign+package for the ARM64 config.
- AppSandboxPackage.vcxproj: run make-release.ps1 for Release on x64 or ARM64,
passing -Platform $(Platform).
- make-release.ps1 / sign-drivers.ps1: -Platform ARM64 selects bin\Release-ARM64,
the ARM64 attestation profile, and the AppSandbox-<ver>-win-arm64.zip name.
Sanitize LIB before Add-Type so the .NET Framework C# compiler does not fail
(CS1668) on the non-existent atlmfc\lib\ARM64 path the VS ARM64 build adds to
LIB.
- Directory.Build.props: disable ApiValidator only when cross-building ARM64 on
a non-ARM64 host (the x86 ApiExtractor cannot read ARM64 PEs); Microsoft
re-runs full API validation on every attestation submission.
- Package.vcxproj: stage the vendored ARM64 devcon.exe into the ARM64 drivers
output, mirroring the x64 devcon copy.
- vendor/devcon/arm64/devcon.exe: add the vendored ARM64 devcon.exe, un-ignored
in .gitignore mirroring the existing vendor/webview2/arm64 precedent.
- LICENSE: MIT, Copyright (c) 2026 James Stringer.
- THIRD-PARTY-NOTICES.md: scopes the MIT grant around bundled third-party
components, documenting each one's license/copyright:
dxgkrnl (Microsoft WSL2 GPU driver) GPL-2.0; iso-patch/engine/xz 0BSD;
wsl-mesa prebuilt (Mesa 25.3.6) MIT; WDF SDK, WebView2 SDK, DevCon (Microsoft).
- tools/linux/dxgkrnl/COPYING: verbatim GPL-2.0 text shipped with the GPL'd
driver source.
- tools/linux/wsl-mesa/prebuilt/MESA-LICENSE.txt: Mesa's headline MIT license,
shipped next to the prebuilt binary.
- README: license section points to LICENSE + THIRD-PARTY-NOTICES.
- Info.plist: copyright string now names the holder and notes MIT.
The asb_drm Linux kernel module keeps its existing dual (GPL-2.0 OR MIT)
licensing; vendored sources keep their own SPDX headers.
- C8 StageEntry stored ARC __strong NSString members in an NSValue (raw byte
copy, no retain), so ARC over-released them each loop iteration ->
double-free/UAF in the privileged stage path; mark __unsafe_unretained
(the source strings outlive the command via the outer autorelease pool).
- L10 resumeDataFromSidecarAt: `raw.length < 8 + n` overflowed for n near
UINT64_MAX, bypassing the guard and throwing NSRangeException; compare
with subtraction (`n > raw.length - 8`).
- C3 appsandbox-clipboard.m: inbound `format_len` was unbounded, so a
32-bit `format_len + 1` could wrap to malloc(0) then read/write ~4GB;
bound it against CLIP_MAX_PAYLOAD and allocate in size_t. Both sites.
- M13 appsandbox-agent.m: mute/unmute (and shutdown) fork+exec children were
never reaped, accumulating zombies; ignore SIGCHLD so Darwin auto-reaps.
- M19 appsandbox-clipboard.m (guest half): handle the new symlink entry type
-- recreate links with symlink() instead of treating them as dirs.
- C10 appsandbox-clipboard.c: the host->guest INCR accumulator doubled
accum_cap *before* realloc, so on realloc failure it memcpy'd into the
un-enlarged buffer (heap overflow). Grow into a temp, commit the cap
only on a successful realloc, and append only when the buffer has room.
- L11 appsandbox-clipboard.c: reject FORMAT_LIST frames with data_size<4
before reading the 4-byte count (OOB read; Linux analog of H4/C9).
- L18 appsandbox-clipboard.c: host->guest folder paste flattened — the
text/uri-list builder listed every nested file and no directory, so the
file manager pasted the files flat with no folder. List top-level items
only (a top-level dir contributes its own URI; nested entries none),
guarded on the dir actually existing.
- L12 appsandbox-display.c: use-after-free reading p->plane_id after
drmModeFreePlane(p) in cursor_init; log the cached cur->plane_id.
- L13 appsandbox-audio.c: idle silence paced at 10ms < the ~21.3ms packet
duration, overrunning the host render ring; pace at the true packet
duration (FRAMES_PER_PACKET * 1000000000 / SAMPLE_RATE).
Built in-VM and validated on a live Ubuntu VM (folder/sub-item paste;
audio/display/clipboard daemons healthy after redeploy).
- M15 wavminiport.cpp: reject PinId >= WaveDescriptor->PinCount in the
proposed-format property handler.
- M16 wavstream.cpp: guard divide-by-zero on m_ulDmaMovementRate and
nAvgBytesPerSec in buffer allocation and presentation-position.
- L14 topology.cpp: set PropertyRequest->ValueSize in both jack
description GET paths.
- L15 adaptercommon.cpp: balance the adapter instance count on the
allocation-failure path.
- C1/C2/H7/H8/H9 engine/squashfs.c: validate block_size (non-zero, power
of two, == 1<<block_log, <= 1 MiB); bound walk_directory to the dir
stream; bound the inode block_sizes[] list and each per-block write to
the output buffer.
- C6/H6 engine/ext4.c: cap inodes_per_group at BLOCK_SIZE*8 and size the
inode table by whole blocks so the bitmap/table buffers can't overrun.
- C7 + M8-sibling iso-patch.c: bound qcow2 l1_size (reject under/oversize,
avoid 32-bit length overflow) and propagate IStream::Read failures.
- H10/H11 prefetch_build_deps.c: reject an over-long Filename and clamp
WriteFile lengths derived from snprintf return values.
- L17 prefetch_wsl_deps.c: route download/extract/copy failures through a
cleanup label that removes the unique temp directory.
- M10/M11 ubuntu_vhdx.c: abort (don't finalize a partial VHDX) when the
squashfs walk reports errors; stop and join workers if a thread fails
to start so they can't touch the freed stack-local pipeline.
- M14 agent.c: serialize the clipboard helper/reader process globals with
a critical section; the SCM control-handler and monitor threads could
otherwise close the same process HANDLE twice.
- L9 agent.c: zero-init and check MultiByteToWideChar for the GPU-copy
destination path before use.
- C9 appsandbox-clipboard.c: reject a FORMAT_LIST name_len that runs past
the received buffer.
- M12 appsandbox-displays.c: gate and clamp the snprintf accumulation so
the resolution list cannot overflow buf.
- C4/C5 p9copy.c: clamp the server-advertised msize to the fixed client
recv buffer, and validate Rread/Rreaddir count against the received
payload before using it.
Make Directory.Build.props the single place a version is ever set on macOS,
matching how Windows derives its version from the same file.
- Add a 'Stamp version' Run Script build phase to the AppSandbox and
AppSandboxCore targets. It runs on every build (Xcode GUI, xcodebuild,
Archive) and writes CFBundleShortVersionString / CFBundleVersion into the
built bundle's Info.plist from Directory.Build.props.
- A build phase (not an xcconfig) is used because xcconfig is read before build
phases run, so a generated xcconfig couldn't version the current build.
- tools/sign/asb-version.sh reads the version; tools/sign/stamp-version.sh is
the build-phase wrapper. make-release-mac.sh uses asb-version.sh for the zip
name.
- Remove the committed, generated version.xcconfig and sync-mac-version.sh;
Local.xcconfig no longer includes version.xcconfig.
A fresh clone builds (ad-hoc or Personal Team, Xcode GUI or CLI) with the
version stamped, and nothing but Directory.Build.props defines it.
- Windows (make-release.ps1): add an $OS param (default 'win') used only in the
zip filename; $Platform is untouched so MSBuild is unaffected. Produces
AppSandbox-<ver>-win-x64.zip. SIGNING.md updated to match.
- macOS (make-release-mac.sh + mac-release-config.json): name the package
<productName>-<version>-<os>-<platform>.zip from productName/os/platform plus
the version derived from Directory.Build.props. Produces
AppSandbox-<ver>-mac-arm64.zip. SIGNING_MAC.md updated.
- xcconfig-based signing: Local.xcconfig (committed) pulls in a git-ignored
LocalUser.xcconfig for the maintainer's team/Developer ID identity, so the
public project builds ad-hoc/Personal-Team with no secrets committed.
- Release config in project.pbxproj: Developer ID + hardened runtime +
secure timestamp + no get-task-allow; arm64; macOS 26 deployment target
(guest helpers stay 13.0); scheme defaults to Release.
- Version derives from Directory.Build.props via sync-mac-version.sh ->
version.xcconfig (the same single source of truth Windows uses).
- make-release-mac.sh: build -> verify-sign -> notarize (recursive, covers
the framework + iso-patch-mac + appsandbox-agent + appsandbox-clipboard) ->
staple -> Gatekeeper check -> publish a zip-only release_package/.
- Config split mirrors the Windows convention: mac-release-config.json
(committed, no secrets) + mac-signing.local.json (git-ignored creds).
- tools/sign/SIGNING_MAC.md documents the whole process end to end.
YubiKey-gated signing/packaging driven from the VS Release|x64 build:
- make-release.ps1 / sign-drivers.ps1: EV-sign all binaries + driver
.sys/.dll, submit drivers to Microsoft for attestation in parallel,
deposit the returned MS-signed drivers into bin\Release\drivers, and
build a flat AppSandbox-<ver>-x64.zip. Token presence is detected via
WinSCard + a silent NCrypt key-open, so an unplugged or unrelated card
never triggers an "insert smart card" dialog; no token => test-sign
only, no packaging.
- AppSandboxPackage.vcxproj runs it as a post-build step.
- Directory.Build.props/targets + build/version.rc: single-source version
stamped into every binary and into the driver INFs (StampInf).
- VDD INF: pull WUDFRd via Include/Needs + Win11 decoration to pass
infverif /h for Partner Center submission.
- Default new Windows VMs to Test Mode off (shipped drivers are MS-signed).
- SIGNING.md documents the git-ignored partner-center.local.json that you
must create with the Entra API secrets for driver attestation.
Partner Center credentials stay in git-ignored tools/sign/*.local.* files.
Provision the Microsoft D3D mapping layers (OpenGLOn12, OpenCLOn12, Dozen)
into Windows GPU-PV guests so apps get hardware-accelerated GL/CL/Vulkan on
top of the projected host D3D12 driver.
- d3dlayers.c/.h: fetch the D3DMappingLayers package from Microsoft on demand
over WinHTTP (displaycatalog -> FE3 -> CDN), extract with in-box tar, cache
beside the exe. Sets the Dozen ICD manifest library_path to the absolute
guest path (the Vulkan loader's LoadLibraryEx fallback uses
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, which requires a fully-qualified path).
- fe3_templates.h: SOAP envelopes for the FE3 client.
- gpu_enum.c/.h: add the AppSandbox.GlLayers Plan 9 share (guest path
C:\Windows\AppSandbox\d3dlayers).
- asb_core.c: stage the layers + OpenGL shim and append the share before VM start.
- disk_util.c: skip the GlLayers share during build-time image staging.
- agent.c: gl_provision() copies dxil.dll to System32 and registers the ICDs
(OpenGL via AppInit_DLLs shim, OpenCL + Vulkan via Khronos registry keys)
after the GPU driver copy, before the device disable/enable cycle.
- tools/gl-shim: appsandbox_gl_shim.dll, an AppInit_DLLs shim that IAT-hooks
gdi32!D3DKMTQueryAdapterInfo to route opengl32 to OpenGLOn12.dll.
- AppSandbox.sln / AppSandboxCore.vcxproj: build wiring.
The resized differencing-create helper was only used by the removed
cloud-image flow (it stretched a small cached cloud-image base to the
user-requested disk size). Confirmed zero callers repo-wide. Delete the
definition (disk_util.c) + declaration (disk_util.h) and drop the comment
reference in iso-patch.c's raw_to_vhdx. The plain vhdx_create_differencing
stays — still used by the template + snapshot paths. Both binaries rebuild
clean.
The cloud-image install flow is gone; reword the last comments that still
referenced it:
- asb_core.c: "Linux cloud-image creates" -> "Linux ISO creates"
- disk_util.c: drop the "Ubuntu cloud-image parent ~2.5 GB" rationale
from vhdx_create_differencing_resized (now describes the generic
inherit-then-resize behavior)
- disk_util.h: stage_linux_agent_and_extras doc no longer says "same
content the cidata builder used to ship"
- appsandbox-agent.c: "autoinstall lets the user pick" -> "the create-VM
modal lets the user pick"
Line-by-line verification of comments against the code they document,
across the Linux creation path and the guest agent. Comment text only —
no code or log-string changes.
prefetch_build_deps.c: header still described the old Packages.gz +
tar.exe gunzip + atomic-rename-staging design; rewrite to match the
actual Packages.xz + in-process xz-embedded decode + direct-to-out_dir
flow, and correct the "~125 KiB" figure.
appsandbox-agent.c: gdm is a system user with UID <1000, not in the
1000-65534 range — reword the uid_is_real_user rationale; the netplan
"detect the active renderer" comment actually probes the NetworkManager
service, reword accordingly.
appsandbox-display.c: cursor-cadence comment said 30 fps (TARGET_FPS is
60); card-selection comment claimed a connected-output check on the
driver-name priority loop (that check is only in the legacy fallback).
appsandbox-audio.c: wire-protocol doc block said bits=16/block_align=4
(the stream is 32-bit / 8-byte align); snd-aloop subdev comment.
Verified accurate (no change): prefetch_repo.c, prefetch_wsl_deps.c,
appsandbox-clipboard.c, appsandbox-input.c, and the Linux comments in
hcs_vm.c (ComPorts/VideoMonitor/Secure Boot) and vm_agent.c (set_ip,
os_shutdown).
Delete the abandoned cloud-image/cidata Linux install path (zero live
callers — linux_create_thread builds the VHDX directly from the ISO):
- disk_util.c: drop iso_create_resources_ubuntu, generate_autoinstall_*,
generate_network_config, generate_ubuntu_setup_sh, find_repo_root +
copy helpers, the DEAD_ #if0 block, and the entire qcow2/VHD/inflate/
cloud-image converter incl. ensure_ubuntu_cloud_image_cached
(4492 -> 2315 lines). Shared helpers kept (create_iso_from_dir,
remove_staging_dir, unix_password_hash, stage_linux_agent_and_extras).
- disk_util.h: drop the two now-dead prototypes.
Rename the guest first-boot identifier poc-firstboot -> appsandbox-firstboot
(service/script/marker) in ubuntu_vhdx.c and reword stale "POC gpt.c"/
"f361def"/"old flow"/"subiquity"/"cidata" comment references to describe
the current direct-ISO->VHDX flow.
Fix factually-wrong comments found in a line-by-line audit:
- asb_core.c: NAT comment claimed DHCP; it's a static IP baked into the
HCN endpoint + pushed by the agent. SHA-512-crypt / Secure Boot /
create-path comments reworded off the removed flow.
- appsandbox-agent.c: gdm UID-range claim (gdm is <1000); netplan
"renderer detect" actually probes the NetworkManager service.
- appsandbox-display.c: cursor cadence said 30fps (TARGET_FPS is 60);
card-selection "connected output" claim corrected (driver-name match).
- appsandbox-audio.c: wire-proto doc said 16-bit/4-align (it's 32/8);
snd-aloop subdev comment.
Move the internal IMPLEMENTATION_PLAN.md design doc out of the repo
(it now lives in the gitignored docs/).