Skip to content

Commit 31c1afa

Browse files
committed
Auto merge of #1797 - atsmtat:env-isolation, r=RalfJung
fix: avoid stopping machine upon running env operations in isolation get and set current dir operations used to halt the machine by throwing an exception in isolation mode. This change updates them to return a dummy `NotFound` error instead, and keep the machine running. I started with a custom error using `ErrorKind::Other`, but since it can't be mapped to a raw OS error, I dropped it. `NotFound` kind of make sense for get operations, but not much for set operations. But that's the only error supported for windows currently.
2 parents f73db5c + ba64f48 commit 31c1afa

11 files changed

+215
-60
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,13 @@ environment variable:
219219
* `-Zmiri-disable-isolation` disables host isolation. As a consequence,
220220
the program has access to host resources such as environment variables, file
221221
systems, and randomness.
222+
* `-Zmiri-isolation-error=<action>` configures Miri's response to operations
223+
requiring host access while isolation is enabled. `abort`, `hide`, `warn`,
224+
and `warn-nobacktrace` are the supported actions. Default action is `abort`
225+
which halts the machine. Rest of the actions configure it to return an error
226+
code for the op and continue executing. `warn` prints backtrace that could
227+
be used to trace the call. `warn-nobacktrace` is less verbose without
228+
backtrace. `hide` hides the warning.
222229
* `-Zmiri-env-exclude=<var>` keeps the `var` environment variable isolated from
223230
the host so that it cannot be accessed by the program. Can be used multiple
224231
times to exclude several variables. On Windows, the `TERM` environment

src/bin/miri.rs

