@@ -5453,7 +5453,6 @@ fn commitment_machine_error(message: impl Into<String>) -> CheckerError {
54535453
54545454const POLICY_WRAPPER_ENTRY_ID : & str = "wrapper:_cellscript_entry" ;
54555455const POLICY_ENTRY_FRAME_BYTES : u32 = 4_192 ;
5456- const POLICY_ADAPTER_FRAME_BYTES : u32 = 5_376 ;
54575456const POLICY_ARGS_POINTER_OFFSET : i32 = 4_144 ;
54585457const POLICY_ARGS_LENGTH_OFFSET : i32 = 4_152 ;
54595458const POLICY_TAG_OFFSET : i32 = 4_160 ;
@@ -6540,6 +6539,19 @@ fn bounded_group_input_machine_error(message: impl Into<String>) -> CheckerError
65406539}
65416540
65426541fn validate_policy_dispatch_machine_contract ( record : & VerifiedLoweringRecord , elf : & ParsedElf ) -> Result < ( ) , CheckerError > {
6542+ if !matches ! ( record. typed_semantics. foundation. entry_contract. dispatch, EntryDispatchContract :: PolicyWitnessV1 ( _) ) {
6543+ return Ok ( ( ) ) ;
6544+ }
6545+ if let Some ( ( logical_record, logical_elf) ) =
6546+ crate :: policy_machine:: normalize_relaxed_branches ( record, elf) . map_err ( policy_machine_error) ?
6547+ {
6548+ validate_policy_dispatch_logical_machine_contract ( & logical_record, & logical_elf)
6549+ } else {
6550+ validate_policy_dispatch_logical_machine_contract ( record, elf)
6551+ }
6552+ }
6553+
6554+ fn validate_policy_dispatch_logical_machine_contract ( record : & VerifiedLoweringRecord , elf : & ParsedElf ) -> Result < ( ) , CheckerError > {
65436555 let EntryDispatchContract :: PolicyWitnessV1 ( contract) = & record. typed_semantics . foundation . entry_contract . dispatch else {
65446556 return Ok ( ( ) ) ;
65456557 } ;
@@ -7152,6 +7164,7 @@ fn validate_policy_action_adapters(
71527164 }
71537165 let all_action_starts =
71547166 contract. variants . iter ( ) . map ( |variant| entry_start ( record, & variant. entry_id ) ) . collect :: < Result < BTreeSet < _ > , _ > > ( ) ?;
7167+ let mut shared_bindings: BTreeMap < String , ( Value , BTreeSet < u64 > ) > = BTreeMap :: new ( ) ;
71557168 for ( adapter, variant) in adapters. into_iter ( ) . zip ( & contract. variants ) {
71567169 let entry = record
71577170 . typed_semantics
@@ -7160,7 +7173,8 @@ fn validate_policy_action_adapters(
71607173 . find ( |entry| entry. id == variant. entry_id )
71617174 . ok_or_else ( || policy_machine_error ( "policy variant has no typed action entry" ) ) ?;
71627175 let expected_outgoing = policy_outgoing_argument_bytes ( entry, & record. typed_semantics ) ?;
7163- let expected_record_frame = POLICY_ADAPTER_FRAME_BYTES
7176+ let ( private_frame, private_capacity) = crate :: policy:: private_adapter_layout ( entry, & record. typed_semantics ) ?;
7177+ let expected_record_frame = private_frame
71647178 . checked_add ( expected_outgoing)
71657179 . ok_or_else ( || policy_machine_error ( "policy adapter recorded frame overflows u32" ) ) ?;
71667180 if adapter. frame_size_bytes != expected_record_frame || adapter. outgoing_argument_bytes != 0 {
@@ -7174,31 +7188,93 @@ fn validate_policy_action_adapters(
71747188 return Err ( policy_machine_error ( "policy positional adapter blocks disagree on frame size" ) ) ;
71757189 }
71767190 let base = entry_start ( record, & adapter. id ) ?;
7177- let prologue = instructions_from ( elf , base , 9 ) ? ;
7178- if ! matches_large_sp_adjust ( prologue , 0 , - ( POLICY_ADAPTER_FRAME_BYTES as i32 ) )
7179- || !matches_large_stack_store ( prologue , 3 , 1 , POLICY_ADAPTER_FRAME_BYTES as i32 - 8 )
7191+ let saved_ra = private_frame as i32 - 8 ;
7192+ let save_address = base + sp_adjust_instruction_bytes ( - ( private_frame as i32 ) ) ;
7193+ if ! matches_sp_adjust_at ( elf , base , - ( private_frame as i32 ) ) ? || !matches_stack_slot_at ( elf , save_address , 1 , saved_ra , false ) ?
71807194 {
71817195 return Err ( policy_machine_error ( "policy adapter prologue no longer owns a bounded private copy frame" ) ) ;
71827196 }
7183- let fail = unique_policy_block ( record, & adapter. id , ".Lentry_witness_fail_" ) ?;
7197+ let decoder_call_start = save_address + stack_slot_instruction_bytes ( saved_ra) ;
7198+ let shared_decoder = elf. control_flow . iter ( ) . find ( |flow| flow. address == decoder_call_start + 4 ) . and_then ( |flow| {
7199+ record. entries . iter ( ) . find ( |candidate| {
7200+ candidate. name . starts_with ( ".Lpolicy_shared_decoder_" ) && entry_start ( record, & candidate. id ) . ok ( ) == Some ( flow. target )
7201+ } )
7202+ } ) ;
7203+ let decoder_owner = if let Some ( decoder) = shared_decoder {
7204+ if !is_auipc ( instruction_at ( elf, decoder_call_start) ?. word , 1 )
7205+ || !is_jalr_call ( instruction_at ( elf, decoder_call_start + 4 ) ?. word )
7206+ || decoder. kind != EntryKind :: Runtime
7207+ || decoder. frame_size_bytes != 0
7208+ || decoder. outgoing_argument_bytes != 0
7209+ {
7210+ return Err ( policy_machine_error ( "shared decoder call or borrowed-frame contract changed" ) ) ;
7211+ }
7212+ let decoder_blocks: Vec < _ > = record. blocks . iter ( ) . filter ( |block| block. owner_entry == decoder. id ) . collect ( ) ;
7213+ if decoder_blocks. iter ( ) . any ( |block| block. frame_size_bytes != 0 || block. outgoing_argument_bytes != 0 )
7214+ || elf
7215+ . stack_adjustments
7216+ . iter ( )
7217+ . any ( |adjustment| decoder_blocks. iter ( ) . any ( |block| block. range . contains ( adjustment. address ) ) )
7218+ || elf. instructions . iter ( ) . any ( |instruction| {
7219+ decoder_blocks. iter ( ) . any ( |block| block. range . contains ( instruction. address ) )
7220+ && matches ! ( instruction. word & 0x7f , 0x6f | 0x67 )
7221+ && ( instruction. word >> 7 ) & 31 != 0
7222+ } )
7223+ {
7224+ return Err ( policy_machine_error ( "shared decoder may neither move sp nor call another function" ) ) ;
7225+ }
7226+ let projections = entry
7227+ . params
7228+ . iter ( )
7229+ . map ( |param| crate :: policy:: builder_parameter_projection ( param, entry, & record. typed_semantics ) )
7230+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
7231+ let key =
7232+ serde_json:: json!( { "params" : entry. params, "projection" : projections, "frame" : private_frame, "capacity" : private_capacity} ) ;
7233+ let ( expected, callers) = shared_bindings. entry ( decoder. id . clone ( ) ) . or_insert_with ( || ( key. clone ( ) , BTreeSet :: new ( ) ) ) ;
7234+ if * expected != key {
7235+ return Err ( policy_machine_error ( "different parameter contracts share one positional decoder" ) ) ;
7236+ }
7237+ callers. insert ( decoder_call_start + 4 ) ;
7238+ let decoder_done = unique_policy_block ( record, & decoder. id , ".Lentry_witness_done_" ) ?;
7239+ if instruction_at ( elf, decoder_done. range . start ) ?. word != 0x0000_8067 {
7240+ return Err ( policy_machine_error ( "shared decoder must return with its borrowed frame intact" ) ) ;
7241+ }
7242+ & decoder. id
7243+ } else {
7244+ & adapter. id
7245+ } ;
7246+ let fail = unique_policy_block ( record, decoder_owner, ".Lentry_witness_fail_" ) ?;
71847247 let done = unique_policy_block ( record, & adapter. id , ".Lentry_witness_done_" ) ?;
7185- let has_payload = entry. params . iter ( ) . any ( |param| matches ! ( param. source. as_str( ) , "default" | "witness" ) ) ;
7248+ let has_payload =
7249+ private_capacity > 0 && entry. params . iter ( ) . any ( |param| matches ! ( param. source. as_str( ) , "default" | "witness" ) ) ;
71867250 if has_payload {
7187- let copy = unique_policy_block ( record, & adapter. id , ".Lpolicy_args_copy_" ) ?;
7188- let prefix_address =
7189- copy. range . start . checked_sub ( 24 ) . ok_or_else ( || policy_machine_error ( "policy adapter copy prefix underflows" ) ) ?;
7190- let prefix = instructions_from ( elf, prefix_address, 6 ) ?;
7191- if !is_lui ( prefix[ 0 ] . word , 5 , 0x0000_1000 )
7192- || !is_bltu ( prefix[ 1 ] . word , 5 , 11 )
7193- || !flow_targets ( elf, prefix[ 1 ] . address , fail. range . start )
7194- || !is_sd ( prefix[ 2 ] . word , 11 , 2 , 0 )
7195- || !is_addi ( prefix[ 3 ] . word , 5 , 10 , 0 )
7196- || !is_addi ( prefix[ 4 ] . word , 6 , 2 , 8 )
7197- || !is_addi ( prefix[ 5 ] . word , 7 , 0 , 0 )
7251+ let copy = unique_policy_block ( record, decoder_owner, ".Lpolicy_args_copy_" ) ?;
7252+ let bound_instructions = if private_capacity <= 2047 || private_capacity == 4096 { 1 } else { 2 } ;
7253+ let prefix_address = copy
7254+ . range
7255+ . start
7256+ . checked_sub ( ( 5 + bound_instructions) * 4 )
7257+ . ok_or_else ( || policy_machine_error ( "policy adapter copy prefix underflows" ) ) ?;
7258+ let prefix = instructions_from ( elf, prefix_address, ( 5 + bound_instructions) as usize ) ?;
7259+ let bound_matches = if private_capacity <= 2047 {
7260+ is_addi ( prefix[ 0 ] . word , 5 , 0 , private_capacity as i32 )
7261+ } else if private_capacity == 4096 {
7262+ is_lui ( prefix[ 0 ] . word , 5 , 0x1000 )
7263+ } else {
7264+ is_lui ( prefix[ 0 ] . word , 5 , 0x1000 ) && is_addi ( prefix[ 1 ] . word , 5 , 5 , private_capacity as i32 - 4096 )
7265+ } ;
7266+ let start = bound_instructions as usize ;
7267+ if !bound_matches
7268+ || !is_bltu ( prefix[ start] . word , 5 , 11 )
7269+ || !flow_targets ( elf, prefix[ start] . address , fail. range . start )
7270+ || !is_sd ( prefix[ start + 1 ] . word , 11 , 2 , 0 )
7271+ || !is_addi ( prefix[ start + 2 ] . word , 5 , 10 , 0 )
7272+ || !is_addi ( prefix[ start + 3 ] . word , 6 , 2 , 8 )
7273+ || !is_addi ( prefix[ start + 4 ] . word , 7 , 0 , 0 )
71987274 {
71997275 return Err ( policy_machine_error ( "policy adapter no longer bounds and privately copies selected args" ) ) ;
72007276 }
7201- let copied = unique_policy_block ( record, & adapter . id , ".Lpolicy_args_copied_" ) ?;
7277+ let copied = unique_policy_block ( record, decoder_owner , ".Lpolicy_args_copied_" ) ?;
72027278 let loop_words = instructions_from ( elf, copy. range . start , 7 ) ?;
72037279 if !is_bgeu ( loop_words[ 0 ] . word , 7 , 11 )
72047280 || !flow_targets ( elf, loop_words[ 0 ] . address , copied. range . start )
@@ -7212,11 +7288,14 @@ fn validate_policy_action_adapters(
72127288 {
72137289 return Err ( policy_machine_error ( "policy adapter selected-args copy loop changed" ) ) ;
72147290 }
7215- } else if !is_bne ( prologue[ 7 ] . word , 11 , 0 )
7216- || !flow_targets ( elf, prologue[ 7 ] . address , fail. range . start )
7217- || !is_sd ( prologue[ 8 ] . word , 0 , 2 , 0 )
7218- {
7219- return Err ( policy_machine_error ( "payload-free policy adapter no longer requires empty args" ) ) ;
7291+ } else {
7292+ let empty_guard = if shared_decoder. is_some ( ) { entry_start ( record, decoder_owner) ? } else { decoder_call_start } ;
7293+ if !is_bne ( instruction_at ( elf, empty_guard) ?. word , 11 , 0 )
7294+ || !flow_targets ( elf, empty_guard, fail. range . start )
7295+ || !is_sd ( instruction_at ( elf, empty_guard + 4 ) ?. word , 0 , 2 , 0 )
7296+ {
7297+ return Err ( policy_machine_error ( "payload-free policy adapter no longer requires empty args" ) ) ;
7298+ }
72207299 }
72217300
72227301 let target = entry_start ( record, & variant. entry_id ) ?;
@@ -7233,6 +7312,13 @@ fn validate_policy_action_adapters(
72337312 ) ) ) ;
72347313 }
72357314 let call = action_calls[ 0 ] ;
7315+ if shared_decoder. is_some ( ) {
7316+ let outgoing_adjustment =
7317+ if expected_outgoing == 0 { 0 } else { sp_adjust_instruction_bytes ( -( expected_outgoing as i32 ) ) } ;
7318+ if call. address != decoder_call_start + 8 + outgoing_adjustment + 4 {
7319+ return Err ( policy_machine_error ( "shared decoder arguments must flow directly to the bound action call" ) ) ;
7320+ }
7321+ }
72367322 if call. address < 4
72377323 || !is_auipc ( instruction_at ( elf, call. address - 4 ) ?. word , 1 )
72387324 || !is_jalr_call ( instruction_at ( elf, call. address ) ?. word )
@@ -7262,14 +7348,22 @@ fn validate_policy_action_adapters(
72627348 if !is_jal_zero ( instruction_at ( elf, completion) ?. word ) || !flow_targets ( elf, completion, done. range . start ) {
72637349 return Err ( policy_machine_error ( "policy adapter call no longer completes through its exact done block" ) ) ;
72647350 }
7265- let done_words = instructions_from ( elf , done. range . start , 8 ) ? ;
7266- if !matches_large_stack_load ( done_words , 0 , 1 , POLICY_ADAPTER_FRAME_BYTES as i32 - 8 )
7267- || !matches_large_sp_adjust ( done_words , 4 , POLICY_ADAPTER_FRAME_BYTES as i32 )
7268- || done_words [ 7 ] . word != 0x0000_8067
7351+ let restore = done. range . start + stack_slot_instruction_bytes ( saved_ra ) ;
7352+ if !matches_stack_slot_at ( elf , done . range . start , 1 , saved_ra , true ) ?
7353+ || !matches_sp_adjust_at ( elf , restore , private_frame as i32 ) ?
7354+ || instruction_at ( elf , restore + sp_adjust_instruction_bytes ( private_frame as i32 ) ) ? . word != 0x0000_8067
72697355 {
72707356 return Err ( policy_machine_error ( "policy adapter completion no longer restores its private frame" ) ) ;
72717357 }
72727358 }
7359+ for decoder in record. entries . iter ( ) . filter ( |entry| entry. name . starts_with ( ".Lpolicy_shared_decoder_" ) ) {
7360+ let ( _, callers) = shared_bindings. get ( & decoder. id ) . ok_or_else ( || policy_machine_error ( "unbound shared policy decoder" ) ) ?;
7361+ let target = entry_start ( record, & decoder. id ) ?;
7362+ let incoming: BTreeSet < _ > = elf. control_flow . iter ( ) . filter ( |flow| flow. target == target) . map ( |flow| flow. address ) . collect ( ) ;
7363+ if callers. len ( ) < 2 || & incoming != callers {
7364+ return Err ( policy_machine_error ( "shared decoder has an undeclared caller or no repeated layout" ) ) ;
7365+ }
7366+ }
72737367 Ok ( ( ) )
72747368}
72757369
@@ -7411,6 +7505,28 @@ fn sp_adjust_instruction_bytes(delta: i32) -> u64 {
74117505 }
74127506}
74137507
7508+ fn stack_slot_instruction_bytes ( offset : i32 ) -> u64 {
7509+ if ( -2048 ..=2047 ) . contains ( & offset) {
7510+ 4
7511+ } else {
7512+ 16
7513+ }
7514+ }
7515+
7516+ fn matches_stack_slot_at ( elf : & ParsedElf , address : u64 , register : u32 , offset : i32 , load : bool ) -> Result < bool , CheckerError > {
7517+ if ( -2048 ..=2047 ) . contains ( & offset) {
7518+ let word = instruction_at ( elf, address) ?. word ;
7519+ Ok ( if load { is_ld ( word, register, 2 , offset) } else { is_sd ( word, register, 2 , offset) } )
7520+ } else {
7521+ let words = instructions_from ( elf, address, 4 ) ?;
7522+ Ok ( if load {
7523+ matches_large_stack_load ( words, 0 , register, offset)
7524+ } else {
7525+ matches_large_stack_store ( words, 0 , register, offset)
7526+ } )
7527+ }
7528+ }
7529+
74147530fn matches_sp_adjust_at ( elf : & ParsedElf , address : u64 , delta : i32 ) -> Result < bool , CheckerError > {
74157531 if ( -2_048 ..=2_047 ) . contains ( & delta) {
74167532 Ok ( is_addi ( instruction_at ( elf, address) ?. word , 2 , 2 , delta) )
0 commit comments