Skip to content

fix(policy): reject non-canonical TCB date in policy v2 compare - #937

Open
MichalTarnacki wants to merge 1 commit into
intel:mainfrom
MichalTarnacki:fix/fail_open_design
Open

fix(policy): reject non-canonical TCB date in policy v2 compare#937
MichalTarnacki wants to merge 1 commit into
intel:mainfrom
MichalTarnacki:fix/fail_open_design

Conversation

@MichalTarnacki

@MichalTarnacki MichalTarnacki commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
# Assessed Severity Bug Call Chain / Location Fix Classification Disposition Reason
1 Medium PolicyProperty::evaluate_string greater-or-equal uses raw lexicographic compare on tcb_date verify_peer_cert -> authenticate_remote -> MigPolicy::evaluate -> PolicyProperty::evaluate_string Parse to chrono::NaiveDate before compare; reject non-canonical formats. weakness Why this is a weakness, not an exploitable true positive: the finding's lexicographic arithmetic is real, but no attacker-controlled input can reach that comparison in a non-canonical form. The left operand is never a free-form string; it is the binary R_TCB_DATE field read as a u64 and rendered through chrono's %Y-%m-%dT%H:%M:%SZ, which always emits zero-padded fixed-width digits, and its value is anchored to the Intel-signed root CA via quote verification, so neither the VMM nor a remote peer can place 2024-9-1 there. The right operand is a policy/collateral field that only materializes after the ECDSA signature over the raw bytes is verified and the peer chain is pinned to the same root CA, so it cannot be forged across a trust boundary either. The only way a non-canonical date enters the compare is a human typing it into a hand-edited, then-signed source file (the *_raw.json policy or td_identity.json), which is a mistake by the already-fully-trusted policy producer rather than an exploit primitive. That makes it a robustness gap, not a remotely reachable vulnerability

@sgrams sgrams left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me check the documentation and return with more comments

Comment thread src/policy/src/v2/policy.rs Outdated
Comment on lines +154 to +161
/// The platform TDX TCB components (16-byte SVN array from the quote)
pub tdx_tcb_components: Option<[u8; 16]>,

/// The platform SGX TCB components (16-byte SVN array from the quote)
pub sgx_tcb_components: Option<[u8; 16]>,

/// The platform PCE SVN
pub pce_svn: Option<u16>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's review this against MigTD Design Guide before merging

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue #2 is incorrectly marked as true positive and the proposed change does not align with TDX Migration TD Design Guide Rev 0.9.3.

This doc defines UpToDate == SWHardeningNeeded == OutOfDate and explicitly require OutOfDate to be accepted because the timestamp is not trusted. Policy v2 Table 5-5 defines only fmspc under global.platform; TCB freshness is represented through global.tcb.tcbEvaluationDataNumber, tcbDate, and tcbStatusAccepted. The implementation already evaluates tcbEvaluationDataNumber with greater-or-equal, and the supplied policy-v2 templates configure this baseline.

Keeping that in mind this code should remain unchanged

@sgrams

sgrams commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Finding no. 3 is not addressed in this PR. If already fixed, please remove it from description, otherwise open a new PR.

@MichalTarnacki

MichalTarnacki commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

removed from the description:

| 3 | High | v2 evaluate_policy_forward/backward/common return Ok(()) when block is None | -> PolicyData::evaluate_policy_forward | Treat None as Err(PolicyIncomplete) or require at least one constraint per direction. | true_positive | The core claim is correct: evaluate_policy_forward/backward/common returned Ok(()) when the policy block was None, so an absent block silently skipped mandatory TCB and engine checks — a genuine fail-open that the design guide's acceptance procedure does not sanction (absence-permissive rules exist only for fmspc and the CRL numbers, never for TCB status or engine). However, the finding's RATLS_PEER_CERT / T1 "directly reachable from untrusted peer" framing is wrong: the evaluated policy is the local, signature-verified get_verified_policy(), not peer-supplied input, so it is not remotely exploitable and not Critical. |

to be handled in seperate commit

| 2 | Critical | TcbStatus/ServtdTcbStatus rank() maps OutOfDate==UpToDate; ALWAYS_ALLOW includes | verify_peer_cert -> authenticate_policy -> evaluate_tcb_status | Give OutOfDate rank<UpToDate; remove OutOfDate from ALWAYS_ALLOW; require explicit policy opt-in. | true_positive | This is a true positive, but mischaracterized, The §5.1 advisory wording ("destination platform should be up to date") and the §5.3.2 normative SVN comparison (table 5-3: integer arrays use array-greater-or-equal and each element "shall be equal or greater-or-equal than the reference", with the design guide's own example policy listing sgxtcbcomponents/pcesvn/tdxtcbcomponents) are both confirmed. The report's third claim — that the TcbStatus string ranking is "impl-invented with no normative ordering" — is false: the same design guide states verbatim "uptodate == swhardeningneeded == outofdate >= configurationneeded == ... > revoked", that "outofdate must be accepted as uptodate because the time stamp is not trusted", and that "revoked should not be accepted", which is exactly what rank() and the ALWAYS_ALLOW/ALWAYS_DENY sets implement. Because of that, the report's stated mechanism and proposed fix are both wrong: OutOfDate is accepted by the ALWAYS_ALLOW early return before rank()-based >= is ever reached, so giving OutOfDate a lower rank changes nothing, and removing it from ALWAYS_ALLOW would contradict the specification. The genuine defect the finding points at is real and worth fixing: v2 adopted the permissive tcbStatus half of the spec while dropping the protective numeric platform-SVN floor that v1 still enforces. The residual exposure that keeps this a weakness rather than Critical is that the floor is policy-driven per the spec, so it only takes effect once the signed v2 policy actually carries the platform SVN baselines. |

as incorrectly triaged false positive

The policy v2 `greater-or-equal` operation on `tcbDate` relied on a
plain lexicographic string comparison. That ordering is only valid for
canonical, fixed-width ISO-8601 UTC timestamps; a non-canonical value
such as "2024-9-1" sorts incorrectly (byte '9' > '1') and could make a
newer platform compare as older than the policy baseline.

Validate that both operands match the exact "YYYY-MM-DDTHH:MM:SSZ" form
before comparing, and fail closed with InvalidReference otherwise.

Signed-off-by: Michal Tarnacki <michal.tarnacki@intel.com>
Co-authored-by: GitHub Copilot <noreply@github.com>
@MichalTarnacki MichalTarnacki changed the title fix(migtd): enforce platform TCB floors in policy v2 fix(policy): reject non-canonical TCB date in policy v2 compare Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants