Skip to content

Commit 4ae4cfd

Browse files
committed
Make uname always safe
This fixes several issues with the current `uname` bindings: - Do not ignore `uname` errors; at least on glibc `uname` can fail, so now it returns a `Result` instead of assuming that the call will always succeed. - Do not assume `uname` will initialize every member of `utsname`; not every implementation initializes every field, so internally the struct is now zero-initialized. - Do not blindly assume strings returned by `uname` will always be valid UTF-8; `UtsName`'s accessors will now return `&OsStr`s instead of `&str`s.
1 parent 3ca28f6 commit 4ae4cfd

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)