Skip to content

srt blocks hw.optional.neon, so Qt apps abort on Apple Silicon #430

Description

@queglay

Qt reads hw.optional.neon to detect NEON. Under srt that read is denied, Qt takes the missing answer for "no NEON", and aborts before reaching main():

Incompatible processor. This Qt build requires the following features:
    neon
Process killed by signal: SIGABRT

We hit this running Houdini's hython in a sandboxed process. Houdini 21.0.729 bundles Qt 6.5.3, and the string above comes from its QtCore. Nothing about the failure is Houdini specific.

The ask is one name added to an allowlist that already permits its siblings by prefix, not a change to the default-deny policy.

Reproduce without Qt

probe.py:

import ctypes, ctypes.util

libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)

def probe(name):
    size = ctypes.c_size_t(8)
    buf = ctypes.create_string_buffer(8)
    if libc.sysctlbyname(name.encode(), buf, ctypes.byref(size), None, 0) == 0:
        return "ok (value=%d)" % int.from_bytes(buf.raw[: size.value], "little")
    return "DENIED errno=%d" % ctypes.get_errno()

for name in ("hw.optional.neon", "hw.optional.armv8_crc32",
             "hw.logicalcpu", "hw.optional.this_does_not_exist"):
    print("  %-32s %s" % (name, probe(name)))

settings.json, granting read access to the whole filesystem:

{
  "network": {
    "allowedDomains": [],
    "deniedDomains": [],
    "allowLocalBinding": true,
    "allowUnixSockets": []
  },
  "filesystem": {
    "denyRead": [],
    "allowRead": ["/"],
    "allowWrite": ["/tmp"],
    "denyWrite": []
  },
  "enableWeakerNestedSandbox": false,
  "enableWeakerNetworkIsolation": false,
  "allowAppleEvents": false
}
/usr/bin/python3 probe.py                                   # normal: all real names ok
srt --settings settings.json -- /usr/bin/python3 probe.py   # sandboxed

Sandboxed:

  hw.optional.neon                 DENIED errno=1
  hw.optional.armv8_crc32          ok (value=1)
  hw.logicalcpu                    ok (value=10)
  hw.optional.this_does_not_exist  DENIED errno=2

errno=1 is EPERM, so the name exists and the read was refused. The deliberately fake name gets errno=2, ENOENT, in both runs. Note the policy grants read access to the whole filesystem and the value is still blocked, because a sysctl read is a separate Seatbelt operation class from file reads.

Cause

dist/sandbox/macos-sandbox-utils.js allows CPU feature names with three prefix rules:

(sysctl-name-prefix "hw.optional.arm")
(sysctl-name-prefix "hw.optional.arm.")
(sysctl-name-prefix "hw.optional.armv8_")

hw.optional.neon begins with none of them, so it falls through to the default deny.

The same fact is already readable inside the sandbox under a different name. hw.optional.arm.AdvSIMD also reports NEON support and returns 1 sandboxed, because it matches the hw.optional.arm. prefix:

  hw.optional.neon             DENIED errno=1
  hw.optional.arm.AdvSIMD      ok (value=1)

So this is a naming gap rather than a boundary. Qt 6.5.3 happens to ask for the spelling that is not covered.

Qt 6.5.3, src/corelib/global/qsimd.cpp, Darwin ARM branch of detectProcessorFeatures():

if (sysctlbyname("hw.optional.neon", &feature, &len, nullptr, 0) == 0)
    features |= feature ? CpuFeatureNEON : 0;

A denied read skips the branch and the bit is never set, so Qt cannot tell "this CPU has no NEON" from "something refused to let me ask".

Qt removed this read in commit aeeb11841b3e (September 2024, "Clean and assert our ARM extensions on Apple hardware"), replacing it with a compile-time assumption. Current Qt queries only hw.optional.armv8_crc32, hw.optional.arm.FEAT_AES and hw.optional.arm.FEAT_SVE, all of which match your existing prefixes. So this affects Qt 6.5 era builds rather than trunk, which is likely why it has not surfaced before. That fix does not help anyone running already-shipped software built against those versions, which cannot be repointed at Qt trunk.

Suggested fix

Add hw.optional.neon.

Adding it discloses nothing new, since hw.optional.arm.AdvSIMD already reports the same capability through your existing prefix.

Widening to the whole hw.optional. prefix may also be worth considering, since naming values one at a time mainly defers the next instance of this. The obvious objection is fingerprinting of exploit mitigations, but on this machine every such flag (FEAT_BTI, FEAT_MTE*, FEAT_CSV2, FEAT_CSV3, FEAT_FPAC) sits under hw.optional.arm.FEAT_* and is already permitted. You are better placed than us to weigh that.

Related here: #287 asks for the sysctl allowlist to be exposed in the JSON policy, which would let people unblock themselves without a release. #78 is the same shape for kern.memorystatus_level.

Seen elsewhere too, so this is not specific to srt: openai/codex#7099 reports the same blocked sysctl breaking Qt, and openscad/openscad#6712 is a downstream sighting of the same message.

Environment

@anthropic-ai/sandbox-runtime 0.0.67, macOS 26.5.2, Apple M2 Pro, Node v25.8.1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions