Skip to content

Commit 2a1e0ce

Browse files
committed
Auto merge of rust-lang#3184 - RalfJung:getenv, r=RalfJung
detect and test for data races between setenv and getenv But only on Unix; Windows doesn't have such a data race. Also make target_os_is_unix properly check for unix, which then makes our completely empty android files useless.
2 parents 933bdbc + 4b69e52 commit 2a1e0ce

File tree

10 files changed

+79
-71
lines changed

10 files changed

+79
-71
lines changed

src/tools/miri/src/helpers.rs

+5-10
Original file line numberDiff line numberDiff line change
@@ -565,10 +565,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
565565
/// is part of the UNIX family. It panics showing a message with the `name` of the foreign function
566566
/// if this is not the case.
567567
fn assert_target_os_is_unix(&self, name: &str) {
568-
assert!(
569-
target_os_is_unix(self.eval_context_ref().tcx.sess.target.os.as_ref()),
570-
"`{name}` is only available for supported UNIX family targets",
571-
);
568+
assert!(self.target_os_is_unix(), "`{name}` is only available for unix targets",);
569+
}
570+
571+
fn target_os_is_unix(&self) -> bool {
572+
self.eval_context_ref().tcx.sess.target.families.iter().any(|f| f == "unix")
572573
}
573574

574575
/// Get last error variable as a place, lazily allocating thread-local storage for it if
@@ -1161,12 +1162,6 @@ pub fn get_local_crates(tcx: TyCtxt<'_>) -> Vec<CrateNum> {
11611162
local_crates
11621163
}
11631164

