test: compare native keychain deletion timing
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
# Deferring native keychain deletion did not prevent inventory growth
|
||||
|
||||
The fixed paired diagnostic reproduced the same `Security::AppleDatabase`
|
||||
inventory in both measured processes: **1 → 2 → 201 → 201**. Deferring native
|
||||
`SecKeychainDelete` and the initial keychain `CFRelease` until after all observed
|
||||
returned item, key, data, error and copy owners were released did not prevent the
|
||||
increase on this pinned native bridge path. This rules out changing only that
|
||||
observed deletion timing as a remedy in this control. It does not identify
|
||||
bridge-internal or Apple ownership, establish whether any unobserved native
|
||||
references remain, or assign a platform defect.
|
||||
|
||||
The two arms used the same fresh private RSA seed, same measured C binary and
|
||||
installed native bridge, and separate processes. The fixed normal arm ran first
|
||||
and reproduced [attempt B](NATIVE_IMPORT.md), whose source and receipts remain
|
||||
unchanged. The deferred arm ran next. There was one paired execution, 200
|
||||
operations per arm, with no retry, count change, reversed order or managed caller
|
||||
in either measured child. These are bounded diagnostics on macOS **26.5.2** build
|
||||
**25F84** arm64. No production mitigation, RSS budget decision, full-soak/G4 pass
|
||||
or #799 closure follows.
|
||||
|
||||
## What changed between the arms
|
||||
|
||||
Both arms ran the same pinned native imports, nine ordered wrapper keychain
|
||||
copies per operation, and item, copy and legacy-key release sequence. The logical
|
||||
registry retired at the same point in all **400** operations: the final copied
|
||||
certificate Untrack after original certificate disposal and initial import-state
|
||||
logical release. At logical zero there were five observed returned references:
|
||||
the initial keychain, current keychain copy, copied certificate, identity and
|
||||
legacy key. The original certificate reference was already released.
|
||||
|
||||
| Arm | Actual native delete and initial release | Observed returned CF owners at delete |
|
||||
|---|---|---:|
|
||||
| Normal | At logical zero, during copied certificate Untrack | 5: initial keychain and four other references |
|
||||
| Deferred | After all item, copy and legacy-key releases | 1: initial keychain only |
|
||||
|
||||
In the deferred arm the initial native owner stayed held after the logical
|
||||
registry retired; its delete and `CFRelease` ran only after all other observed
|
||||
returned owners were gone. The collector replayed every logical owner transition
|
||||
and independently verified the physical keychain owner and actual deletion state.
|
||||
This ledger covers references returned to the child. It does not enumerate
|
||||
unobservable references within the installed bridge or Security.framework.
|
||||
|
||||
All **200** keychain deletes in each arm returned native status zero. All bridge
|
||||
operations that use one as success returned one; the reached OSStatus fields
|
||||
were zero. Each arm observed **1,800** keychain copy calls with normalized status
|
||||
zero and **800** returned private associations. The four associated copy stages
|
||||
were identical: post-move original certificate Track, copied certificate Track,
|
||||
original certificate Untrack and copied certificate Untrack. Both identity calls
|
||||
returned null with success. Each arm balanced **2,402/2,402** returned CF owners,
|
||||
including **200/200** initial keychain refs, with zero remaining logical owners
|
||||
and no fallback cleanup. First and last certificate identity checks matched the
|
||||
one unchanged shared seed in both arms.
|
||||
|
||||
## Native measurements and cleanup
|
||||
|
||||
| Arm | Checkpoint | AppleDatabase count / shallow bytes | Numeric FDs | Owned keychain files / FDs | RSS bytes |
|
||||
|---|---|---:|---:|---:|---:|
|
||||
| Normal | Before | 1 / 768 | 3 | 0 / 0 | 7,667,712 |
|
||||
| Normal | First | 2 / 1,536 | 3 | 0 / 0 | 10,895,360 |
|
||||
| Normal | 200 | 201 / 154,368 | 3 | 0 / 0 | 14,548,992 |
|
||||
| Normal | Five-second idle | 201 / 154,368 | 3 | 0 / 0 | 14,581,760 |
|
||||
| Deferred | Before | 1 / 768 | 3 | 0 / 0 | 7,536,640 |
|
||||
| Deferred | First | 2 / 1,536 | 3 | 0 / 0 | 10,878,976 |
|
||||
| Deferred | 200 | 201 / 154,368 | 3 | 0 / 0 | 14,123,008 |
|
||||
| Deferred | Five-second idle | 201 / 154,368 | 3 | 0 / 0 | 14,139,392 |
|
||||
|
||||
Native child times were **17.403** and **17.290** seconds, respectively. Parent
|
||||
wall including the seed and both arms was **35.545 seconds**. Heap inventory
|
||||
came from `heap --noContent -s`; RSS was Mach task resident size. Numeric FD
|
||||
counts did not rise. A classified native heap object is not an open handle and
|
||||
its shallow bytes do not explain the full RSS change. The one fixed-order pair
|
||||
does not estimate a timing effect on RSS or generalize beyond this control.
|
||||
|
||||
The single .NET **10.0.14** SDK **2.0.1** seed ran outside both measured children.
|
||||
Its private DER/PKCS1 fixture files were mode 0600 in a private root, and its
|
||||
SDK PKI was removed after seed exit. Both measured children read the same seed
|
||||
files; the public leaf hash and private file identity metadata remained stable.
|
||||
The native children checked the pinned bridge image and rejected CLR/SDK images.
|
||||
No PFX parser or managed finalizer behavior was emulated. The explicit legacy
|
||||
key release order was unchanged between the arms.
|
||||
|
||||
All four read-only default/search-list fingerprints matched; all query statuses
|
||||
were zero and the search-list count was two. No setter or restore API ran.
|
||||
Both builds, seed and both measured children exited zero and were reaped. Both
|
||||
owned keychain directories already had zero keychain files before harness
|
||||
cleanup. The remaining private fixture files were overwritten and unlinked
|
||||
only after all reaping, and the private root was removed. No physical secure
|
||||
erasure guarantee is claimed. Native compilation emitted **six unsuppressed
|
||||
deprecation warnings** for the exercised public keychain APIs; the seed build
|
||||
reported zero warnings and errors.
|
||||
|
||||
The result narrows the remaining question: the installed native bridge plus
|
||||
Security.framework import path retained the database inventory with both tested
|
||||
release orders, while the [empty keychain control](PURE_APPLE.md) did not. It
|
||||
remains unresolved which native component owns the retained objects. There is no
|
||||
supported application-only correction demonstrated by this experiment.
|
||||
|
||||
## Pinned evidence
|
||||
|
||||
Raw private output is `/tmp/opcua2-native-import-timing-20261110a`.
|
||||
[Selected receipts](evidence/2026-11-10-native-import-timing/verification.json)
|
||||
retain the source/runtime/binary hashes, build logs, shared seed and cleanup
|
||||
receipts, both arm summaries and nine exact raw JSONL rows per arm. The 207 raw
|
||||
rows per arm and all native heap/FD snapshots stay local and are hash-bound.
|
||||
|
||||
- Pair result SHA-256: `f0b4195fe445259934b91e9c6743437c8427844b8fe5e3a9aafe28847f495986`
|
||||
- Normal raw JSONL SHA-256: `73288755c768065ad0a8b2dad6964696d707f1c6be5a6b8bd531902cb3b4bf51`
|
||||
- Deferred raw JSONL SHA-256: `61691ff4c500d2696f6998cd225d19677a0cb8eb47ed8f7545b01f76e4acc456`
|
||||
- Measured binary SHA-256: `cec035355aea998d6dd9311561b23e1617a1096191847cf99f1a0a90a7b6d112`
|
||||
- Seed DLL SHA-256: `36e648ac37b61cb5dd65f03dcf5db555fc297d23243c42e282ef522f15a8a7a1`
|
||||
|
||||
The fixed collector accepts only the pinned source and 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_timing.py \
|
||||
--references /tmp/opcua2-native-reload-20261110a/bin/Release/net10.0 \
|
||||
--output /tmp/opcua2-native-import-timing-FRESH
|
||||
```
|
||||
Reference in New Issue
Block a user