@@ -979,12 +979,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
979
979
}
980
980
981
981
/// 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.
985
987
///
986
988
/// 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 > {
988
994
let mut done = FxHashSet :: default ( ) ;
989
995
let mut todo = ids;
990
996
while let Some ( id) = todo. pop ( ) {
@@ -999,25 +1005,117 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
999
1005
continue ;
1000
1006
}
1001
1007
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 .
1003
1009
let alloc = self . get_alloc_raw ( id) ?;
1004
1010
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
+ }
1006
1015
if let Some ( id) = prov. get_alloc_id ( ) {
1007
1016
todo. push ( id) ;
1008
1017
}
1009
1018
}
1019
+
1010
1020
// Also expose the provenance of the interpreter-level allocation, so it can
1011
1021
// be read by FFI. The `black_box` is defensive programming as LLVM likes
1012
1022
// 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
+ }
1021
1119
}
1022
1120
}
1023
1121
interp_ok ( ( ) )
0 commit comments