1164-
/// Helper function used inside the shims of foreign functions to check that
1165-
/// `target_os` is a supported UNIX OS.
1166-
pub fn target_os_is_unix(target_os: &str) -> bool {
1167-
matches!(target_os, "linux" | "macos" | "freebsd" | "android")
1168-
}
1169-
11701165
pub(crate) fn bool_to_simd_element(b: bool, size: Size) -> Scalar<Provenance> {
11711166
// SIMD uses all-1 as pattern for "true". In two's complement,
11721167
// -1 has all its bits set to one and `from_int` will truncate or

src/tools/miri/src/shims/env.rs

+33-20
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use rustc_middle::ty::layout::LayoutOf;
99
use rustc_middle::ty::Ty;
1010
use rustc_target::abi::Size;
1111

12-
use crate::helpers::target_os_is_unix;
1312
use crate::*;
1413

1514
/// Check whether an operation that writes to a target buffer was successful.
@@ -53,16 +52,15 @@ impl<'tcx> EnvVars<'tcx> {
5352
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
5453
config: &MiriConfig,
5554
) -> InterpResult<'tcx> {
56-
let target_os = ecx.tcx.sess.target.os.as_ref();
57-
55+
// Initialize the `env_vars` map.
5856
// Skip the loop entirely if we don't want to forward anything.
5957
if ecx.machine.communicate() || !config.forwarded_env_vars.is_empty() {
6058
for (name, value) in &config.env {
6159
let forward = ecx.machine.communicate()
6260
|| config.forwarded_env_vars.iter().any(|v| **v == *name);
6361
if forward {
64-
let var_ptr = match target_os {
65-
target if target_os_is_unix(target) =>
62+
let var_ptr = match ecx.tcx.sess.target.os.as_ref() {
63+
_ if ecx.target_os_is_unix() =>
6664
alloc_env_var_as_c_str(name.as_ref(), value.as_ref(), ecx)?,
6765
"windows" => alloc_env_var_as_wide_str(name.as_ref(), value.as_ref(), ecx)?,
6866
unsupported =>
@@ -75,7 +73,17 @@ impl<'tcx> EnvVars<'tcx> {
7573
}
7674
}
7775
}
78-
ecx.update_environ()
76+
77+
// Initialize the `environ` pointer when needed.
78+
if ecx.target_os_is_unix() {
79+
// This is memory backing an extern static, hence `ExternStatic`, not `Env`.
80+
let layout = ecx.machine.layouts.mut_raw_ptr;
81+
let place = ecx.allocate(layout, MiriMemoryKind::ExternStatic.into())?;
82+
ecx.write_null(&place)?;
83+
ecx.machine.env_vars.environ = Some(place);
84+
ecx.update_environ()?;
85+
}
86+
Ok(())
7987
}
8088

8189
pub(crate) fn cleanup<'mir>(
@@ -87,9 +95,11 @@ impl<'tcx> EnvVars<'tcx> {
8795
ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?;
8896
}
8997
// Deallocate environ var list.
90-
let environ = ecx.machine.env_vars.environ.as_ref().unwrap();
91-
let old_vars_ptr = ecx.read_pointer(environ)?;
92-
ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
98+
if ecx.target_os_is_unix() {
99+
let environ = ecx.machine.env_vars.environ.as_ref().unwrap();
100+
let old_vars_ptr = ecx.read_pointer(environ)?;
101+
ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
102+
}
93103
Ok(())
94104
}
95105
}
@@ -127,6 +137,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
127137

128138
let name_ptr = this.read_pointer(name_op)?;
129139
let name = this.read_os_str_from_c_str(name_ptr)?;
140+
this.read_environ()?;
130141
Ok(match this.machine.env_vars.map.get(name) {
131142
Some(var_ptr) => {
132143
// The offset is used to strip the "{name}=" part of the string.
@@ -275,7 +286,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
275286
// Delete environment variable `{name}`
276287
if let Some(var) = this.machine.env_vars.map.remove(&name) {
277288
this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
278-
this.update_environ()?;
279289
}
280290
Ok(this.eval_windows("c", "TRUE"))
281291
} else {
@@ -284,7 +294,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
284294
if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
285295
this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
286296
}
287-
this.update_environ()?;
288297
Ok(this.eval_windows("c", "TRUE"))
289298
}
290299
}
@@ -431,15 +440,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
431440
fn update_environ(&mut self) -> InterpResult<'tcx> {
432441
let this = self.eval_context_mut();
433442
// Deallocate the old environ list, if any.
434-
if let Some(environ) = this.machine.env_vars.environ.as_ref() {
435-
let old_vars_ptr = this.read_pointer(environ)?;
443+
let environ = this.machine.env_vars.environ.as_ref().unwrap().clone();
444+
let old_vars_ptr = this.read_pointer(&environ)?;
445+
if !this.ptr_is_null(old_vars_ptr)? {
436446
this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
437-
} else {
438-
// No `environ` allocated yet, let's do that.
439-
// This is memory backing an extern static, hence `ExternStatic`, not `Env`.
440-
let layout = this.machine.layouts.mut_raw_ptr;
441-
let place = this.allocate(layout, MiriMemoryKind::ExternStatic.into())?;
442-
this.machine.env_vars.environ = Some(place);
443447
}
444448

445449
// Collect all the pointers to each variable in a vector.
@@ -459,8 +463,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
459463
let place = this.project_field(&vars_place, idx)?;
460464
this.write_pointer(var, &place)?;
461465
}
462-
this.write_pointer(vars_place.ptr(), &this.machine.env_vars.environ.clone().unwrap())?;
466+
this.write_pointer(vars_place.ptr(), &environ)?;
467+
468+
Ok(())
469+
}
463470

471+
/// Reads from the `environ` static.
472+
/// We don't actually care about the result, but we care about this potentially causing a data race.
473+
fn read_environ(&self) -> InterpResult<'tcx> {
474+
let this = self.eval_context_ref();
475+
let environ = this.machine.env_vars.environ.as_ref().unwrap();
476+
let _vars_ptr = this.read_pointer(environ)?;
464477
Ok(())
465478
}
466479

src/tools/miri/src/shims/foreign_items.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ use rustc_target::{
2222
};
2323

2424
use super::backtrace::EvalContextExt as _;
25-
use crate::helpers::target_os_is_unix;
2625
use crate::*;
2726

