Files
lmxopcua/scripts/validation/opcua-lifecycle-memory/native-retention/NATIVE_IMPORT.md
T

7.2 KiB

Installed native bridge import reproduces AppleDatabase retention

The corrected fixed-200 diagnostic reproduced the native heap inventory growth without a managed caller in the measured process. With the exact installed .NET Apple cryptography bridge still loaded, Security::AppleDatabase rose from 1 to 201 objects and stayed at 201 after five seconds idle. All observed returned CF references balanced, native delete statuses were zero, and numeric FD counts and owned keychain files/FDs stayed flat. The native bridge plus Apple import/keychain path is sufficient in this control; managed .NET and the OPC UA SDK are not required in the measured process.

This does not identify the retaining native owner or separate bridge-native ownership from Apple caching. A classified heap object is not an open handle, and its shallow bytes do not explain the full RSS change. The control provides no production mitigation, budget waiver, full-soak/G4 pass or #799 closure.

Controlled sequence and actual ownership

The measured arm ran once in a fresh private root on macOS 26.5.2 build 25F84 arm64. Its C child hash-checked and loaded the exact installed native bridge, verified its export image and rejected CLR/SDK images. The separate .NET 10.0.14 SDK 2.0.1 seed made a normal RSA application fixture, verified its private key against the public leaf, disposed the SDK manager, wrote private mode-0600 inputs under a mode-0700 directory and exited before the measured child. The seed's SDK PKI was removed before native measurement. The child consumed public DER and PKCS1 private key bytes, not a PFX, and did not emulate managed PFX parsing, matching or finalizer timing. The explicit legacy-key release was after identity disposal. The design maps the actual installed binary ABI, native exports and wrapper copy/release sequence.

The native child performed the pinned bridge's DER certificate import, modern RSA key creation/public copy/external representation, ephemeral legacy private key import, private keychain move/identity lookup and identity certificate copy. Every operation exercised nine ordered wrapper keychain-copy calls. The logical owner model counted only observed non-null copies whose type, pointer and private generated path matched the initial native keychain. The collector independently replayed those owner transitions and reconciled actual deletion stage and returned CF output counts. No logical owner was invented for a null copy. Any previously tracked item losing its association would fail the run.

All 200 moves and identity certificate copies succeeded. Native keychain create/path/set-never-lock/delete and the reached native OSStatus outputs were zero. The bridge operations that use return value one did return one. Both first/last returned certificate identity checks matched the unchanged seed leaf. All 1,800 wrapper copies had bridge-normalized status zero. Four calls per operation returned an owned private keychain reference: post-move original cert Track, copied cert Track, original cert Untrack and copied cert Untrack. The other five calls returned null, including both identity calls. All 800 returned keychain copies were released. Across all outputs the ledger was 2,402 acquired / 2,402 released, including 200/200 initial keychain refs. There were no fallback cleanups or remaining logical owners.

In all 200 operations, the original cert was released, then the initial import-state logical owner was relinquished. Deletion occurred inside the final copied-certificate Untrack, while that keychain copy, copied certificate, identity and legacy key references were still alive. The native delete returned zero, then the initial native keychain owner, copied keychain ref and copied certificate were released in order. The identity's subsequent Untrack returned no keychain association. This observed order differs from the fixed identity association wrongly assumed by attempt A, which stopped during its first operation and remains preserved separately.

Native observations and cleanup

Checkpoint AppleDatabase count / shallow bytes Numeric FDs Owned keychain files / FDs RSS bytes
Before 1 / 768 3 0 / 0 7,749,632
First 2 / 1,536 3 0 / 0 10,862,592
200 201 / 154,368 3 0 / 0 14,598,144
Five-second idle 201 / 154,368 3 0 / 0 14,614,528

Default and search-list fingerprints were equal before and after, with all query statuses zero and search-list count two. Four owned query copies were released; no setter or restore API was called. All temporary private keychains were generated with unrelated filenames, prompt disabled, and path-verified before deletion. There were zero owned keychain files before harness cleanup. Both builds, seed and measured process exited zero and were reaped. The remaining private fixture files were overwritten and unlinked only after reaping; the task root was removed. No physical secure-erasure guarantee is claimed.

Native child time was 16.772 seconds, parent time including probes 17.022 seconds. Heap probes were heap --noContent -s, numeric descriptors from lsof, and RSS from Mach task info. Parent and child checkpoints use a 180-second limit, with separate bounded probe and process-reap overhead. The C build succeeded with six unsuppressed deprecation warnings for the legacy public keychain APIs it exercises. The seed build had zero warnings/errors.

The empty native keychain control stayed at one database object throughout its 200 operations, so empty create/delete/release alone was insufficient. The SDK configured-store A/B reproduced inventory growth despite its observed SDK and managed owner release. This native import control narrows that observation to the installed native bridge plus Security.framework path. It does not assign a particular .NET, SDK or Apple defect, establish native delete status for the separate managed A/B, or show that a public-Apple-only import sequence reproduces the result.

Pinned evidence

The unchanged private raw result is /tmp/opcua2-native-import-20261110b. Selected receipts and hashes retain build logs, provenance, seed and cleanup receipts, and a subset of the 207 exact raw JSONL rows. Full raw heap/FD inventories and all 200 operation rows remain local. There is no retained private fixture. Source, installed bridge, native executable and seed DLL hashes are in the provenance.

  • Result JSON SHA-256: 73771e5aea164d0207a1096d3a9ecb92603166a82a905dfc6860598b0b37f8b1
  • Full raw JSONL SHA-256: 350f35e46e159424c5e552b90e3b4a3a46d572d7ba034df274cd1646636e7498
  • Native executable SHA-256: 6a0d0d1980b505c20b5b270c8e3eceb6d0256b6b8953fbc2dad4b6f66db732cf
  • Seed DLL SHA-256: bc9ea0f2ee2e6bd004bb2ecb801cbc4b1cbc250b1267bf31616d1f334a102893

The fixed collector invocation requires the pinned binary set and a fresh private output. Any further run needs its own scope and serialization review.

PYTHONDONTWRITEBYTECODE=1 python3 \
  scripts/validation/opcua-lifecycle-memory/native-retention/native_import.py \
  --references /tmp/opcua2-native-reload-20261110a/bin/Release/net10.0 \
  --output /tmp/opcua2-native-import-FRESH