@@ -906,15 +906,64 @@ impl Analyzer {
906906 } ) ;
907907 }
908908
909- // mem::transmute — type-punning bypasses Rust's type system entirely
909+ // mem::transmute — type-punning bypasses Rust's type system entirely.
910+ //
911+ // JIT-aware classification: when the file is in a Cranelift JIT
912+ // context AND the transmute targets a function pointer type, this
913+ // is the canonical pattern for invoking a JIT-emitted function —
914+ // there is no `unsafe`-free way to call a pointer returned by a
915+ // code-generation framework. In that context the finding is
916+ // downgraded from Critical to High and a classification suffix is
917+ // appended so reviewers know to look at the JIT soundness invariants
918+ // (signature match, lifetime ownership, thread affinity) rather
919+ // than treating the transmute as arbitrary type-punning.
920+ //
921+ // Both signals are required:
922+ // 1. JIT context — file imports cranelift_jit / cranelift_module
923+ // or constructs a JITModule.
924+ // 2. Function-pointer target — the transmute target is `fn(...)`,
925+ // either via turbofish (`transmute::<_, fn(...) -> R>`) or via
926+ // a `let x: fn(...) -> R = transmute(...)` binding.
927+ //
928+ // See 007-lang/audits/audit-ffi-unsafe.md §2 for the motivating
929+ // case (jit_compiler.rs's Cranelift dispatch).
910930 if code_only. contains ( "transmute(" ) || code_only. contains ( "transmute::<" ) {
931+ let in_jit_context = code_only. contains ( "cranelift_jit::JITModule" )
932+ || code_only. contains ( "cranelift_module::Module" )
933+ || code_only. contains ( "JITModule::new(" )
934+ || ( code_only. contains ( "cranelift_jit" )
935+ && code_only. contains ( "get_finalized_function" ) ) ;
936+
937+ let transmute_targets_fn_ptr = code_only. contains ( "transmute::<_, fn(" )
938+ || code_only. contains ( "transmute::<_, unsafe fn(" )
939+ || code_only. contains ( "transmute::<*const u8, fn(" )
940+ || code_only. contains ( "transmute::<*mut u8, fn(" )
941+ || ( code_only. contains ( ": fn(" ) && code_only. contains ( "= std::mem::transmute(" ) )
942+ || ( code_only. contains ( ": fn(" ) && code_only. contains ( "= mem::transmute(" ) )
943+ || ( code_only. contains ( ": unsafe fn(" ) && code_only. contains ( "transmute(" ) ) ;
944+
945+ let ( severity, description) = if in_jit_context && transmute_targets_fn_ptr {
946+ (
947+ Severity :: High ,
948+ format ! (
949+ "mem::transmute usage in {} (Cranelift JIT function-pointer dispatch — verify signature match + module ownership invariants per audit-ffi-unsafe.md classification)" ,
950+ file_path
951+ ) ,
952+ )
953+ } else {
954+ (
955+ Severity :: Critical ,
956+ format ! ( "mem::transmute usage in {}" , file_path) ,
957+ )
958+ } ;
959+
911960 weak_points. push ( WeakPoint {
912961 file : None ,
913962 line : None ,
914963 category : WeakPointCategory :: UnsafeCode ,
915964 location : Some ( file_path. to_string ( ) ) ,
916- severity : Severity :: Critical ,
917- description : format ! ( "mem::transmute usage in {}" , file_path ) ,
965+ severity,
966+ description,
918967 recommended_attack : vec ! [ AttackAxis :: Memory ] ,
919968 suppressed : false ,
920969 } ) ;
@@ -5334,6 +5383,180 @@ fn few_unwraps() {
53345383 ) ;
53355384 }
53365385
5386+ // ---------------------------------------------------------------
5387+ // mem::transmute: JIT-aware classification
5388+ // (Cranelift function-pointer dispatch downgrades Critical → High)
5389+ // ---------------------------------------------------------------
5390+
5391+ #[ test]
5392+ fn analyze_rust_transmute_in_jit_context_downgrades_to_high ( ) {
5393+ let analyzer = Analyzer {
5394+ target : std:: path:: PathBuf :: from ( "jit_compiler.rs" ) ,
5395+ language : crate :: types:: Language :: Rust ,
5396+ verbose : false ,
5397+ browser_extension : false ,
5398+ } ;
5399+ // Canonical Cranelift JIT dispatch pattern: a function pointer
5400+ // returned by JITModule::get_finalized_function() is transmuted
5401+ // to a typed `fn(...) -> R` so it can be invoked from Rust.
5402+ let content = r#"
5403+ use cranelift_jit::JITModule;
5404+ use cranelift_module::Module;
5405+
5406+ pub struct CompiledFunction {
5407+ fn_ptr: *const u8,
5408+ param_count: usize,
5409+ _module: JITModule,
5410+ }
5411+
5412+ pub fn call(compiled: &CompiledFunction, args: &[i64]) -> Option<i64> {
5413+ unsafe {
5414+ match compiled.param_count {
5415+ 0 => {
5416+ let f: fn() -> i64 = std::mem::transmute(compiled.fn_ptr);
5417+ Some(f())
5418+ }
5419+ 1 => {
5420+ let f: fn(i64) -> i64 = std::mem::transmute(compiled.fn_ptr);
5421+ Some(f(args[0]))
5422+ }
5423+ _ => None,
5424+ }
5425+ }
5426+ }
5427+ "# ;
5428+ let mut stats = ProgramStatistics {
5429+ total_lines : 0 ,
5430+ unsafe_blocks : 0 ,
5431+ panic_sites : 0 ,
5432+ unwrap_calls : 0 ,
5433+ allocation_sites : 0 ,
5434+ io_operations : 0 ,
5435+ threading_constructs : 0 ,
5436+ } ;
5437+ let mut weak_points = Vec :: new ( ) ;
5438+ analyzer
5439+ . analyze_rust ( content, & mut stats, & mut weak_points, "jit_compiler.rs" )
5440+ . unwrap ( ) ;
5441+
5442+ let transmute_findings: Vec < _ > = weak_points
5443+ . iter ( )
5444+ . filter ( |wp| wp. description . contains ( "mem::transmute" ) )
5445+ . collect ( ) ;
5446+
5447+ assert_eq ! (
5448+ transmute_findings. len( ) ,
5449+ 1 ,
5450+ "Exactly one mem::transmute finding expected"
5451+ ) ;
5452+ assert_eq ! (
5453+ transmute_findings[ 0 ] . severity,
5454+ Severity :: High ,
5455+ "JIT-context transmute targeting fn(...) -> R should downgrade Critical → High"
5456+ ) ;
5457+ assert ! (
5458+ transmute_findings[ 0 ]
5459+ . description
5460+ . contains( "Cranelift JIT function-pointer dispatch" ) ,
5461+ "Downgraded finding should carry the JIT classification suffix; got: {}" ,
5462+ transmute_findings[ 0 ] . description
5463+ ) ;
5464+ }
5465+
5466+ #[ test]
5467+ fn analyze_rust_transmute_outside_jit_context_stays_critical ( ) {
5468+ let analyzer = Analyzer {
5469+ target : std:: path:: PathBuf :: from ( "util.rs" ) ,
5470+ language : crate :: types:: Language :: Rust ,
5471+ verbose : false ,
5472+ browser_extension : false ,
5473+ } ;
5474+ // Arbitrary type-punning transmute, NO Cranelift markers anywhere.
5475+ // Must remain Critical — the JIT-aware downgrade should never
5476+ // suppress real type-punning bugs.
5477+ let content = r#"
5478+ fn pun_u64_to_two_u32s(x: u64) -> (u32, u32) {
5479+ unsafe { std::mem::transmute(x) }
5480+ }
5481+ "# ;
5482+ let mut stats = ProgramStatistics {
5483+ total_lines : 0 ,
5484+ unsafe_blocks : 0 ,
5485+ panic_sites : 0 ,
5486+ unwrap_calls : 0 ,
5487+ allocation_sites : 0 ,
5488+ io_operations : 0 ,
5489+ threading_constructs : 0 ,
5490+ } ;
5491+ let mut weak_points = Vec :: new ( ) ;
5492+ analyzer
5493+ . analyze_rust ( content, & mut stats, & mut weak_points, "util.rs" )
5494+ . unwrap ( ) ;
5495+
5496+ let transmute_findings: Vec < _ > = weak_points
5497+ . iter ( )
5498+ . filter ( |wp| wp. description . contains ( "mem::transmute" ) )
5499+ . collect ( ) ;
5500+
5501+ assert_eq ! ( transmute_findings. len( ) , 1 ) ;
5502+ assert_eq ! (
5503+ transmute_findings[ 0 ] . severity,
5504+ Severity :: Critical ,
5505+ "Non-JIT transmute must stay Critical — JIT-aware downgrade must not over-suppress"
5506+ ) ;
5507+ assert ! (
5508+ !transmute_findings[ 0 ]
5509+ . description
5510+ . contains( "Cranelift JIT" ) ,
5511+ "Non-JIT finding must not carry the JIT classification suffix"
5512+ ) ;
5513+ }
5514+
5515+ #[ test]
5516+ fn analyze_rust_jit_marker_without_fn_ptr_target_stays_critical ( ) {
5517+ // Edge case: file IS a JIT file (cranelift_jit imported) but the
5518+ // transmute target is NOT a function pointer — could be a real bug
5519+ // in JIT code. Stay Critical.
5520+ let analyzer = Analyzer {
5521+ target : std:: path:: PathBuf :: from ( "jit_misc.rs" ) ,
5522+ language : crate :: types:: Language :: Rust ,
5523+ verbose : false ,
5524+ browser_extension : false ,
5525+ } ;
5526+ let content = r#"
5527+ use cranelift_jit::JITModule;
5528+
5529+ fn pun_in_jit_file(x: u64) -> [u8; 8] {
5530+ unsafe { std::mem::transmute(x) }
5531+ }
5532+ "# ;
5533+ let mut stats = ProgramStatistics {
5534+ total_lines : 0 ,
5535+ unsafe_blocks : 0 ,
5536+ panic_sites : 0 ,
5537+ unwrap_calls : 0 ,
5538+ allocation_sites : 0 ,
5539+ io_operations : 0 ,
5540+ threading_constructs : 0 ,
5541+ } ;
5542+ let mut weak_points = Vec :: new ( ) ;
5543+ analyzer
5544+ . analyze_rust ( content, & mut stats, & mut weak_points, "jit_misc.rs" )
5545+ . unwrap ( ) ;
5546+
5547+ let transmute_findings: Vec < _ > = weak_points
5548+ . iter ( )
5549+ . filter ( |wp| wp. description . contains ( "mem::transmute" ) )
5550+ . collect ( ) ;
5551+
5552+ assert_eq ! ( transmute_findings. len( ) , 1 ) ;
5553+ assert_eq ! (
5554+ transmute_findings[ 0 ] . severity,
5555+ Severity :: Critical ,
5556+ "JIT-context but no fn-ptr target — must stay Critical (could be real bug)"
5557+ ) ;
5558+ }
5559+
53375560 // ---------------------------------------------------------------
53385561 // CryptoMisuse: missing signature verification (new patterns)
53395562 // ---------------------------------------------------------------
0 commit comments