|
5 | 5 |
|
6 | 6 | #include "VMPilot_crypto.hpp" |
7 | 7 | #include "binding/inner_partition.hpp" |
| 8 | +#include "binding/resolved_profile.hpp" |
8 | 9 | #include "cbor/strict.hpp" |
| 10 | +#include "eh_guard.hpp" |
9 | 11 | #include "vm/domain_labels.hpp" |
10 | 12 | #include "vm/family_policy.hpp" |
11 | 13 |
|
@@ -78,6 +80,31 @@ bool hash_equals(const std::array<std::uint8_t, 32>& a, |
78 | 80 | return std::memcmp(a.data(), b.data(), 32) == 0; |
79 | 81 | } |
80 | 82 |
|
| 83 | +UnitAcceptError map_eh_contract_error( |
| 84 | + VMPilot::Runtime::EH::ContractVerifyError error) noexcept { |
| 85 | + using VMPilot::Runtime::EH::ContractVerifyError; |
| 86 | + |
| 87 | + switch (error) { |
| 88 | + case ContractVerifyError::MalformedContract: |
| 89 | + return UnitAcceptError::ExceptionUnwindContractMalformed; |
| 90 | + case ContractVerifyError::ExecutableEhStatusNotReservedDisabled: |
| 91 | + return UnitAcceptError::ExecutableEhStatusNotReservedDisabled; |
| 92 | + case ContractVerifyError::CrossProtectedFrameUnwindPermitted: |
| 93 | + return UnitAcceptError::CrossProtectedFrameUnwindPermitted; |
| 94 | + case ContractVerifyError::NativeBoundaryBehaviorNotFailClosed: |
| 95 | + return UnitAcceptError::NativeBoundaryUnwindBehaviorNotFailClosed; |
| 96 | + case ContractVerifyError::HandlerTableNotReservedEmpty: |
| 97 | + return UnitAcceptError::HandlerTableNotReservedEmpty; |
| 98 | + case ContractVerifyError::CleanupTableNotReservedEmpty: |
| 99 | + return UnitAcceptError::CleanupTableNotReservedEmpty; |
| 100 | + case ContractVerifyError::UnknownCriticalExtension: |
| 101 | + return UnitAcceptError::UnknownEhCriticalExtension; |
| 102 | + case ContractVerifyError::FamilySpecificUnwindSurfaceMismatch: |
| 103 | + return UnitAcceptError::FamilySpecificUnwindSurfaceMismatch; |
| 104 | + } |
| 105 | + return UnitAcceptError::ExceptionUnwindContractMalformed; |
| 106 | +} |
| 107 | + |
81 | 108 | // ─── Field extraction helpers ─────────────────────────────────────────── |
82 | 109 |
|
83 | 110 | struct RequireCtx { |
@@ -475,6 +502,39 @@ accept_unit_entry(const std::uint8_t* artifact_data, |
475 | 502 | return err(UnitAcceptError::ResolvedProfileContentHashMismatch); |
476 | 503 | } |
477 | 504 |
|
| 505 | + // 6b. Profile ↔ UBR cross-check (doc 08 §9 #2). The profile's own |
| 506 | + // embedded (family_id / policy_id / profile_id) must agree with |
| 507 | + // the UBR that referenced it. Otherwise a producer could slide a |
| 508 | + // debug-policy profile in under a highsec-policy UBR: every |
| 509 | + // hash still matches (UBR committed to this exact profile), but |
| 510 | + // the profile's declared policy contradicts the UBR's claim and |
| 511 | + // the runtime would dispatch on the wrong tier. |
| 512 | + auto profile_header = parse_resolved_family_profile_header(profile_bytes); |
| 513 | + if (!profile_header) { |
| 514 | + return err(UnitAcceptError::ResolvedProfileTableMalformed); |
| 515 | + } |
| 516 | + if (profile_header->family_id != ubr.family_id) { |
| 517 | + return err(UnitAcceptError::ProfileFamilyIdMismatch); |
| 518 | + } |
| 519 | + if (profile_header->requested_policy_id != ubr.requested_policy_id) { |
| 520 | + return err(UnitAcceptError::ProfilePolicyIdMismatch); |
| 521 | + } |
| 522 | + if (profile_header->profile_id != ubr.resolved_family_profile_id) { |
| 523 | + return err(UnitAcceptError::ProfileIdMismatch); |
| 524 | + } |
| 525 | + |
| 526 | + // 6c. Stage 9 — 1.0 reserved exception/unwind surface verifier. |
| 527 | + // The profile must carry the typed contract, but 1.0 runtime only |
| 528 | + // accepts the strictly reserved posture: executable EH disabled, |
| 529 | + // empty handler/cleanup tables, cross-frame unwind forbidden, and |
| 530 | + // native boundary unwind translated to trap/fail-closed. |
| 531 | + auto eh_contract = |
| 532 | + VMPilot::Runtime::EH::verify_reserved_exception_unwind_contract( |
| 533 | + profile_bytes, ubr.family_id); |
| 534 | + if (!eh_contract) { |
| 535 | + return err(map_eh_contract_error(eh_contract.error())); |
| 536 | + } |
| 537 | + |
478 | 538 | // 7. Payload identity: single-unit packaging for now — the whole |
479 | 539 | // payload partition belongs to this unit. Multi-unit payload |
480 | 540 | // slicing is a future stage. |
@@ -510,6 +570,15 @@ accept_unit_entry(const std::uint8_t* artifact_data, |
510 | 570 | return err(UnitAcceptError::PackageEpochBelowUnitEpoch); |
511 | 571 | } |
512 | 572 |
|
| 573 | + // 8c. Runtime policy floor (doc 15 §9 #4). The enum ordering is |
| 574 | + // Debug(1) < Standard(2) < HighSec(3) — numerically |
| 575 | + // comparable. A standard-tier package cannot unlock a |
| 576 | + // runtime that requires highsec. |
| 577 | + if (static_cast<std::uint8_t>(ubr.requested_policy_id) < |
| 578 | + static_cast<std::uint8_t>(config.minimum_policy_floor)) { |
| 579 | + return err(UnitAcceptError::PolicyBelowRuntimeFloor); |
| 580 | + } |
| 581 | + |
513 | 582 | AcceptedUnit out; |
514 | 583 | out.descriptor = std::move(desc); |
515 | 584 | out.ubr = std::move(ubr); |
|
0 commit comments