From bd4015ef156070731b102faffa5dadf9db920859 Mon Sep 17 00:00:00 2001 From: Mathias Hellevang Date: Fri, 10 Jul 2026 15:59:58 +0200 Subject: [PATCH 1/2] fix(build): gate SCSI HBA behind opt-in build flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ASFWSCSIControllerService personality instantiates a kernel-side IOUserSCSIParallelInterfaceController as soon as the FireWire card matches at boot. On machines where AMFI enforces entitlements, the ad-hoc-signed dext carrying the restricted scsicontroller entitlement is killed at launch, and the orphaned kernel stub panics the kernel about a minute after boot — a boot loop, with no device attached. Default build now ships neither the personality nor the entitlement: - ASFW_ENABLE_SCSI=NO (pbxproj) selects the entitlements file and preprocesses the SCSI personality out of Info.plist - ./build.sh --scsi opts in (xcodebuild ASFW_ENABLE_SCSI=YES) - sign.sh picks the matching entitlements file by detecting the personality in the built dext, so signature and plist never diverge Verified both flag states: built artifacts carry personality and scsicontroller entitlement together or not at all. --- ASFW.xcodeproj/project.pbxproj | 20 +++++++++++++++-- ASFWDriver/ASFWDriver+SCSI.entitlements | 16 ++++++++++++++ ASFWDriver/ASFWDriver.entitlements | 2 -- ASFWDriver/Info.plist | 2 ++ README.md | 29 +++++++++++++++++++++++++ build.sh | 15 +++++++++++++ sign.sh | 16 ++++++++++++++ 7 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 ASFWDriver/ASFWDriver+SCSI.entitlements diff --git a/ASFW.xcodeproj/project.pbxproj b/ASFW.xcodeproj/project.pbxproj index 60840bcc..168b82fc 100644 --- a/ASFW.xcodeproj/project.pbxproj +++ b/ASFW.xcodeproj/project.pbxproj @@ -596,9 +596,14 @@ isa = XCBuildConfiguration; buildSettings = { AD_HOC_CODE_SIGNING_ALLOWED = YES; + ASFW_DEXT_ENTITLEMENTS_NO = ASFWDriver/ASFWDriver.entitlements; + ASFW_DEXT_ENTITLEMENTS_YES = "ASFWDriver/ASFWDriver+SCSI.entitlements"; + ASFW_ENABLE_SCSI = NO; + ASFW_INFOPLIST_DEFS_NO = ""; + ASFW_INFOPLIST_DEFS_YES = "ASFW_ENABLE_SCSI=1"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++23"; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = ASFWDriver/ASFWDriver.entitlements; + CODE_SIGN_ENTITLEMENTS = "$(ASFW_DEXT_ENTITLEMENTS_$(ASFW_ENABLE_SCSI))"; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -612,6 +617,9 @@ ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ASFWDriver/Info.plist; + INFOPLIST_OTHER_PREPROCESSOR_FLAGS = "-traditional"; + INFOPLIST_PREPROCESS = YES; + INFOPLIST_PREPROCESSOR_DEFINITIONS = "$(ASFW_INFOPLIST_DEFS_$(ASFW_ENABLE_SCSI))"; LIBRARY_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = 1.0; OTHER_LDFLAGS = "-framework SCSIControllerDriverKit"; @@ -631,9 +639,14 @@ isa = XCBuildConfiguration; buildSettings = { AD_HOC_CODE_SIGNING_ALLOWED = YES; + ASFW_DEXT_ENTITLEMENTS_NO = ASFWDriver/ASFWDriver.entitlements; + ASFW_DEXT_ENTITLEMENTS_YES = "ASFWDriver/ASFWDriver+SCSI.entitlements"; + ASFW_ENABLE_SCSI = NO; + ASFW_INFOPLIST_DEFS_NO = ""; + ASFW_INFOPLIST_DEFS_YES = "ASFW_ENABLE_SCSI=1"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++23"; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = ASFWDriver/ASFWDriver.entitlements; + CODE_SIGN_ENTITLEMENTS = "$(ASFW_DEXT_ENTITLEMENTS_$(ASFW_ENABLE_SCSI))"; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -647,6 +660,9 @@ ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ASFWDriver/Info.plist; + INFOPLIST_OTHER_PREPROCESSOR_FLAGS = "-traditional"; + INFOPLIST_PREPROCESS = YES; + INFOPLIST_PREPROCESSOR_DEFINITIONS = "$(ASFW_INFOPLIST_DEFS_$(ASFW_ENABLE_SCSI))"; LIBRARY_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = 1.0; OTHER_LDFLAGS = "-framework SCSIControllerDriverKit"; diff --git a/ASFWDriver/ASFWDriver+SCSI.entitlements b/ASFWDriver/ASFWDriver+SCSI.entitlements new file mode 100644 index 00000000..b9057caa --- /dev/null +++ b/ASFWDriver/ASFWDriver+SCSI.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.developer.driverkit + + com.apple.developer.driverkit.transport.pci + + com.apple.developer.driverkit.transport.pci.bridge + + com.apple.developer.driverkit.family.audio + + com.apple.developer.driverkit.family.scsicontroller + + + diff --git a/ASFWDriver/ASFWDriver.entitlements b/ASFWDriver/ASFWDriver.entitlements index e76369c5..f90ff122 100644 --- a/ASFWDriver/ASFWDriver.entitlements +++ b/ASFWDriver/ASFWDriver.entitlements @@ -10,8 +10,6 @@ com.apple.developer.driverkit.family.audio - com.apple.developer.driverkit.family.scsicontroller - \ No newline at end of file diff --git a/ASFWDriver/Info.plist b/ASFWDriver/Info.plist index 95c77060..d8d388bf 100644 --- a/ASFWDriver/Info.plist +++ b/ASFWDriver/Info.plist @@ -101,6 +101,7 @@ IOUserServerOneProcess +#if ASFW_ENABLE_SCSI ASFWSCSIControllerService CFBundleIdentifier @@ -124,6 +125,7 @@ IOUserServerOneProcess +#endif OSBundleUsageDescription AS FireWire Driver diff --git a/README.md b/README.md index 31f3b643..84f27d56 100644 --- a/README.md +++ b/README.md @@ -355,6 +355,29 @@ NOTE: You need an Apple Developer account (paid) and appropriate entitlements Enabling `systemextensionsctl developer on` is recommended — it allows installing system extensions from the build +### SCSI HBA (SBP-2 scanners/disks) — opt-in + +The SCSI HBA (`ASFWSCSIControllerService`, for SBP-2 devices such as FireWire film +scanners and disks) is **excluded from the default build**. It requires the restricted +`com.apple.developer.driverkit.family.scsicontroller` entitlement, and its Info.plist +personality instantiates a kernel-side `IOUserSCSIParallelInterfaceController` as soon +as the FireWire card is matched at boot. On a machine where AMFI enforces entitlements +(SIP enabled, or partially enabled), the dext is killed at launch and the orphaned +kernel stub **panics the kernel about a minute after boot** — a boot loop, with no +device attached. The default build therefore carries neither the personality nor the +entitlement and cannot trigger this. + +To include the HBA, opt in explicitly (only on a machine that runs with SIP disabled): + +```bash +./build.sh --scsi # or: xcodebuild … ASFW_ENABLE_SCSI=YES +./sign.sh # picks the +SCSI entitlements automatically +``` + +If a machine ever ends up in this panic loop: boot into Recovery, `csrutil disable`, +boot normally, uninstall the extension +(`systemextensionsctl uninstall - net.mrmidi.ASFW.ASFWDriver`), then re-enable SIP. + ## Installing a prebuilt build (testers) If you want to test ASFireWire without building it yourself, tagged releases attach a @@ -366,6 +389,12 @@ for experimental testing only — not general use. > Only do this on a machine you are comfortable using for testing, and re-enable SIP > (`csrutil enable`) when you are done. The build is unsigned/un-notarized and provided > as-is; run it only if you understand and accept that. +> +> **Uninstall the extension _before_ re-enabling SIP.** With SIP back on, AMFI refuses +> to launch the ad-hoc-signed dext; an installed build that includes the SCSI HBA then +> leaves an orphaned kernel-side SCSI stub behind at every boot, which can panic the +> machine into a boot loop (recovery: Recovery → `csrutil disable` → boot → uninstall +> → `csrutil enable`). **Requirements:** an Apple Silicon Mac running macOS 26 (Tahoe), and FireWire hardware (a PCIe FireWire/OHCI card, or an Apple Thunderbolt-to-FireWire adapter). diff --git a/build.sh b/build.sh index 72d16847..036f28a3 100755 --- a/build.sh +++ b/build.sh @@ -49,6 +49,12 @@ SWIFT_TEST_ONLY=false # When true, generate Swift code coverage SWIFT_COVERAGE=false SWIFT_COVERAGE_LCOV="${BUILD_DIR}/swift_coverage.lcov" +# When true, include the SCSI HBA: the ASFWSCSIControllerService personality in +# Info.plist and the restricted scsicontroller entitlement in the dext signature. +# Default OFF: an ad-hoc-signed dext carrying that entitlement is killed by AMFI +# on machines that enforce it, and the orphaned kernel-side +# IOUserSCSIParallelInterfaceController stub then panics the kernel at boot. +ENABLE_SCSI=false usage() { cat <&1 | tee "${RAW_LOG}" diff --git a/sign.sh b/sign.sh index ec98a24a..47555264 100755 --- a/sign.sh +++ b/sign.sh @@ -46,6 +46,22 @@ ok() { echo "[OK] $*"; } DEXT_PATH="$(find "${APP_PATH}/Contents/Library/SystemExtensions" -maxdepth 1 -name '*.dext' -print -quit 2>/dev/null || true)" [[ -n "${DEXT_PATH}" ]] || { err "No .dext found under ${APP_PATH}/Contents/Library/SystemExtensions"; exit 1; } +# The entitlement set must match what the build put in Info.plist: a dext whose +# signature carries the restricted scsicontroller entitlement is killed by AMFI +# on enforcing machines, and the orphaned kernel-side SCSI stub then panics the +# kernel at boot. Detect the SCSI personality (present only when the app was +# built with ASFW_ENABLE_SCSI=YES / ./build.sh --scsi) and pick the matching file +# — never sign the SCSI entitlement onto a build without the personality, or +# vice versa. +if /usr/libexec/PlistBuddy -c "Print :IOKitPersonalities:ASFWSCSIControllerService" \ + "${DEXT_PATH}/Info.plist" >/dev/null 2>&1; then + DEXT_ENTITLEMENTS="ASFWDriver/ASFWDriver+SCSI.entitlements" + log "SCSI HBA personality detected — signing with ${DEXT_ENTITLEMENTS}" + [[ -f "${DEXT_ENTITLEMENTS}" ]] || { err "Missing ${DEXT_ENTITLEMENTS}"; exit 1; } +else + log "No SCSI HBA personality — signing with ${DEXT_ENTITLEMENTS} (audio only)" +fi + # Sign inner-first: the dext, then the enclosing app. log "Signing dext: ${DEXT_PATH}" codesign --force --sign - --timestamp=none \ From 8b770d72cc910546c5200a5dc4e8d1e9e6d0c0b2 Mon Sep 17 00:00:00 2001 From: Mathias Hellevang Date: Fri, 10 Jul 2026 17:06:34 +0200 Subject: [PATCH 2/2] docs(scsi): correct panic mechanism, warn --scsi is not cold-boot-safe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README and build.sh attributed the boot panic solely to the AMFI-kill path ("only on a machine that runs with SIP disabled"), implying SIP-off machines are safe. They are not: with the dext running, the HBA reports target 0 present unconditionally and the probe INQUIRY is held with no deadline waiting for an SBP-2 login that never arrives when no SBP-2 device is on the bus — the stalled target registration trips the same 60 s registry busy-timeout panic (IOService.cpp:5986) regardless of SIP state. Document both paths, warn that --scsi builds are not cold-boot-safe without a powered-on SBP-2 device until the HBA-side fix (create the target at SBP-2 login instead of boot) lands, and note that a stalled probe on a running system can still panic later on adapter unplug or extension teardown. --- README.md | 41 ++++++++++++++++++++++++++++++----------- build.sh | 13 +++++++++---- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 84f27d56..6fa26166 100644 --- a/README.md +++ b/README.md @@ -358,23 +358,42 @@ Enabling `systemextensionsctl developer on` is recommended — it allows install ### SCSI HBA (SBP-2 scanners/disks) — opt-in The SCSI HBA (`ASFWSCSIControllerService`, for SBP-2 devices such as FireWire film -scanners and disks) is **excluded from the default build**. It requires the restricted -`com.apple.developer.driverkit.family.scsicontroller` entitlement, and its Info.plist -personality instantiates a kernel-side `IOUserSCSIParallelInterfaceController` as soon -as the FireWire card is matched at boot. On a machine where AMFI enforces entitlements -(SIP enabled, or partially enabled), the dext is killed at launch and the orphaned -kernel stub **panics the kernel about a minute after boot** — a boot loop, with no -device attached. The default build therefore carries neither the personality nor the -entitlement and cannot trigger this. - -To include the HBA, opt in explicitly (only on a machine that runs with SIP disabled): +scanners and disks) is **excluded from the default build**, because shipping it by +default can **panic the machine into a boot loop** (~60 s registry busy-timeout, +`IOService.cpp:5986`, no device attached) through two independent paths: + +- Its Info.plist personality instantiates a kernel-side + `IOUserSCSIParallelInterfaceController` as soon as the FireWire card matches at + boot. The HBA currently reports SCSI target 0 as present unconditionally, and the + probe INQUIRY is then held with no deadline waiting for an SBP-2 login that never + arrives when no SBP-2 device is on the bus (audio interfaces are not SBP-2). The + stalled target registration keeps the PCI nub busy past watchdogd's 60 s boot + quiesce. This happens **regardless of SIP/AMFI state**. +- It requires the restricted + `com.apple.developer.driverkit.family.scsicontroller` entitlement. On a machine + where AMFI enforces entitlements, the ad-hoc-signed dext carrying it is killed at + launch (taking the audio driver down with it, since everything runs in one + process), and the orphaned kernel stub strands the same busy chain. + +The default build carries neither the personality nor the entitlement and cannot +trigger either path. + +To include the HBA, opt in explicitly: ```bash ./build.sh --scsi # or: xcodebuild … ASFW_ENABLE_SCSI=YES ./sign.sh # picks the +SCSI entitlements automatically ``` -If a machine ever ends up in this panic loop: boot into Recovery, `csrutil disable`, +> **Warning:** `--scsi` builds are currently **not cold-boot-safe**: cold-booting +> with the FireWire controller attached and no powered-on SBP-2 device on the bus +> can hit the 60 s boot panic even with SIP fully disabled. Power the SBP-2 device +> on before booting. A stalled probe on a running system does not panic +> immediately, but can panic later when the adapter is unplugged or the extension +> is torn down. The HBA-side fix (create the target at SBP-2 login instead of +> boot) will follow in a separate PR. + +If a machine ever ends up in a panic loop: boot into Recovery, `csrutil disable`, boot normally, uninstall the extension (`systemextensionsctl uninstall - net.mrmidi.ASFW.ASFWDriver`), then re-enable SIP. diff --git a/build.sh b/build.sh index 036f28a3..5a0cd711 100755 --- a/build.sh +++ b/build.sh @@ -51,9 +51,13 @@ SWIFT_COVERAGE=false SWIFT_COVERAGE_LCOV="${BUILD_DIR}/swift_coverage.lcov" # When true, include the SCSI HBA: the ASFWSCSIControllerService personality in # Info.plist and the restricted scsicontroller entitlement in the dext signature. -# Default OFF: an ad-hoc-signed dext carrying that entitlement is killed by AMFI -# on machines that enforce it, and the orphaned kernel-side -# IOUserSCSIParallelInterfaceController stub then panics the kernel at boot. +# Default OFF: the personality matches the FireWire card at boot, and with no +# powered SBP-2 device on the bus the HBA's probe INQUIRY stalls target-0 +# registration past watchdogd's 60 s boot quiesce (registry busy-timeout panic, +# IOService.cpp:5986) — regardless of SIP state. On AMFI-enforcing machines the +# ad-hoc signature carrying the restricted entitlement additionally gets the dext +# killed at launch, stranding the orphaned kernel stub in the same panic. +# See README "SCSI HBA — opt-in". ENABLE_SCSI=false usage() { @@ -68,7 +72,8 @@ Usage: $0 [--verbose] [--no-bump] [--scheme NAME] [--config CONFIG] [--arch ARCH --commands Generate compile_commands.json via xcpretty --analyze Run PVS-Studio static analyzer after build --scsi Include the SCSI HBA (personality + restricted entitlement); - requires SIP disabled on the target machine — see README + requires SIP disabled, and is NOT cold-boot-safe without a + powered-on SBP-2 device attached — see README --scheme NAME Override scheme (default: ${SCHEME_NAME}) --config CONFIG Override configuration (default: ${CONFIGURATION}) --arch ARCH Override architecture passed to xcodebuild (default: ${ARCH_NAME})