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