Skip to content

Commit 4ccc6c6

Browse files
bors[bot]koute
andauthored
Merge #1672
1672: Make `uname` always safe r=asomers a=koute Currently `uname` doesn't check for errors and just blindly assumes that it always succeeds. According to the manpage this function can fail, even though no actual errors are defined: ``` RETURN VALUE Upon successful completion, a non-negative value shall be returned. Otherwise, -1 shall be returned and errno set to indicate the error. ERRORS No errors are defined. The following sections are informative. ``` Looking at [the glibc's sources](https://github.com/bminor/glibc/blob/b92a49359f33a461db080a33940d73f47c756126/posix/uname.c#L29) we can see that it indeed could fail if the internal `gethostname` call fails for some reason. This code also assumes that every field of `utsname` is going to be initialized by the call to `uname`, which apparently is also not true. Even though the interface doesn't expose this field so it's not a problem in practice (although it might be UB since we do call `assume_init` on the whole struct) [the `utsname` does have a `domainname` field](https://docs.rs/libc/0.2.119/libc/struct.utsname.html) which glibc doesn't initialize. The code also assumes that every field is a valid UTF-8 string, which is also technically not guaranteed. The code also assumes that every field will be null terminated, which might not be true if any of the strings are too long (since glibc uses `strncpy` which will *not* null-terminate the string if it ends up running out of space). This PR should fix all of these problems. This is a breaking change. Co-authored-by: Jan Bujak <[email protected]>
2 parents d2bc189 + 4ae4cfd commit 4ccc6c6

File tree

4 files changed

+50
-44
lines changed

4 files changed

+50
-44
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
8080
- Deprecated `IpAddr`, `Ipv4Addr`, and `Ipv6Addr` in favor of their equivalents
8181
from the standard library.
8282
(#[1685](https://github.com/nix-rust/nix/pull/1685))
83+
- `uname` now returns a `Result` instead of blindly assuming the call never fails.
84+
- Getters on the `UtsName` struct now return a `&OsStr` instead of `&str`.
8385

8486
### Fixed
8587

src/features.rs

+12-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ pub use self::os::*;
33

44
#[cfg(any(target_os = "linux", target_os = "android"))]
55
mod os {
6+
use std::os::unix::ffi::OsStrExt;
67
use crate::sys::utsname::uname;
8+
use crate::Result;
79

810
// Features:
911
// * atomic cloexec on socket: 2.6.27
@@ -22,15 +24,15 @@ mod os {
2224
*dst += (b - b'0') as usize;
2325
}
2426

25-
fn parse_kernel_version() -> usize {
26-
let u = uname();
27+
fn parse_kernel_version() -> Result<usize> {
28+
let u = uname()?;
2729

2830
let mut curr: usize = 0;
2931
let mut major: usize = 0;
3032
let mut minor: usize = 0;
3133
let mut patch: usize = 0;
3234

33-
for b in u.release().bytes() {
35+
for &b in u.release().as_bytes() {
3436
if curr >= 3 {
3537
break;
3638
}
@@ -50,7 +52,7 @@ mod os {
5052
}
5153
}
5254

53-
if major >= 3 {
55+
Ok(if major >= 3 {
5456
VERS_3
5557
} else if major >= 2 {
5658
if minor >= 7 {
@@ -68,29 +70,29 @@ mod os {
6870
}
6971
} else {
7072
VERS_UNKNOWN
71-
}
73+
})
7274
}
7375

74-
fn kernel_version() -> usize {
76+
fn kernel_version() -> Result<usize> {
7577
static mut KERNEL_VERS: usize = 0;
7678

7779
unsafe {
7880
if KERNEL_VERS == 0 {
79-
KERNEL_VERS = parse_kernel_version();
81+
KERNEL_VERS = parse_kernel_version()?;
8082
}
8183

82-
KERNEL_VERS
84+
Ok(KERNEL_VERS)
8385
}
8486
}
8587

8688
/// Check if the OS supports atomic close-on-exec for sockets
8789
pub fn socket_atomic_cloexec() -> bool {
88-
kernel_version() >= VERS_2_6_27
90+
kernel_version().map(|version| version >= VERS_2_6_27).unwrap_or(false)
8991
}
9092

9193
#[test]
9294
pub fn test_parsing_kernel_version() {
93-
assert!(kernel_version() > 0);
95+
assert!(kernel_version().unwrap() > 0);
9496
}
9597
}
9698

src/sys/utsname.rs

+29-27
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,77 @@
11
//! Get system identification
22
use std::mem;
3-
use libc::{self, c_char};
4-
use std::ffi::CStr;
5-
use std::str::from_utf8_unchecked;
3+
use std::os::unix::ffi::OsStrExt;
4+
use std::ffi::OsStr;
5+
use libc::c_char;
6+
use crate::{Errno, Result};
67

78
/// Describes the running system. Return type of [`uname`].
89
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
910
#[repr(transparent)]
1011
pub struct UtsName(libc::utsname);
1112

1213
impl UtsName {
13-
/// Name of the operating system implementation
14-
pub fn sysname(&self) -> &str {
15-
to_str(&(&self.0.sysname as *const c_char ) as *const *const c_char)
14+
/// Name of the operating system implementation.
15+
pub fn sysname(&self) -> &OsStr {
16+
cast_and_trim(&self.0.sysname)
1617
}
1718

1819
/// Network name of this machine.
19-
pub fn nodename(&self) -> &str {
20-
to_str(&(&self.0.nodename as *const c_char ) as *const *const c_char)
20+
pub fn nodename(&self) -> &OsStr {
21+
cast_and_trim(&self.0.nodename)
2122
}
2223

2324
/// Release level of the operating system.
24-
pub fn release(&self) -> &str {
25-
to_str(&(&self.0.release as *const c_char ) as *const *const c_char)
25+
pub fn release(&self) -> &OsStr {
26+
cast_and_trim(&self.0.release)
2627
}
2728

2829
/// Version level of the operating system.
29-
pub fn version(&self) -> &str {
30-
to_str(&(&self.0.version as *const c_char ) as *const *const c_char)
30+
pub fn version(&self) -> &OsStr {
31+
cast_and_trim(&self.0.version)
3132
}
3233

3334
/// Machine hardware platform.
34-
pub fn machine(&self) -> &str {
35-
to_str(&(&self.0.machine as *const c_char ) as *const *const c_char)
35+
pub fn machine(&self) -> &OsStr {
36+
cast_and_trim(&self.0.machine)
3637
}
3738
}
3839

3940
/// Get system identification
40-
pub fn uname() -> UtsName {
41+
pub fn uname() -> Result<UtsName> {
4142
unsafe {
42-
let mut ret = mem::MaybeUninit::uninit();
43-
libc::uname(ret.as_mut_ptr());
44-
UtsName(ret.assume_init())
43+
let mut ret = mem::MaybeUninit::zeroed();
44+
Errno::result(libc::uname(ret.as_mut_ptr()))?;
45+
Ok(UtsName(ret.assume_init()))
4546
}
4647
}
4748

48-
#[inline]
49-
fn to_str<'a>(s: *const *const c_char) -> &'a str {
50-
unsafe {
51-
let res = CStr::from_ptr(*s).to_bytes();
52-
from_utf8_unchecked(res)
53-
}
49+
fn cast_and_trim(slice: &[c_char]) -> &OsStr {
50+
let length = slice.iter().position(|&byte| byte == 0).unwrap_or(slice.len());
51+
let bytes = unsafe {
52+
std::slice::from_raw_parts(slice.as_ptr().cast(), length)
53+
};
54+
55+
OsStr::from_bytes(bytes)
5456
}
5557

5658
#[cfg(test)]
5759
mod test {
5860
#[cfg(target_os = "linux")]
5961
#[test]
6062
pub fn test_uname_linux() {
61-
assert_eq!(super::uname().sysname(), "Linux");
63+
assert_eq!(super::uname().unwrap().sysname(), "Linux");
6264
}
6365

6466
#[cfg(any(target_os = "macos", target_os = "ios"))]
6567
#[test]
6668
pub fn test_uname_darwin() {
67-
assert_eq!(super::uname().sysname(), "Darwin");
69+
assert_eq!(super::uname().unwrap().sysname(), "Darwin");
6870
}
6971

7072
#[cfg(target_os = "freebsd")]
7173
#[test]
7274
pub fn test_uname_freebsd() {
73-
assert_eq!(super::uname().sysname(), "FreeBSD");
75+
assert_eq!(super::uname().unwrap().sysname(), "FreeBSD");
7476
}
7577
}

test/common/mod.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,15 @@ cfg_if! {
111111
let version_requirement = VersionReq::parse($version_requirement)
112112
.expect("Bad match_version provided");
113113

114-
let uname = nix::sys::utsname::uname();
115-
println!("{}", uname.sysname());
116-
println!("{}", uname.nodename());
117-
println!("{}", uname.release());
118-
println!("{}", uname.version());
119-
println!("{}", uname.machine());
114+
let uname = nix::sys::utsname::uname().unwrap();
115+
println!("{}", uname.sysname().to_str().unwrap());
116+
println!("{}", uname.nodename().to_str().unwrap());
117+
println!("{}", uname.release().to_str().unwrap());
118+
println!("{}", uname.version().to_str().unwrap());
119+
println!("{}", uname.machine().to_str().unwrap());
120120

121121
// Fix stuff that the semver parser can't handle
122-
let fixed_release = &uname.release().to_string()
122+
let fixed_release = &uname.release().to_str().unwrap().to_string()
123123
// Fedora 33 reports version as 4.18.el8_2.x86_64 or
124124
// 5.18.200-fc33.x86_64. Remove the underscore.
125125
.replace("_", "-")

0 commit comments

Comments
 (0)