-
Notifications
You must be signed in to change notification settings - Fork 365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
flate2-rs unsound due to calls to getenv
allowing safe data race
#926
Comments
This is honestly a bug in the stdlib that setenv is exposed in safe code. Work is underway to potentially fix this in the stdlib, although it's tricky to find a way without breakage. In the meantime though, I'm kind of with Alex here that going "looking for FFI crates where this could be a problem" isn't exactly helpful 🤷 |
Based on a quick look I agree. We don't tend to track transitive unsoundness and would rather track a vuln against std for this. If we don't already have one for this (I don't see one) I'd suggest filing a soundness vuln against std. But even that seems tricky as I don't see a canonical upstream tracking issue for this. This is the closest I've seen so far: rust-lang/rust#27970 It seems like |
You cannot create soundness bug using only Rust with this, |
Again, the bug here is in the stdlib exposing the setenv API as safe in the first place. I suspect the logic you're using is the one that was used at a time, but it's flawed: Rust doesn't exist in a vacuum. For years, C code has held that setenv is unsafe to use in the context of multiple threads, but that getenv is safe (this fact is even documented as part of POSIX). This API is too pervasively used to imagine that realistically Rust can assume safety given that the only users of it are from within Rust. Note that in many cases, there's no viable solution here for C bindings. In some cases you can cut out the functionality or make it an IMO, Rust should change the behavior of the Also, in practice, this isn't a problem 99% of the time, which is why despite the same issue being present in libstd, it still hasn't been fixed yet. Don't get me wrong: it should be fixed, but... realistically, nobody who isn't setting out to try and trigger this sort of bug will hit it. You have to aggressively perform set_var in a loop from a different thread than the reader, and that just isn't common behavior. Again, this isn't to say that things are fine without a fix here, but just to say waiting on a fix from stdlib is probably fine, rather than either arguing every C library binding needs to do something about a stdlib soundness hole. ¹ And even if patching is viable (e.g. in -sys crates that bundle/vendor) it is honestly kinda dodgy. Personally, I would really rather random -sys crates I use not be applying various custom patches to the underlying C libs they're binding to, especially for something unlikely to cause issues like this. |
While I initially said the same thing, it turns out this isn't true. Here's a user saying that the bug (in time & chrono) was discovered in real-world usage. Personally, I made the decision to eliminate the unsound behavior, though I am reconsidering that decision. |
I'm not sure how relevant this is, but wouldn't this implementation of #[no_mangle]
pub unsafe extern "C" fn setenv(key: *const c_char, value: *const c_char, overwrite: c_int) -> c_int {
if get_running_threads() > 1 {
unreachable_unchecked()
} else {
real_setenv(key, value, overwrite)
}
} |
I'm not sure how Also in what way is the existence of more than one thread "unreachable"? If there's more than one thread, that appears to have undefined behavior. From what I can tell the only way to soundly implement this sort of thing is acquiring the OS-specific environment lock:
...and since that isn't directly exposed, the only way to do so is by (vicariously) invoking e.g. |
I was using Rust syntax as an example. My point was that, to my knowledge, calling setenv in the presence of multiple threads is UB according to POSIX. |
FYI, I opened a rust-internals thread asking if it would make sense to expose functions for obtaining |
Yes, notably |
Also, I've changed my mind from my earlier position. I think it would make sense to go ahead and file an advisory for this. AFAIK there's no change to |
This isn't true. Here are some of the possible changes that would fix
The ship has probably sailed for 1, but last I checked, 2 was still being discussed. There are likely others still as well. |
I don't think it has: if there are no other threads right now, then that means there are also no threads that could be spawning new threads that would make this a race. |
@thomcc I don't see how 1 helps. The problem is one of synchronization, and in that regard it already has locks and provides a safe API. The problem is one of unsoundness if there is another caller which isn't participating in that synchronization. Slapping
Aah, indeed... |
The safety contract of I think there's a possible solution between 1 and 2 though.
As there's no sound way to have the changes visible to FFI at the moment since the lock is not exposed, the first is not really a breaking change. And the second recovers the lost ability, but makes it actually explicit that there's a real thread safety issue. |
It's very much a breaking change, insofar as people today call |
What about something like this? (Rust-flavored pseudocode, don't pay too much attention to the details) static RUST_ENV: RwLock<HashMap<String, String>> = Default::default();
pub fn set_var(key: &str, value: &str) {
RUST_ENV.write().insert(key, value);
}
pub unsafe fn set_system_var(key: &str, value: &str) {
libc::setenv(key, value);
}
pub fn var(key: &str) -> Option<String> {
RUST_ENV.get(key).or_else(|| libc::getenv(key))
}
impl Command {
pub fn spawn(&mut self) -> Result<Child> {
self.envs(RUST_ENV.iter()).spawn_impl()
}
} |
This is kinda how Java does with the mutable shared state being System properties, and only providing a System.getenv() method but no set method. So already recognized as bad practice in java land Maybe we should torpedo set_var entirely as Java effectively does, and add notes to the libc crate on the gotchas. |
We can't do that without breaking backwards compatibility. I suppose a very literal reading of the breaking change policy could allow this, but it's definitely not reasonable. |
To me it's really looking like the best course of action would be to deprecate
|
That's fair, but unsoundness requiring a deprecated function is still unsoundness. It seems fair to ignore it, though, considering the alternatives. |
Sum summarum: flat2-rs unsoundness There are upstream rust-lang/rust issue(s) and libs are well aware of this by now with extensive discussions. There is also another issue more general in nature: #1190 time was only re-filed because the maintainer wanted it back - #1242 (comment) chrono was only re-filed to advertise fix because there was RiR localtime_r in 0.4.20 - #1310 Filing something against something where the flate2 maintainer disagrees that this should be done has made this controversial in addition that there can be no fix in flate2 itself. Closing this issue in favor of the upstream issue rust-lang/rust#90308 - if needed re: specific to flate2 - please re-open. |
The logic is for various reasons in conventional all software development:
|
Please don't necro-post. The discussion here is long over, and if there's new things to discuss it should be done in a new issue. |
rust-lang/flate2-rs#272
Closed without being addressed leaving security vulnerability. Unsound.
The text was updated successfully, but these errors were encountered: