Files
lmxopcua/scripts/validation/opcua-lifecycle-memory/native-retention/PURE_APPLE.md
T
2026-11-10 16:56:31 -05:00

5.3 KiB

Native empty-keychain control — negative retention result

The plain C control completed 200 private keychain create/delete/CFRelease operations successfully, with Security::AppleDatabase heap inventory staying at 1 object / 768 shallow bytes before, after the first operation, after 200, and after a five-second idle. This does not reproduce the retention seen in private-PFX and SDK configured-store loading. Empty native create/delete/release alone is therefore insufficient to explain that result on this machine.

This does not rule out deletion/resource interactions for populated imported keychains, identify the retaining owner, or assign an SDK, CLR or Apple defect. Private-key/certificate materialization and its associated native interactions remain distinct. No production mitigation, resource-budget decision, G4 pass or #799 closure follows from this control.

Reviewed execution and isolation

Astra reviewed the actual C/Python source before execution and afterward independently verified all 207 JSONL rows, 200 ordered operations/status triples, reference balance, default/search-list fingerprints, all four native heap counts/bytes, FD/file counts, source/runner/binary hashes, cleanup and warnings. Only one actual native execution ran, serialized after the coordinator's other ownership control. Source is PureAppleControl.c; collector is pure_apple.py.

The child is a plain native C program linked only to Security.framework, CoreFoundation.framework and libSystem. It does not load certificates/PFX, the OPC UA SDK or CLR. SecKeychainCreate used a fresh random filename beneath its private mode-0700 task root, a generated password and promptUser=false. A returned keychain's path had to exactly match that generated private filename/backing file before deletion was allowed. Each returned create reference received exactly one CFRelease, including failure paths. The program made no default/search-list setter or restore call. Read-only SecKeychainCopyDefault, SecKeychainCopySearchList and SecKeychainGetPath produced hashed before/after fingerprints; owned Copy results were released, borrowed array elements were not released separately. Existing keychain paths/passwords were never logged.

Native checkpoints were before, first, 200 and five-second idle. Each parent snapshot used heap --noContent -s and numeric lsof FDs, with Mach task resident bytes reported by the child. It did not invoke GC or allocator-pressure APIs. The original fixed 180-second checkpoint/child limit, 20-second heap and five-second lsof limits, bounded final wait and terminate/kill reap remain in the collector; those probe/reap bounds can extend total wall time beyond 180 seconds. Actual child monotonic duration was 8.995 seconds; parent wall time including probes was 9.288 seconds. The child exited zero and was reaped.

Actual results

Checkpoint AppleDatabase count / shallow bytes Numeric FDs Owned keychain files / FDs RSS bytes
Before 1 / 768 3 0 / 0 7,372,800
First 1 / 768 3 0 / 0 8,028,160
200 1 / 768 3 0 / 0 8,699,904
Five-second idle 1 / 768 3 0 / 0 8,699,904

All 200 create/path/delete OSStatus triples were (0, 0, 0). Each create returned an owned reference, its private path was verified, and all 200/200 references were released exactly once. All read-only default/path/search-list query statuses were zero. Before/after fingerprints were identical, with search list count 2 at both observations. Four query-owned Copy references were released across the two fingerprint collections. No list setter was invoked.

Default fingerprint: 08c2c77dd8eaf8042b4020991ea06c6215cc391532869bbf6ced2cf1126c5e63. Search-list fingerprint: c05fae700986dc7a6a147a938ef2adc4a274173420e137929ed58ad986eb2a3c.

Owned keychain files were already zero before harness cleanup. The fresh owned fixture root was then removed. Native class count is a heap classification, not an open-handle count or an exclusive attribution of the modest RSS change.

The first attempt stopped at compilation, before a native child or API call: strict C11 headers did not declare explicit_bzero. Replacing only that caller-owned buffer wipe with a volatile-byte wipe made the build succeed; empty fixture creation was moved after successful compilation. The failed build is retained separately. Both compile attempts emitted five unsuppressed deprecated-public-API warnings for the intentionally exercised legacy keychain APIs. This is not a zero-warning build and no warning suppression was added.

Evidence and reproduction

Selected, home-path-redacted receipts are under evidence/2026-11-10-pure-apple/; verification.json retains the unchanged local raw receipt hashes. Actual raw output is /tmp/opcua2-pure-apple-20261110b; the first compile-only failure is /tmp/opcua2-pure-apple-20261110a. Raw native snapshots stay private/local.

python3 scripts/validation/opcua-lifecycle-memory/native-retention/pure_apple.py \
  --output /tmp/opcua2-pure-apple-FRESH

The output must be fresh. Fixed counts are not configurable; all generated paths are task-owned and no user/fleet PKI is used. This negative control refines the A/B directory-load findings, and does not approve another experiment, a native workaround, a public upstream post or a long run.