Files

120 lines
7.2 KiB
Markdown

# 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](NATIVE_IMPORT_DESIGN.md) 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](NATIVE_IMPORT_ATTEMPT.md), 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](PURE_APPLE.md) stayed at one database object
throughout its 200 operations, so empty create/delete/release alone was
insufficient. The [SDK configured-store A/B](README.md) 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](evidence/2026-11-10-native-import-attempt-b/verification.json)
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.
```sh
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
```