+34-1
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ fn main() {
263263
let mut miri_config = miri::MiriConfig::default();
264264
let mut rustc_args = vec![];
265265
let mut after_dashdash = false;
266+
267+
// If user has explicitly enabled/disabled isolation
268+
let mut isolation_enabled: Option<bool> = None;
266269
for arg in env::args() {
267270
if rustc_args.is_empty() {
268271
// Very first arg: binary name.
@@ -291,7 +294,37 @@ fn main() {
291294
miri_config.check_abi = false;
292295
}
293296
"-Zmiri-disable-isolation" => {
294-
miri_config.communicate = true;
297+
if matches!(isolation_enabled, Some(true)) {
298+
panic!(
299+
"-Zmiri-disable-isolation cannot be used along with -Zmiri-isolation-error"
300+
);
301+
} else {
302+
isolation_enabled = Some(false);
303+
}
304+
miri_config.isolated_op = miri::IsolatedOp::Allow;
305+
}
306+
arg if arg.starts_with("-Zmiri-isolation-error=") => {
307+
if matches!(isolation_enabled, Some(false)) {
308+
panic!(
309+
"-Zmiri-isolation-error cannot be used along with -Zmiri-disable-isolation"
310+
);
311+
} else {
312+
isolation_enabled = Some(true);
313+
}
314+
315+
miri_config.isolated_op = match arg
316+
.strip_prefix("-Zmiri-isolation-error=")
317+
.unwrap()
318+
{
319+
"abort" => miri::IsolatedOp::Reject(miri::RejectOpWith::Abort),
320+
"hide" => miri::IsolatedOp::Reject(miri::RejectOpWith::NoWarning),
321+
"warn" => miri::IsolatedOp::Reject(miri::RejectOpWith::Warning),
322+
"warn-nobacktrace" =>
323+
miri::IsolatedOp::Reject(miri::RejectOpWith::WarningWithoutBacktrace),
324+
_ => panic!(
325+
"-Zmiri-isolation-error must be `abort`, `hide`, `warn`, or `warn-nobacktrace`"
326+
),
327+
};
295328
}
296329
"-Zmiri-ignore-leaks" => {
297330
miri_config.ignore_leaks = true;

src/diagnostics.rs

+26-15
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ pub enum NonHaltingDiagnostic {
5252
CreatedCallId(CallId),
5353
CreatedAlloc(AllocId),
5454
FreedAlloc(AllocId),
55+
RejectedIsolatedOp(String),
56+
}
57+
58+
/// Level of Miri specific diagnostics
59+
enum DiagLevel {
60+
Error,
61+
Warning,
62+
Note,
5563
}
5664

5765
/// Emit a custom diagnostic without going through the miri-engine machinery
@@ -76,7 +84,7 @@ pub fn report_error<'tcx, 'mir>(
7684
#[rustfmt::skip]
7785
let helps = match info {
7886
UnsupportedInIsolation(_) =>
79-
vec![(None, format!("pass the flag `-Zmiri-disable-isolation` to disable isolation"))],
87+
vec![(None, format!("pass the flag `-Zmiri-disable-isolation` to disable isolation; or pass `-Zmiri-isolation-error=warn to configure Miri to return an error code from isolated operations and continue with a warning"))],
8088
ExperimentalUb { url, .. } =>
8189
vec![
8290
(None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental")),
@@ -137,7 +145,7 @@ pub fn report_error<'tcx, 'mir>(
137145
let msg = e.to_string();
138146
report_msg(
139147
*ecx.tcx,
140-
/*error*/ true,
148+
DiagLevel::Error,
141149
&if let Some(title) = title { format!("{}: {}", title, msg) } else { msg.clone() },
142150
msg,
143151
helps,
@@ -174,18 +182,19 @@ pub fn report_error<'tcx, 'mir>(
174182
/// Also emits a full stacktrace of the interpreter stack.
175183
fn report_msg<'tcx>(
176184
tcx: TyCtxt<'tcx>,
177-
error: bool,
185+
diag_level: DiagLevel,
178186
title: &str,
179187
span_msg: String,
180188
mut helps: Vec<(Option<SpanData>, String)>,
181189
stacktrace: &[FrameInfo<'tcx>],
182190
) {
183191
let span = stacktrace.first().map_or(DUMMY_SP, |fi| fi.span);
184-
let mut err = if error {
185-
tcx.sess.struct_span_err(span, title)
186-
} else {
187-
tcx.sess.diagnostic().span_note_diag(span, title)
192+
let mut err = match diag_level {
193+
DiagLevel::Error => tcx.sess.struct_span_err(span, title),
194+
DiagLevel::Warning => tcx.sess.struct_span_warn(span, title),
195+
DiagLevel::Note => tcx.sess.diagnostic().span_note_diag(span, title),
188196
};
197+
189198
// Show main message.
190199
if span != DUMMY_SP {
191200
err.span_label(span, span_msg);
@@ -303,15 +312,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
303312
CreatedCallId(id) => format!("function call with id {}", id),
304313
CreatedAlloc(AllocId(id)) => format!("created allocation with id {}", id),
305314
FreedAlloc(AllocId(id)) => format!("freed allocation with id {}", id),
315+
RejectedIsolatedOp(ref op) =>
316+
format!("`{}` was made to return an error due to isolation", op),
306317
};
307-
report_msg(
308-
*this.tcx,
309-
/*error*/ false,
310-
"tracking was triggered",
311-
msg,
312-
vec![],
313-
&stacktrace,
314-
);
318+
319+
let (title, diag_level) = match e {
320+
RejectedIsolatedOp(_) =>
321+
("operation rejected by isolation", DiagLevel::Warning),
322+
_ => ("tracking was triggered", DiagLevel::Note),
323+
};
324+
325+
report_msg(*this.tcx, diag_level, title, msg, vec![], &stacktrace);
315326
}
316327
});
317328
}

src/eval.rs

+33-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,35 @@ pub enum AlignmentCheck {
2222
Int,
2323
}
2424

25+
#[derive(Copy, Clone, Debug, PartialEq)]
26+
pub enum RejectOpWith {
27+
/// Isolated op is rejected with an abort of the machine.
28+
Abort,
29+
30+
/// If not Abort, miri returns an error for an isolated op.
31+
/// Following options determine if user should be warned about such error.
32+
/// Do not print warning about rejected isolated op.
33+
NoWarning,
34+
35+
/// Print a warning about rejected isolated op, with backtrace.
36+
Warning,
37+
38+
/// Print a warning about rejected isolated op, without backtrace.
39+
WarningWithoutBacktrace,
40+
}
41+
42+
#[derive(Copy, Clone, Debug, PartialEq)]
43+
pub enum IsolatedOp {
44+
/// Reject an op requiring communication with the host. By
45+
/// default, miri rejects the op with an abort. If not, it returns
46+
/// an error code, and prints a warning about it. Warning levels
47+
/// are controlled by `RejectOpWith` enum.
48+
Reject(RejectOpWith),
49+
50+
/// Execute op requiring communication with the host, i.e. disable isolation.
51+
Allow,
52+
}
53+
2554
/// Configuration needed to spawn a Miri instance.
2655
#[derive(Clone)]
2756
pub struct MiriConfig {
@@ -33,8 +62,8 @@ pub struct MiriConfig {
3362
pub check_alignment: AlignmentCheck,
3463
/// Controls function [ABI](Abi) checking.
3564
pub check_abi: bool,
36-
/// Determines if communication with the host environment is enabled.
37-
pub communicate: bool,
65+
/// Action for an op requiring communication with the host.
66+
pub isolated_op: IsolatedOp,
3867
/// Determines if memory leaks should be ignored.
3968
pub ignore_leaks: bool,
4069
/// Environment variables that should always be isolated from the host.
@@ -68,7 +97,7 @@ impl Default for MiriConfig {
6897
stacked_borrows: true,
6998
check_alignment: AlignmentCheck::Int,
7099
check_abi: true,
71-
communicate: false,
100+
isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
72101
ignore_leaks: false,
73102
excluded_env_vars: vec![],
74103
args: vec![],
@@ -233,7 +262,7 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
233262
}
234263
SchedulingAction::ExecuteTimeoutCallback => {
235264
assert!(
236-
ecx.machine.communicate,
265+
ecx.machine.communicate(),
237266
"scheduler callbacks require disabled isolation, but the code \
238267
that created the callback did not check it"
239268
);

src/helpers.rs

+35-12
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
140140

141141
let mut data = vec![0; usize::try_from(len).unwrap()];
142142

143-
if this.machine.communicate {
143+
if this.machine.communicate() {
144144
// Fill the buffer using the host's rng.
145145
getrandom::getrandom(&mut data)
146146
.map_err(|err| err_unsup_format!("host getrandom failed: {}", err))?;
@@ -391,12 +391,32 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
391391
/// disabled. It returns an error using the `name` of the foreign function if this is not the
392392
/// case.
393393
fn check_no_isolation(&self, name: &str) -> InterpResult<'tcx> {
394-
if !self.eval_context_ref().machine.communicate {
395-
isolation_error(name)?;
394+
if !self.eval_context_ref().machine.communicate() {
395+
self.reject_in_isolation(name, RejectOpWith::Abort)?;
396396
}
397397
Ok(())
398398
}
399399

400+
/// Helper function used inside the shims of foreign functions which reject the op
401+
/// when isolation is enabled. It is used to print a warning/backtrace about the rejection.
402+
fn reject_in_isolation(&self, op_name: &str, reject_with: RejectOpWith) -> InterpResult<'tcx> {
403+
let this = self.eval_context_ref();
404+
match reject_with {
405+
RejectOpWith::Abort => isolation_abort_error(op_name),
406+
RejectOpWith::WarningWithoutBacktrace => {
407+
this.tcx
408+
.sess
409+
.warn(&format!("`{}` was made to return an error due to isolation", op_name));
410+
Ok(())
411+
}
412+
RejectOpWith::Warning => {
413+
register_diagnostic(NonHaltingDiagnostic::RejectedIsolatedOp(op_name.to_string()));
414+
Ok(())
415+
}
416+
RejectOpWith::NoWarning => Ok(()), // no warning
417+
}
418+
}
419+
400420
/// Helper function used inside the shims of foreign functions to assert that the target OS
401421
/// is `target_os`. It panics showing a message with the `name` of the foreign function
402422
/// if this is not the case.
@@ -440,15 +460,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
440460
this.read_scalar(&errno_place.into())?.check_init()
441461
}
442462

443-
/// Sets the last OS error using a `std::io::Error`. This function tries to produce the most
463+
/// Sets the last OS error using a `std::io::ErrorKind`. This function tries to produce the most
444464
/// similar OS error from the `std::io::ErrorKind` and sets it as the last OS error.
445-
fn set_last_error_from_io_error(&mut self, e: std::io::Error) -> InterpResult<'tcx> {
465+
fn set_last_error_from_io_error(&mut self, err_kind: std::io::ErrorKind) -> InterpResult<'tcx> {
446466
use std::io::ErrorKind::*;
447467
let this = self.eval_context_mut();
448468
let target = &this.tcx.sess.target;
449469
let target_os = &target.os;
450470
let last_error = if target.families.contains(&"unix".to_owned()) {
451-
this.eval_libc(match e.kind() {
471+
this.eval_libc(match err_kind {
452472
ConnectionRefused => "ECONNREFUSED",
453473
ConnectionReset => "ECONNRESET",
454474
PermissionDenied => "EPERM",
@@ -464,18 +484,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
464484
AlreadyExists => "EEXIST",
465485
WouldBlock => "EWOULDBLOCK",
466486
_ => {
467-
throw_unsup_format!("io error {} cannot be transformed into a raw os error", e)
487+
throw_unsup_format!(
488+
"io error {:?} cannot be transformed into a raw os error",
489+
err_kind
490+
)
468491
}
469492
})?
470493
} else if target.families.contains(&"windows".to_owned()) {
471494
// FIXME: we have to finish implementing the Windows equivalent of this.
472495
this.eval_windows(
473496
"c",
474-
match e.kind() {
497+
match err_kind {
475498
NotFound => "ERROR_FILE_NOT_FOUND",
476499
_ => throw_unsup_format!(
477-
"io error {} cannot be transformed into a raw os error",
478-
e
500+
"io error {:?} cannot be transformed into a raw os error",
501+
err_kind
479502
),
480503
},
481504
)?
@@ -501,7 +524,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
501524
match result {
502525
Ok(ok) => Ok(ok),
503526
Err(e) => {
504-
self.eval_context_mut().set_last_error_from_io_error(e)?;
527+
self.eval_context_mut().set_last_error_from_io_error(e.kind())?;
505528
Ok((-1).into())
506529
}
507530
}
@@ -651,7 +674,7 @@ where
651674
throw_ub_format!("incorrect number of arguments: got {}, expected {}", args.len(), N)
652675
}
653676

654-
pub fn isolation_error(name: &str) -> InterpResult<'static> {
677+
pub fn isolation_abort_error(name: &str) -> InterpResult<'static> {
655678
throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!(
656679
"{} not available when isolation is enabled",
657680
name,

src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ pub use crate::diagnostics::{
5959
register_diagnostic, report_error, EvalContextExt as DiagnosticsEvalContextExt,
6060
NonHaltingDiagnostic, TerminationInfo,
6161
};
62-
pub use crate::eval::{create_ecx, eval_main, AlignmentCheck, MiriConfig};
62+
pub use crate::eval::{
63+
create_ecx, eval_main, AlignmentCheck, IsolatedOp, MiriConfig, RejectOpWith,
64+
};
6365
pub use crate::helpers::EvalContextExt as HelpersEvalContextExt;
6466
pub use crate::machine::{
6567
AllocExtra, Evaluator, FrameData, MemoryExtra, MiriEvalContext, MiriEvalContextExt,

src/machine.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,10 @@ pub struct Evaluator<'mir, 'tcx> {
263263
/// TLS state.
264264
pub(crate) tls: TlsData<'tcx>,
265265

266-
/// If enabled, the `env_vars` field is populated with the host env vars during initialization
267-
/// and random number generation is delegated to the host.
268-
pub(crate) communicate: bool,
266+
/// What should Miri do when an op requires communicating with the host,
267+
/// such as accessing host env vars, random number generation, and
268+
/// file system access.
269+
pub(crate) isolated_op: IsolatedOp,
269270

270271
/// Whether to enforce the validity invariant.
271272
pub(crate) validate: bool,
@@ -314,7 +315,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
314315
argv: None,
315316
cmd_line: None,
316317
tls: TlsData::default(),
317-
communicate: config.communicate,
318+
isolated_op: config.isolated_op,
318319
validate: config.validate,
319320
enforce_abi: config.check_abi,
320321
file_handler: Default::default(),
@@ -328,6 +329,10 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
328329
exported_symbols_cache: FxHashMap::default(),
329330
}
330331
}
332+
333+
pub(crate) fn communicate(&self) -> bool {
334+
self.isolated_op == IsolatedOp::Allow
335+
}
331336
}
332337

333338
/// A rustc InterpCx for Miri.

0 commit comments

Comments
 (0)