Skip to content

Commit 3ed8b69

Browse files
committed
Auto merge of #44965 - oconnor663:res_init_glibc, r=dtolnay
replace libc::res_init with res_init_if_glibc_before_2_26 The previous workaround for gibc's res_init bug is not thread-safe on other implementations of libc, and it can cause crashes. Use a runtime check to make sure we only call res_init when we need to, which is also when it's safe. See #43592. ~This PR is returning an InvalidData IO error if the glibc version string fails to parse. We could also have treated that case as "not glibc", and gotten rid of the idea that these functions could return an error. (Though I'm not a huge fan of ignoring error returns from `res_init` in any case.) Do other folks agree with these design choices?~ I'm pretty new to hacking on libstd. Is there an easy way to build a toy rust program against my changes to test this, other than doing an entire `sudo make install` on my system? What's the usual workflow?
2 parents ed1cffd + 9602fe1 commit 3ed8b69

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

src/libstd/sys/unix/l4re.rs

+4
Original file line numberDiff line numberDiff line change
@@ -437,5 +437,9 @@ pub mod net {
437437
pub fn lookup_host(_: &str) -> io::Result<LookupHost> {
438438
unimpl!();
439439
}
440+
441+
pub fn res_init_if_glibc_before_2_26() -> io::Result<()> {
442+
unimpl!();
443+
}
440444
}
441445

src/libstd/sys/unix/net.rs

+79
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,82 @@ impl FromInner<c_int> for Socket {
355355
impl IntoInner<c_int> for Socket {
356356
fn into_inner(self) -> c_int { self.0.into_raw() }
357357
}
358+
359+
// In versions of glibc prior to 2.26, there's a bug where the DNS resolver
360+
// will cache the contents of /etc/resolv.conf, so changes to that file on disk
361+
// can be ignored by a long-running program. That can break DNS lookups on e.g.
362+
// laptops where the network comes and goes. See
363+
// https://sourceware.org/bugzilla/show_bug.cgi?id=984. Note however that some
364+
// distros including Debian have patched glibc to fix this for a long time.
365+
//
366+
// A workaround for this bug is to call the res_init libc function, to clear
367+
// the cached configs. Unfortunately, while we believe glibc's implementation
368+
// of res_init is thread-safe, we know that other implementations are not
369+
// (https://github.com/rust-lang/rust/issues/43592). Code here in libstd could
370+
// try to synchronize its res_init calls with a Mutex, but that wouldn't
371+
// protect programs that call into libc in other ways. So instead of calling
372+
// res_init unconditionally, we call it only when we detect we're linking
373+
// against glibc version < 2.26. (That is, when we both know its needed and
374+
// believe it's thread-safe).
375+
pub fn res_init_if_glibc_before_2_26() -> io::Result<()> {
376+
// If the version fails to parse, we treat it the same as "not glibc".
377+
if let Some(Ok(version_str)) = glibc_version_cstr().map(CStr::to_str) {
378+
if let Some(version) = parse_glibc_version(version_str) {
379+
if version < (2, 26) {
380+
let ret = unsafe { libc::res_init() };
381+
if ret != 0 {
382+
return Err(io::Error::last_os_error());
383+
}
384+
}
385+
}
386+
}
387+
Ok(())
388+
}
389+
390+
fn glibc_version_cstr() -> Option<&'static CStr> {
391+
weak! {
392+
fn gnu_get_libc_version() -> *const libc::c_char
393+
}
394+
if let Some(f) = gnu_get_libc_version.get() {
395+
unsafe { Some(CStr::from_ptr(f())) }
396+
} else {
397+
None
398+
}
399+
}
400+
401+
// Returns Some((major, minor)) if the string is a valid "x.y" version,
402+
// ignoring any extra dot-separated parts. Otherwise return None.
403+
fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
404+
let mut parsed_ints = version.split(".").map(str::parse::<usize>).fuse();
405+
match (parsed_ints.next(), parsed_ints.next()) {
406+
(Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
407+
_ => None
408+
}
409+
}
410+
411+
#[cfg(test)]
412+
mod test {
413+
use super::*;
414+
415+
#[test]
416+
fn test_res_init() {
417+
// This mostly just tests that the weak linkage doesn't panic wildly...
418+
res_init_if_glibc_before_2_26().unwrap();
419+
}
420+
421+
#[test]
422+
fn test_parse_glibc_version() {
423+
let cases = [
424+
("0.0", Some((0, 0))),
425+
("01.+2", Some((1, 2))),
426+
("3.4.5.six", Some((3, 4))),
427+
("1", None),
428+
("1.-2", None),
429+
("1.foo", None),
430+
("foo.1", None),
431+
];
432+
for &(version_str, parsed) in cases.iter() {
433+
assert_eq!(parsed, parse_glibc_version(version_str));
434+
}
435+
}
436+
}

src/libstd/sys_common/net.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,15 @@ pub fn lookup_host(host: &str) -> io::Result<LookupHost> {
175175
},
176176
#[cfg(unix)]
177177
Err(e) => {
178-
// The lookup failure could be caused by using a stale /etc/resolv.conf.
179-
// See https://github.com/rust-lang/rust/issues/41570.
180-
// We therefore force a reload of the nameserver information.
181-
c::res_init();
178+
// If we're running glibc prior to version 2.26, the lookup
179+
// failure could be caused by caching a stale /etc/resolv.conf.
180+
// We need to call libc::res_init() to clear the cache. But we
181+
// shouldn't call it in on any other platform, because other
182+
// res_init implementations aren't thread-safe. See
183+
// https://github.com/rust-lang/rust/issues/41570 and
184+
// https://github.com/rust-lang/rust/issues/43592.
185+
use sys::net::res_init_if_glibc_before_2_26;
186+
let _ = res_init_if_glibc_before_2_26();
182187
Err(e)
183188
},
184189
// the cfg is needed here to avoid an "unreachable pattern" warning

0 commit comments

Comments
 (0)