2827
/// Type of dynamic symbols (for `dlsym` et al)
@@ -1060,7 +1059,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
10601059
// Platform-specific shims
10611060
_ =>
10621061
return match this.tcx.sess.target.os.as_ref() {
1063-
target_os if target_os_is_unix(target_os) =>
1062+
_ if this.target_os_is_unix() =>
10641063
shims::unix::foreign_items::EvalContextExt::emulate_foreign_item_inner(
10651064
this, link_name, abi, args, dest,
10661065
),

src/tools/miri/src/shims/unix/android/foreign_items.rs

-30
This file was deleted.

src/tools/miri/src/shims/unix/android/mod.rs

-1
This file was deleted.

src/tools/miri/src/shims/unix/foreign_items.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use shims::unix::mem::EvalContextExt as _;
1515
use shims::unix::sync::EvalContextExt as _;
1616
use shims::unix::thread::EvalContextExt as _;
1717

18-
use shims::unix::android::foreign_items as android;
1918
use shims::unix::freebsd::foreign_items as freebsd;
2019
use shims::unix::linux::foreign_items as linux;
2120
use shims::unix::macos::foreign_items as macos;
@@ -32,11 +31,10 @@ fn is_dyn_sym(name: &str, target_os: &str) -> bool {
3231
// Give specific OSes a chance to allow their symbols.
3332
_ =>
3433
match target_os {
35-
"android" => android::is_dyn_sym(name),
3634
"freebsd" => freebsd::is_dyn_sym(name),
3735
"linux" => linux::is_dyn_sym(name),
3836
"macos" => macos::is_dyn_sym(name),
39-
target_os => panic!("unsupported Unix OS {target_os}"),
37+
_ => false,
4038
},
4139
}
4240
}
@@ -706,7 +704,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
706704
_ => {
707705
let target_os = &*this.tcx.sess.target.os;
708706
return match target_os {
709-
"android" => android::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
710707
"freebsd" => freebsd::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
711708
"linux" => linux::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),
712709
"macos" => macos::EvalContextExt::emulate_foreign_item_inner(this, link_name, abi, args, dest),

src/tools/miri/src/shims/unix/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ mod mem;
55
mod sync;
66
mod thread;
77

8-
mod android;
98
mod freebsd;
109
mod linux;
1110
mod macos;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//@compile-flags: -Zmiri-disable-isolation -Zmiri-preemption-rate=0
2+
//@ignore-target-windows: No libc on Windows
3+
4+
use std::env;
5+
use std::thread;
6+
7+
fn main() {
8+
let t = thread::spawn(|| unsafe {
9+
// Access the environment in another thread without taking the env lock.
10+
// This represents some C code that queries the environment.
11+
libc::getenv(b"TZ\0".as_ptr().cast()); //~ERROR: Data race detected
12+
});
13+
// Meanwhile, the main thread uses the "safe" Rust env accessor.
14+
env::set_var("MY_RUST_VAR", "Ferris");
15+
16+
t.join().unwrap();
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: Data race detected between (1) non-atomic write on thread `main` and (2) non-atomic read on thread `<unnamed>` at ALLOC. (2) just happened here
2+
--> $DIR/env-set_var-data-race.rs:LL:CC
3+
|
4+
LL | libc::getenv(b"TZ/0".as_ptr().cast());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between (1) non-atomic write on thread `main` and (2) non-atomic read on thread `<unnamed>` at ALLOC. (2) just happened here
6+
|
7+
help: and (1) occurred earlier here
8+
--> $DIR/env-set_var-data-race.rs:LL:CC
9+
|
10+
LL | env::set_var("MY_RUST_VAR", "Ferris");
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
13+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
14+
= note: BACKTRACE (of the first span):
15+
= note: inside closure at $DIR/env-set_var-data-race.rs:LL:CC
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to previous error
20+

src/tools/miri/tests/pass-dep/shims/env-cleanup-data-race.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
//@ignore-target-windows: No libc on Windows
33

44
use std::ffi::CStr;
5-
use std::ffi::CString;
65
use std::thread;
76

87
fn main() {
98
unsafe {
109
thread::spawn(|| {
1110
// Access the environment in another thread without taking the env lock
12-
let k = CString::new("MIRI_ENV_VAR_TEST".as_bytes()).unwrap();
13-
let s = libc::getenv(k.as_ptr()) as *const libc::c_char;
11+
let s = libc::getenv("MIRI_ENV_VAR_TEST\0".as_ptr().cast());
1412
if s.is_null() {
1513
panic!("null");
1614
}
@@ -19,5 +17,6 @@ fn main() {
1917
thread::yield_now();
2018
// After the main thread exits, env vars will be cleaned up -- but because we have not *joined*
2119
// the other thread, those accesses technically race with those in the other thread.
20+
// We don't want to emit an error here, though.
2221
}
2322
}

0 commit comments

Comments
 (0)