@@ -979,12 +979,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
979979 }
980980
981981 /// Handle the effect an FFI call might have on the state of allocations.
982- /// This overapproximates the modifications which external code might make to memory:
983- /// We set all reachable allocations as initialized, mark all reachable provenances as exposed
984- /// and overwrite them with `Provenance::WILDCARD`.
982+ /// If `paranoid` is true, overapproximates the modifications which external code might make
983+ /// to memory: We set all reachable allocations as initialized, mark all reachable provenances
984+ /// as exposed and overwrite them with `Provenance::WILDCARD`. Otherwise, it just makes sure
985+ /// that all allocations are properly set up so that we don't leak whatever was in the uninit
986+ /// bytes on FFI call.
985987 ///
986988 /// The allocations in `ids` are assumed to be already exposed.
987- pub fn prepare_for_native_call ( & mut self , ids : Vec < AllocId > ) -> InterpResult < ' tcx > {
989+ pub fn prepare_for_native_call (
990+ & mut self ,
991+ ids : Vec < AllocId > ,
992+ paranoid : bool ,
993+ ) -> InterpResult < ' tcx > {
988994 let mut done = FxHashSet :: default ( ) ;
989995 let mut todo = ids;
990996 while let Some ( id) = todo. pop ( ) {
@@ -999,25 +1005,117 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
9991005 continue ;
10001006 }
10011007
1002- // Expose all provenances in this allocation, and add them to `todo` .
1008+ // Make sure we iterate over everything recursively, preparing the extra alloc info .
10031009 let alloc = self . get_alloc_raw ( id) ?;
10041010 for prov in alloc. provenance ( ) . provenances ( ) {
1005- M :: expose_provenance ( self , prov) ?;
1011+ if paranoid {
1012+ // Expose all provenances in this allocation, and add them to `todo`.
1013+ M :: expose_provenance ( self , prov) ?;
1014+ }
10061015 if let Some ( id) = prov. get_alloc_id ( ) {
10071016 todo. push ( id) ;
10081017 }
10091018 }
1019+
10101020 // Also expose the provenance of the interpreter-level allocation, so it can
10111021 // be read by FFI. The `black_box` is defensive programming as LLVM likes
10121022 // to (incorrectly) optimize away ptr2int casts whose result is unused.
1013- std:: hint:: black_box ( alloc. get_bytes_unchecked_raw ( ) . expose_provenance ( ) ) ;
1014-
1015- // Prepare for possible write from native code if mutable.
1016- if info. mutbl . is_mut ( ) {
1017- self . get_alloc_raw_mut ( id) ?
1018- . 0
1019- . prepare_for_native_write ( )
1020- . map_err ( |e| e. to_interp_error ( id) ) ?;
1023+ if paranoid {
1024+ std:: hint:: black_box ( alloc. get_bytes_unchecked_raw ( ) . expose_provenance ( ) ) ;
1025+ // Prepare for possible write from native code if mutable.
1026+ if info. mutbl . is_mut ( ) {
1027+ self . get_alloc_raw_mut ( id) ?. 0 . prepare_for_native_write ( ) ;
1028+ }
1029+ }
1030+ }
1031+ interp_ok ( ( ) )
1032+ }
1033+
1034+ /// Updates the machine state "as if" the accesses given had been performed.
1035+ /// Used only by Miri for FFI, for taking note of events that were intercepted from foreign
1036+ /// code and properly (but still conservatively) marking their effects. Remember to call
1037+ /// `prepare_for_native_call` with `paranoid` set to false first on the same `AllocId`s, or
1038+ /// some writes may be discarded!
1039+ ///
1040+ /// The allocations in `ids` are assumed to be already exposed.
1041+ pub fn apply_accesses (
1042+ & mut self ,
1043+ mut ids : Vec < AllocId > ,
1044+ reads : Vec < std:: ops:: Range < u64 > > ,
1045+ writes : Vec < std:: ops:: Range < u64 > > ,
1046+ ) -> InterpResult < ' tcx > {
1047+ /// Helper function to avoid some code duplication over range overlaps.
1048+ fn get_start_size (
1049+ rg : std:: ops:: Range < u64 > ,
1050+ alloc_base : u64 ,
1051+ alloc_size : u64 ,
1052+ ) -> Option < ( u64 , u64 ) > {
1053+ // A bunch of range bounds nonsense that effectively simplifies to
1054+ // "get the starting point of the overlap and the length from there".
1055+ // Needs to also account for the allocation being in the middle of the
1056+ // range or completely containing it
1057+ let signed_start = rg. start . cast_signed ( ) - alloc_base. cast_signed ( ) ;
1058+ let size_uncapped = if signed_start < 0 {
1059+ // If this returns, they don't overlap
1060+ ( signed_start + ( rg. end - rg. start ) . cast_signed ( ) ) . try_into ( ) . ok ( ) ?
1061+ } else {
1062+ rg. end - rg. start
1063+ } ;
1064+ let start: u64 = signed_start. try_into ( ) . unwrap_or ( 0 ) ;
1065+ let size = std:: cmp:: min ( size_uncapped, alloc_size - start) ;
1066+ Some ( ( start, size) )
1067+ }
1068+
1069+ let mut done = FxHashSet :: default ( ) ;
1070+ while let Some ( id) = ids. pop ( ) {
1071+ if !done. insert ( id) {
1072+ continue ;
1073+ }
1074+ let info = self . get_alloc_info ( id) ;
1075+
1076+ // If there is no data behind this pointer, skip this.
1077+ if !matches ! ( info. kind, AllocKind :: LiveData ) {
1078+ continue ;
1079+ }
1080+
1081+ let alloc_base: u64 = {
1082+ // Keep the alloc here so the borrow checker is happy
1083+ let alloc = self . get_alloc_raw ( id) ?;
1084+ // No need for black_box trickery since we actually use the address
1085+ alloc. get_bytes_unchecked_raw ( ) . expose_provenance ( ) . try_into ( ) . unwrap ( )
1086+ } ;
1087+ let alloc_size = info. size . bytes ( ) ;
1088+
1089+ // Find reads which overlap with the current allocation
1090+ for rg in & reads {
1091+ if let Some ( ( start, size) ) = get_start_size ( rg. clone ( ) , alloc_base, alloc_size) {
1092+ let alloc = self . get_alloc_raw ( id) ?;
1093+ let prov_map = alloc. provenance ( ) ;
1094+ // Only iterate on the bytes that overlap with the access
1095+ for i in start..start + size {
1096+ // We can be conservative and only expose provenances actually read
1097+ if let Some ( prov) = prov_map. get ( Size :: from_bytes ( 1 ) , self )
1098+ && rg. contains ( & ( alloc_base + i) )
1099+ {
1100+ M :: expose_provenance ( self , prov) ?;
1101+ if let Some ( id) = prov. get_alloc_id ( ) {
1102+ ids. push ( id) ;
1103+ }
1104+ }
1105+ }
1106+ }
1107+ }
1108+
1109+ // Then do the same thing for writes, marking down that a write happened
1110+ for rg in & writes {
1111+ if let Some ( ( start, size) ) = get_start_size ( rg. clone ( ) , alloc_base, alloc_size)
1112+ && self . get_alloc_mutability ( id) ?. is_mut ( )
1113+ {
1114+ let alloc_mut = self . get_alloc_raw_mut ( id) ?. 0 ;
1115+ let range =
1116+ AllocRange { start : Size :: from_bytes ( start) , size : Size :: from_bytes ( size) } ;
1117+ alloc_mut. mark_foreign_write ( range) ;
1118+ }
10211119 }
10221120 }
10231121 interp_ok ( ( ) )
0 commit comments