test: record native Apple keychain negative control
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# 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.
|
||||
|
||||
```sh
|
||||
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](README.md), and does not approve another experiment,
|
||||
a native workaround, a public upstream post or a long run.
|
||||
Reference in New Issue
Block a user