Guard reqwest::proxy libtests against concurrent ENV modification

As ENV is process global, modifying it within a thread (as is normal
for all test targets in a rust libtest) results in a concurrency
data-race.

This patch fences the two known cases of needing to modify this by
locking all ENV modifications, and collection of data dependent on
said modifications, into a narrow path isolated by a Mutex lock, with
no test assert!()'s while the Mutex is held
( to avoid a Mutex Posioning ).

However, the code doesn't treat lock failure as a special circumstance,
and if the lock fails, then the pre-existing risk of conccurent ENV
modification returns, and these 2 tests can still randomly fail, but
_in that situation_.

And as mutexes can _only_ be poisoned by the 2 threads holding this
mutex, this regression can now only trip into concurrency issues when
either of these 2 tests are already failing from _non test_ assertions,
so this patch still improves the status quo substantially.

Closes: https://github.com/seanmonstar/reqwest/issues/829
This commit is contained in:
Kent Fredric
2020-03-04 21:13:54 +13:00
committed by Sean McArthur
parent b42d2e0e0c
commit 6a41459862

View File

@@ -691,6 +691,8 @@ fn get_from_registry() -> SystemProxyMap {
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
use lazy_static::lazy_static;
impl Dst for Url {
fn scheme(&self) -> &str {
@@ -799,47 +801,75 @@ mod tests {
}
}
// Smallest possible content for a mutex
struct MutexInner;
lazy_static! {
static ref ENVLOCK: Mutex<MutexInner> = {
Mutex::new(MutexInner)
};
}
#[test]
fn test_get_sys_proxies_parsing() {
// Stop other threads from modifying process-global ENV while we are.
let _lock = ENVLOCK.lock();
// save system setting first.
let _g1 = env_guard("HTTP_PROXY");
let _g2 = env_guard("http_proxy");
// empty
assert_eq!(get_sys_proxies().contains_key("http"), false);
// Mock ENV, get the results, before doing assertions
// to avoid assert! -> panic! -> Mutex Poisoned.
let baseline_proxies = get_sys_proxies();
// the system proxy setting url is invalid.
env::set_var("http_proxy", "123465");
assert_eq!(get_sys_proxies().contains_key("http"), false);
let invalid_proxies = get_sys_proxies();
// set valid proxy
env::set_var("http_proxy", "http://127.0.0.1/");
let proxies = get_sys_proxies();
let p = &proxies["http"];
assert_eq!(p.scheme(), "http");
assert_eq!(p.host(), "127.0.0.1");
let valid_proxies = get_sys_proxies();
// reset user setting when guards drop
drop(_g1);
drop(_g2);
// Let other threads run now
drop(_lock);
assert_eq!(baseline_proxies.contains_key("http"), false);
assert_eq!(invalid_proxies.contains_key("http"), false);
let p = &valid_proxies["http"];
assert_eq!(p.scheme(), "http");
assert_eq!(p.host(), "127.0.0.1");
}
#[test]
fn test_get_sys_proxies_in_cgi() {
// Stop other threads from modifying process-global ENV while we are.
let _lock = ENVLOCK.lock();
// save system setting first.
let _g1 = env_guard("REQUEST_METHOD");
let _g2 = env_guard("HTTP_PROXY");
// Mock ENV, get the results, before doing assertions
// to avoid assert! -> panic! -> Mutex Poisoned.
env::set_var("HTTP_PROXY", "http://evil/");
// not in CGI yet
assert_eq!(get_sys_proxies()["http"].host(), "evil");
let baseline_proxies = get_sys_proxies();
// set like we're in CGI
env::set_var("REQUEST_METHOD", "GET");
assert!(!get_sys_proxies().contains_key("http"));
let cgi_proxies = get_sys_proxies();
// reset user setting when guards drop
drop(_g1);
drop(_g2);
// Let other threads run now
drop(_lock);
// not in CGI yet
assert_eq!(baseline_proxies["http"].host(), "evil");
// In CGI
assert!(!cgi_proxies.contains_key("http"));
}
/// Guard an environment variable, resetting it to the original value