The retraction

I found a CFGetTypeID gap in secd's IPC decode path, claimed it was reachable from unauthenticated IPC, could not prove it, and retracted. On the discipline of knowing the difference between source-level analysis and a finding.


When I wrote my SANS ICMP paper in 2001, the hardest part was not the technical content. It was the distinction between what I could demonstrate and what I thought was probably true. I was sure of the mechanism. I was fairly confident about the implications. The discipline of the paper — the thing that made it worth publishing rather than just circulating as a note to colleagues — was being clear about which category each claim fell into. A reader picking up that paper deserved to know whether I was reporting observation or inference. Twenty-five years later, I am still learning that lesson.

SECD-01 is the finding that taught it to me most recently.

What I found

Technical Finding — SECD-01 (Self-Retracted)

Component: secd — the Security daemon managing the macOS keychain

CWE: CWE-704 — Incorrect Type Conversion or Cast

Source-level observation: IPC-triggered callbacks that decode incoming data do not consistently call CFGetTypeID before operating on the decoded object. A CFDataGetLength call on a non-CFData object — for example a CFString cast to CFDataRef — produces a type confusion that can crash the daemon.

Original claim: An attacker with the ability to send crafted IPC messages to secd could trigger a daemon crash and potential keychain-unavailability condition.

Status: Self-retracted. The source-level observation is correct. The IPC reachability claim was not demonstrated. Apple closed cleanly after retraction.

The CFGetTypeID pattern — or rather the absence of it — is verifiable in the Security open-source distribution at github.com/apple-oss-distributions/Security. In several keychain decode callbacks, a value received over IPC is used directly as a CFDataRef without first confirming via CFGetTypeID that it is, in fact, a CFData object. Pass a CFString where the code expects CFData, and CFDataGetLength will produce undefined behaviour — in practice, a daemon crash.

That much is real. The source is there. The pattern is there. The type of crash it would produce is predictable.

What I could not prove

The problem was the word “attacker”. My original submission implied that an unprivileged process could reach this code path by sending a crafted IPC message to secd. When I sat down to demonstrate that empirically — to actually craft the message, send it, and watch the type confusion fire — I could not do it.

The crashes I could reproduce were real crashes in secd. But they came from secd's own internal retry logic under keychain-corruption conditions. The crash pattern was consistent with the source-level observation — a CFGetTypeID gap triggering on bad data — but the data was coming from secd's own internal state, not from an IPC payload I had crafted and injected. These are not the same thing.

I could not isolate a path from an unprivileged IPC call to the vulnerable decode callback. The IPC layer in secd has authorisation checks; the data decode happens after those checks pass. Whether an unprivileged caller can pass those checks with a payload that exercises the type confusion path — I could not demonstrate that it could. The IPS crash reports I had were evidence of the crash pattern, not evidence of the attack path.

The PoC — and why it was insufficient

secd-investigation.sh Evidence I had — and why it was not enough
#!/bin/bash
# secd type confusion investigation — FINDING RETRACTED
# This script documents what evidence existed and why it was insufficient.

echo "=== Source-level observation (verifiable) ==="
echo "CFGetTypeID gap in keychain decode callbacks:"
echo "  github.com/apple-oss-distributions/Security"
echo "  Search: CFDataGetLength without preceding CFGetTypeID check"
echo ""

echo "=== Natural crash pattern (what I could reproduce) ==="
echo "Checking secd process:"
pgrep -la secd
echo ""
echo "secd crashes occur under keychain-corruption + retry load."
echo "Crash pattern is consistent with CWE-704 (CFGetTypeID gap)."
echo "These crashes are from secd's OWN internal state, not from"
echo "an IPC payload I crafted and injected."
echo ""

echo "=== What I could NOT demonstrate ==="
echo "An unprivileged IPC caller sending a crafted payload that:"
echo "  1. Passes secd's IPC authorisation checks, AND"
echo "  2. Reaches the CFDataGetLength call with a non-CFData object"
echo ""
echo "The source-level analysis (CFGetTypeID gap) is correct."
echo "The reachability claim from unauthenticated IPC was not proven."
echo "Retraction was the right call."
echo ""

# To verify the source observation:
# git clone https://github.com/apple-oss-distributions/Security
# grep -r "CFDataGetLength" Security/OSX/sec/Security/
# Cross-reference against CFGetTypeID calls in the same functions

The retraction and Apple's response

I retracted the IPC-triggered framing and submitted the natural-crash IPS files to Apple with a note explaining what I could and could not demonstrate. Apple closed the finding cleanly. No adverse comment. No friction. This is what honest retraction looks like — the relationship cost was zero, and I came away with more confidence in the process, not less.

Opinion

Source-level analysis is the beginning of a finding, not the end of one. The CFGetTypeID gap in secd's decode path is a real code-quality issue. It may one day be the enabling condition for something more significant — if the IPC authorisation model ever changes, or if a new codepath reaches the vulnerable callbacks from a less-guarded entry point. But a finding requires a demonstrated attack path, not a plausible one. The SANS discipline I applied in 2001 — say what you know, say what you infer, keep them separate — was the right rule then and it remains the right rule now. The crash I could produce was real. The crash I claimed an attacker could produce was speculation. Those two things needed to be separated before anything went to Apple, and I did not separate them cleanly enough the first time.

The lesson I take from SECD-01 is not that source-level analysis is useless. It is that source-level analysis that cannot be backed by a reproducible attack path requires a different label. “Code quality observation” is a perfectly good label. “Security finding” requires more.


Disclosure note

This finding was submitted to Apple through the Apple Security Research Framework with supporting IPS crash reports and source references. The IPC-triggered reachability claim was retracted before Apple completed review. Apple confirmed clean closure. The source-level observation (CWE-704, CFGetTypeID gap) is based on the publicly available Security framework source at github.com/apple-oss-distributions/Security and is accurately characterised here.

Are you there? The crash was there. The attacker was not.