ptp-helper: Add some tests for functionality and memory safety of unsafe code

These tests are mostly for ensuring that the calls to system APIs are
done correctly and that there are no memory bugs (that would be caught
by valgrind) in the unsafe code.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4458>
This commit is contained in:
Sebastian Dröge 2023-04-20 10:32:43 +03:00 committed by GStreamer Marge Bot
parent fe4f034c8a
commit 0219b6f6fa
6 changed files with 728 additions and 213 deletions

View file

@ -224,6 +224,9 @@ pub mod unix {
pub fn freeifaddrs(ifa: *mut ifaddrs);
pub fn setpriority(which: c_int, who: c_int, prio: c_int) -> c_int;
#[cfg(test)]
pub fn pipe(pipefd: *mut i32) -> i32;
}
#[cfg(any(target_os = "linux", target_os = "solaris", target_os = "illumos"))]
@ -606,6 +609,14 @@ pub mod windows {
pub fn SetThreadPriority(pthread: HANDLE, npriority: i32) -> i32;
pub fn GetCurrentThread() -> HANDLE;
#[cfg(test)]
pub fn CreatePipe(
hreadpipe: *mut HANDLE,
hwritepipe: *mut HANDLE,
lppipeattributes: *mut c_void,
nsize: u32,
) -> i32;
}
pub const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002;

View file

@ -8,8 +8,23 @@
//
// SPDX-License-Identifier: MPL-2.0
/// Result of polling the inputs of the `Poll`.
///
/// Any input that has data available for reading will be set to `true`, potentially multiple
/// at once.
///
/// Note that reading from the sockets is non-blocking but reading from stdin is blocking so
/// special care has to be taken to only read as much as is available.
pub struct PollResult {
pub event_socket: bool,
pub general_socket: bool,
pub stdin: bool,
}
#[cfg(unix)]
mod imp {
use super::PollResult;
use std::{
io::{self, Read, Write},
net::UdpSocket,
@ -28,17 +43,81 @@ mod imp {
stdout: Stdout,
}
/// Result of polling the inputs of the `Poll`.
///
/// Any input that has data available for reading will be set to `true`, potentially multiple
/// at once.
///
/// Note that reading from the sockets is non-blocking but reading from stdin is blocking so
/// special care has to be taken to only read as much as is available.
pub struct PollResult {
pub event_socket: bool,
pub general_socket: bool,
pub stdin: bool,
#[cfg(test)]
/// A file descriptor pair representing a pipe for testing purposes.
pub struct Pipe {
pub read: i32,
pub write: i32,
}
#[cfg(test)]
impl Pipe {
fn new() -> io::Result<Self> {
// SAFETY: Requires two integers to be passed in and creates the read
// and write end of a pipe.
unsafe {
let mut fds = std::mem::MaybeUninit::<[i32; 2]>::uninit();
let res = pipe(fds.as_mut_ptr() as *mut i32);
if res == 0 {
let fds = fds.assume_init();
Ok(Pipe {
read: fds[0],
write: fds[1],
})
} else {
Err(io::Error::last_os_error())
}
}
}
}
#[cfg(test)]
impl Drop for Pipe {
fn drop(&mut self) {
// SAFETY: Only ever created with valid fds
unsafe {
close(self.read);
close(self.write);
}
}
}
#[cfg(test)]
impl Read for Pipe {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
// SAFETY: read() requires a valid fd and a mutable buffer with the given size.
// The fd is valid by construction as is the buffer.
//
// read() will return the number of bytes read or a negative value on errors.
let res = unsafe { read(self.read, buf.as_mut_ptr(), buf.len()) };
if res < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(res as usize)
}
}
}
#[cfg(test)]
impl Write for Pipe {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
// SAFETY: write() requires a valid fd and a mutable buffer with the given size.
// The fd is valid by construction as is the buffer.
//
// write() will return the number of bytes written or a negative value on errors.
let res = unsafe { write(self.write, buf.as_ptr(), buf.len()) };
if res == -1 {
Err(std::io::Error::last_os_error())
} else {
Ok(res as usize)
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Poll {
@ -65,6 +144,29 @@ mod imp {
})
}
#[cfg(test)]
/// Create a new `Poll` instance for testing purposes.
///
/// The returned `Pipe`s are for stdin and stdout.
pub fn new_test(
event_socket: UdpSocket,
general_socket: UdpSocket,
) -> Result<(Self, Pipe, Pipe), Error> {
let stdin = Pipe::new().unwrap();
let stdout = Pipe::new().unwrap();
Ok((
Self {
event_socket,
general_socket,
stdin: Stdin(stdin.read),
stdout: Stdout(stdout.write),
},
stdin,
stdout,
))
}
/// Mutable reference to the event socket.
pub fn event_socket(&mut self) -> &mut UdpSocket {
&mut self.event_socket
@ -210,6 +312,8 @@ mod imp {
#[cfg(windows)]
mod imp {
use super::PollResult;
use std::{
cmp,
io::{self, Read, Write},
@ -270,25 +374,111 @@ mod imp {
}
}
/// Result of polling the inputs of the `Poll`.
///
/// Any input that has data available for reading will be set to `true`, potentially multiple
/// at once.
///
/// Note that reading from the sockets is non-blocking but reading from stdin is blocking so
/// special care has to be taken to only read as much as is available.
pub struct PollResult {
pub event_socket: bool,
pub general_socket: bool,
pub stdin: bool,
#[cfg(test)]
pub struct Pipe {
read: HANDLE,
write: HANDLE,
}
#[cfg(test)]
impl Drop for Pipe {
fn drop(&mut self) {
// SAFETY: Both handles are by construction valid up there.
unsafe {
CloseHandle(self.read);
CloseHandle(self.write);
}
}
}
#[cfg(test)]
impl Pipe {
fn new() -> io::Result<Self> {
// SAFETY: On success returns a non-zero integer and stores read/write handles in the
// two out pointers, which will have to be closed again later.
unsafe {
let mut readpipe = mem::MaybeUninit::uninit();
let mut writepipe = mem::MaybeUninit::uninit();
let res = CreatePipe(
readpipe.as_mut_ptr(),
writepipe.as_mut_ptr(),
ptr::null_mut(),
0,
);
if res != 0 {
Ok(Self {
read: readpipe.assume_init(),
write: writepipe.assume_init(),
})
} else {
Err(io::Error::last_os_error())
}
}
}
}
#[cfg(test)]
impl Read for Pipe {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
// SAFETY: Reads the given number of bytes into the buffer from the stdin handle.
unsafe {
let mut lpnumberofbytesread = mem::MaybeUninit::uninit();
let res = ReadFile(
self.read,
buf.as_mut_ptr(),
cmp::min(buf.len() as u32, u32::MAX) as u32,
lpnumberofbytesread.as_mut_ptr(),
ptr::null_mut(),
);
if res == 0 {
Err(io::Error::last_os_error())
} else {
Ok(lpnumberofbytesread.assume_init() as usize)
}
}
}
}
#[cfg(test)]
impl Write for Pipe {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
// SAFETY: Writes the given number of bytes to stdout or at most u32::MAX. On error
// zero is returned, otherwise the number of bytes written is set accordingly and
// returned.
unsafe {
let mut lpnumberofbyteswritten = mem::MaybeUninit::uninit();
let res = WriteFile(
self.write,
buf.as_ptr(),
cmp::min(buf.len() as u32, u32::MAX) as u32,
lpnumberofbyteswritten.as_mut_ptr(),
ptr::null_mut(),
);
if res == 0 {
Err(io::Error::last_os_error())
} else {
Ok(lpnumberofbyteswritten.assume_init() as usize)
}
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Poll {
/// Create a new `Poll` instance from the two sockets.
pub fn new(event_socket: UdpSocket, general_socket: UdpSocket) -> Result<Self, Error> {
let stdin = Stdin::acquire().context("Failure acquiring stdin handle")?;
let stdout = Stdout::acquire().context("Failed acquiring stdout handle")?;
/// Internal constructor.
pub fn new_internal(
event_socket: UdpSocket,
general_socket: UdpSocket,
stdin: Stdin,
stdout: Stdout,
) -> Result<Self, Error> {
// Create event objects for the readability of the sockets.
let event_socket_event = EventHandle::new().context("Failed creating WSA event")?;
let general_socket_event = EventHandle::new().context("Failed creating WSA event")?;
@ -329,6 +519,35 @@ mod imp {
})
}
/// Create a new `Poll` instance from the two sockets.
pub fn new(event_socket: UdpSocket, general_socket: UdpSocket) -> Result<Self, Error> {
let stdin = Stdin::acquire().context("Failure acquiring stdin handle")?;
let stdout = Stdout::acquire().context("Failed acquiring stdout handle")?;
Self::new_internal(event_socket, general_socket, stdin, stdout)
}
#[cfg(test)]
/// Create a new `Poll` instance for testing purposes.
///
/// The returned `Pipe`s are for stdin and stdout.
pub fn new_test(
event_socket: UdpSocket,
general_socket: UdpSocket,
) -> Result<(Self, Pipe, Pipe), Error> {
let stdin_pipe = Pipe::new().unwrap();
let stdout_pipe = Pipe::new().unwrap();
let stdin =
Stdin::from_handle(stdin_pipe.read).context("Failure acquiring stdin handle")?;
let stdout =
Stdout::from_handle(stdout_pipe.write).context("Failed acquiring stdout handle")?;
let poll = Self::new_internal(event_socket, general_socket, stdin, stdout)?;
Ok((poll, stdin_pipe, stdout_pipe))
}
/// Mutable reference to the event socket.
pub fn event_socket(&mut self) -> &mut UdpSocket {
&mut self.event_socket
@ -516,6 +735,11 @@ mod imp {
handle
};
Self::from_handle(handle)
}
fn from_handle(handle: HANDLE) -> Result<Self, Error> {
// SAFETY: GetFileType() is safe to call on any valid handle.
let type_ = unsafe { GetFileType(handle) };
@ -717,6 +941,11 @@ mod imp {
handle
};
Self::from_handle(handle)
}
fn from_handle(handle: HANDLE) -> Result<Self, Error> {
// SAFETY: GetFileType() is safe to call on any valid handle.
let type_ = unsafe { GetFileType(handle) };
@ -766,4 +995,69 @@ mod imp {
}
}
pub use self::imp::{Poll, PollResult, Stdin, Stdout};
pub use self::imp::{Poll, Stdin, Stdout};
#[cfg(test)]
mod test {
#[test]
fn test_poll() {
use std::io::prelude::*;
let event_socket = std::net::UdpSocket::bind(std::net::SocketAddr::from((
std::net::Ipv4Addr::LOCALHOST,
0,
)))
.unwrap();
let event_port = event_socket.local_addr().unwrap().port();
let general_socket = std::net::UdpSocket::bind(std::net::SocketAddr::from((
std::net::Ipv4Addr::LOCALHOST,
0,
)))
.unwrap();
let general_port = general_socket.local_addr().unwrap().port();
let send_socket = std::net::UdpSocket::bind(std::net::SocketAddr::from((
std::net::Ipv4Addr::LOCALHOST,
0,
)))
.unwrap();
let (mut poll, mut stdin, _stdout) =
super::Poll::new_test(event_socket, general_socket).unwrap();
let mut buf = [0u8; 4];
for _ in 0..10 {
send_socket
.send_to(&[1, 2, 3, 4], (std::net::Ipv4Addr::LOCALHOST, event_port))
.unwrap();
let res = poll.poll().unwrap();
assert!(res.event_socket);
assert!(!res.general_socket);
assert!(!res.stdin);
assert_eq!(poll.event_socket().recv(&mut buf).unwrap(), 4);
assert_eq!(buf, [1, 2, 3, 4]);
send_socket
.send_to(&[1, 2, 3, 4], (std::net::Ipv4Addr::LOCALHOST, general_port))
.unwrap();
let res = poll.poll().unwrap();
assert!(!res.event_socket);
assert!(res.general_socket);
assert!(!res.stdin);
assert_eq!(poll.general_socket().recv(&mut buf).unwrap(), 4);
assert_eq!(buf, [1, 2, 3, 4]);
stdin.write_all(&[1, 2, 3, 4]).unwrap();
let res = poll.poll().unwrap();
assert!(!res.event_socket);
assert!(!res.general_socket);
assert!(res.stdin);
poll.stdin().read_exact(&mut buf).unwrap();
assert_eq!(buf, [1, 2, 3, 4]);
}
drop(poll);
}
}

View file

@ -98,7 +98,6 @@ conf_lib_rs = configure_file(input : 'conf_lib.rs.in',
conf = static_library('gst_ptp_helper_conf', conf_lib_rs,
override_options : ['rust_std=2018'],
rust_args : ['-Cpanic=abort'],
rust_crate_type : 'rlib')
exe = executable('gst-ptp-helper', 'main.rs',
@ -109,6 +108,16 @@ exe = executable('gst-ptp-helper', 'main.rs',
install_dir : helpers_install_dir,
install : true)
exe_test = executable('gst-ptp-helper-test', 'main.rs',
override_options : ['rust_std=2018'],
rust_args : ['--test', rust_args],
dependencies : [cap_dep],
link_with : conf,
install_dir : helpers_install_dir,
install : true)
test('gst-ptp-helper-test', exe_test, protocol : 'rust')
if host_system != 'windows'
meson.add_install_script('ptp_helper_post_install.sh',
helpers_install_dir, with_ptp_helper_permissions,

View file

@ -665,3 +665,34 @@ mod imp {
}
pub use imp::*;
#[cfg(test)]
mod test {
#[test]
fn test_query_interfaces() {
let ifaces = super::query_interfaces().unwrap();
for iface in ifaces {
assert!(!iface.name.is_empty());
assert_ne!(iface.index, 0);
assert!(!iface.ip_addr.is_unspecified());
}
}
#[test]
fn test_join_multicast() {
let ifaces = super::query_interfaces().unwrap();
let iface = if ifaces.is_empty() {
return;
} else {
&ifaces[0]
};
let socket = std::net::UdpSocket::bind(std::net::SocketAddr::from((
std::net::Ipv4Addr::UNSPECIFIED,
0,
)))
.unwrap();
super::set_reuse(&socket);
super::join_multicast_v4(&socket, &std::net::Ipv4Addr::new(224, 0, 0, 1), iface).unwrap();
}
}

View file

@ -10,125 +10,184 @@
use crate::error::Error;
/// Drop all additional permissions / capabilities the current process might have as they're not
/// needed anymore.
///
/// This does nothing if no such mechanism is implemented / selected for the target platform.
pub fn drop() -> Result<(), Error> {
#[cfg(ptp_helper_permissions = "setcap")]
{
// Drop all current capabilities of the process.
#[cfg(ptp_helper_permissions = "setcap")]
mod setcap {
use super::*;
use std::io;
use crate::{error::Context, ffi::unix::setcaps::*};
use std::io;
use crate::{bail, ffi::unix::setcaps::*};
struct Cap(cap_t);
struct Cap(cap_t);
impl Drop for Cap {
fn drop(&mut self) {
// SAFETY: The capabilities are valid by construction and are only dropped
// once here.
unsafe {
let _ = cap_free(self.0);
}
}
}
// SAFETY: There are 3 steps here
// 1. Get the current capabilities of the process. This
// returns NULL on error or otherwise newly allocated capabilities that have to be
// freed again in the end. For that purpose we wrap them in the Cap struct.
//
// 2. Clearing all current capabilities. This requires a valid capabilities pointer,
// which we have at this point by construction.
//
// 3. Setting the current process's capabilities, which is only affecting the current
// thread unfortunately. At this point, no other threads were started yet so this is
// not a problem. Also the capabilities pointer is still valid by construction.
//
// On every return path, the capabilities are going to be freed.
unsafe {
let c = cap_get_proc();
if c.is_null() {
bail!(
source: io::Error::last_os_error(),
"Failed to get current process capabilities"
);
}
let c = Cap(c);
if cap_clear(c.0) != 0 {
bail!(
source: io::Error::last_os_error(),
"Failed to clear capabilities"
);
}
if cap_set_proc(c.0) != 0 {
bail!(
source: io::Error::last_os_error(),
"Failed to set current process capabilities"
);
impl Drop for Cap {
fn drop(&mut self) {
// SAFETY: The capabilities are valid by construction and are only dropped
// once here.
unsafe {
let _ = cap_free(self.0);
}
}
}
#[cfg(ptp_helper_permissions = "setuid-root")]
{
// Drop the process's UID/GID from root to the configured user/group or the user "nobody".
use std::{ffi::CString, io};
use crate::{bail, error::Context, ffi::unix::setuid_root::*};
fn get_uid_gid_for_user(name: &str) -> io::Result<(uid_t, gid_t)> {
let name_cstr = CString::new(name).unwrap();
loop {
// SAFETY: getpwnam() requires a NUL-terminated user name string and
// returns either the user information in static storage, or NULL on error.
// In case of EINTR, getting the user information can be retried.
//
// The user information is stored in static storage so might change if something
// else calls related functions. As this is the only thread up to this point and we
// just extract two integers from it there is no such possibility.
unsafe {
let pw = getpwnam(name_cstr.as_ptr());
if pw.is_null() {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(err);
}
return Ok(((*pw).pw_uid, (*pw).pw_gid));
impl Cap {
/// Get the current process' capabilities.
fn get_proc() -> io::Result<Self> {
// SAFETY: Get the current capabilities of the process. This returns NULL on error or
// otherwise newly allocated capabilities that have to be freed again in the end. For
// that purpose we wrap them in the Cap struct.
unsafe {
let c = cap_get_proc();
if c.is_null() {
return Err(io::Error::last_os_error());
}
Ok(Cap(c))
}
}
fn get_gid_for_group(name: &str) -> io::Result<gid_t> {
let name_cstr = CString::new(name).unwrap();
loop {
// SAFETY: getgrnam() requires a NUL-terminated group name string and
// returns either the group information in static storage, or NULL on error.
// In case of EINTR, getting the group information can be retried.
//
// The user information is stored in static storage so might change if something
// else calls related functions. As this is the only thread up to this point and we
// just extract two integers from it there is no such possibility.
unsafe {
let grp = getgrnam(name_cstr.as_ptr());
if grp.is_null() {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(err);
/// Clear all capabilities.
fn clear(&mut self) -> io::Result<()> {
// SAFETY: Clearing all current capabilities. This requires a valid capabilities
// pointer, which we have at this point by construction.
unsafe {
if cap_clear(self.0) != 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
/// Set current process' capabilities.
fn set_proc(&self) -> io::Result<()> {
// SAFETY: Setting the current process's capabilities, which is only affecting the
// current thread unfortunately. At this point, no other threads were started yet so
// this is not a problem. Also the capabilities pointer is still valid by construction.
unsafe {
if cap_set_proc(self.0) != 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
}
/// Drop all current capabilities of the process.
pub fn drop() -> Result<(), Error> {
let mut c = Cap::get_proc().context("Failed to get current process capabilities")?;
c.clear().context("Failed to clear capabilities")?;
c.set_proc()
.context("Failed to set current process capabilities")?;
Ok(())
}
#[cfg(test)]
mod test {
#[test]
fn test_get_set_same_and_clear_cap() {
let mut c = super::Cap::get_proc().unwrap();
// Setting the same capabilities should always succeed
c.set_proc().unwrap();
c.clear().unwrap();
}
}
}
#[cfg(ptp_helper_permissions = "setuid-root")]
mod setuid_root {
use super::*;
use crate::{bail, error::Context, ffi::unix::setuid_root::*};
use std::{ffi::CString, io};
/// Retrieve UID and GID for the given username.
fn get_uid_gid_for_user(name: &str) -> io::Result<(uid_t, gid_t)> {
let name_cstr = CString::new(name).unwrap();
loop {
// SAFETY: getpwnam() requires a NUL-terminated user name string and
// returns either the user information in static storage, or NULL on error.
// In case of EINTR, getting the user information can be retried.
//
// The user information is stored in static storage so might change if something
// else calls related functions. As this is the only thread up to this point and we
// just extract two integers from it there is no such possibility.
unsafe {
let pw = getpwnam(name_cstr.as_ptr());
if pw.is_null() {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
if err.raw_os_error() == Some(0) {
return Err(io::Error::from(io::ErrorKind::NotFound));
}
return Ok((*grp).gr_gid);
return Err(err);
}
return Ok(((*pw).pw_uid, (*pw).pw_gid));
}
}
}
/// Retrieve GID for the given group name.
fn get_gid_for_group(name: &str) -> io::Result<gid_t> {
let name_cstr = CString::new(name).unwrap();
loop {
// SAFETY: getgrnam() requires a NUL-terminated group name string and
// returns either the group information in static storage, or NULL on error.
// In case of EINTR, getting the group information can be retried.
//
// The user information is stored in static storage so might change if something
// else calls related functions. As this is the only thread up to this point and we
// just extract two integers from it there is no such possibility.
unsafe {
let grp = getgrnam(name_cstr.as_ptr());
if grp.is_null() {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
if err.raw_os_error() == Some(0) {
return Err(io::Error::from(io::ErrorKind::NotFound));
}
return Err(err);
}
return Ok((*grp).gr_gid);
}
}
}
#[cfg(test)]
mod test {
#[test]
fn test_get_uid_gid_for_user() {
match super::get_uid_gid_for_user("root") {
Ok(_) => (),
Err(err) if err.kind() != std::io::ErrorKind::NotFound => {
panic!("{}", err);
}
_ => (),
}
}
#[test]
fn test_get_gid_for_group() {
match super::get_gid_for_group("root") {
Ok(_) => (),
Err(err) if err.kind() != std::io::ErrorKind::NotFound => {
panic!("{}", err);
}
_ => (),
}
}
}
/// Drop the process's UID/GID from root to the configured user/group or the user "nobody".
pub fn drop() -> Result<(), Error> {
let username = gst_ptp_helper_conf::PTP_HELPER_SETUID_USER.unwrap_or("nobody");
let (uid, gid) = get_uid_gid_for_user(username)
@ -163,6 +222,23 @@ pub fn drop() -> Result<(), Error> {
bail!(source: err, "Failed to set user id {} for process", uid);
}
}
Ok(())
}
}
/// Drop all additional permissions / capabilities the current process might have as they're not
/// needed anymore.
///
/// This does nothing if no such mechanism is implemented / selected for the target platform.
pub fn drop() -> Result<(), Error> {
#[cfg(ptp_helper_permissions = "setcap")]
{
setcap::drop()?;
}
#[cfg(ptp_helper_permissions = "setuid-root")]
{
setuid_root::drop()?;
}
Ok(())

View file

@ -8,70 +8,31 @@
//
// SPDX-License-Identifier: MPL-2.0
/// Returns a random'ish 64 bit value.
pub fn rand() -> [u8; 8] {
#[cfg(unix)]
{
// Try getrandom syscall or otherwise first on Linux
#[cfg(target_os = "linux")]
{
use std::io::Read;
#[cfg(unix)]
mod unix {
use std::io;
use crate::ffi::unix::linux::*;
#[cfg(target_os = "linux")]
/// Try using the getrandom syscall on Linux.
pub fn getrandom() -> io::Result<[u8; 8]> {
use std::io::Read;
// Depends on us knowing the syscall number
if SYS_getrandom != 0 {
struct GetRandom;
use crate::ffi::unix::linux::*;
impl Read for GetRandom {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
// SAFETY: `getrandom` syscall fills up to the requested amount of bytes of
// the provided memory and returns the number of bytes or a negative value
// on errors.
unsafe {
let res = syscall(SYS_getrandom, buf.as_mut_ptr(), buf.len(), 0u32);
if res < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(res as usize)
}
}
}
}
let mut r = [0u8; 8];
if GetRandom.read_exact(&mut r).is_ok() {
return r;
}
}
// Depends on us knowing the syscall number
if SYS_getrandom == 0 {
return Err(io::Error::from(io::ErrorKind::Unsupported));
}
// Otherwise try /dev/urandom
{
use crate::ffi::unix::*;
use std::{io::Read, os::raw::c_int};
struct GetRandom;
struct Fd(c_int);
impl Drop for Fd {
fn drop(&mut self) {
// SAFETY: The fd is valid by construction below and closed by this at
// most once.
unsafe {
// Return value is intentionally ignored as there's nothing that
// can be done on errors anyway.
let _ = close(self.0);
}
}
}
impl Read for Fd {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
// SAFETY: read() requires a valid fd and a mutable buffer with the given size.
// The fd is valid by construction as is the buffer.
//
// read() will return the number of bytes read or a negative value on errors.
let res = unsafe { read(self.0, buf.as_mut_ptr(), buf.len()) };
impl Read for GetRandom {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
// SAFETY: `getrandom` syscall fills up to the requested amount of bytes of
// the provided memory and returns the number of bytes or a negative value
// on errors.
unsafe {
let res = syscall(SYS_getrandom, buf.as_mut_ptr(), buf.len(), 0u32);
if res < 0 {
Err(std::io::Error::last_os_error())
} else {
@ -79,37 +40,108 @@ pub fn rand() -> [u8; 8] {
}
}
}
}
let fd = loop {
// SAFETY: open() requires a NUL-terminated file path and will
// return an integer in any case. A negative value is an invalid fd
// and signals an error. On EINTR, opening can be retried.
let fd = unsafe { open(b"/dev/urandom\0".as_ptr(), O_RDONLY) };
if fd < 0 {
let err = std::io::Error::last_os_error();
if err.kind() == std::io::ErrorKind::Interrupted {
continue;
}
let mut r = [0u8; 8];
GetRandom.read_exact(&mut r)?;
break None;
}
Ok(r)
}
break Some(Fd(fd));
};
/// Try reading random numbers from /dev/urandom.
pub fn dev_urandom() -> io::Result<[u8; 8]> {
use crate::ffi::unix::*;
use std::{io::Read, os::raw::c_int};
if let Some(mut fd) = fd {
let mut r = [0u8; 8];
struct Fd(c_int);
if fd.read_exact(&mut r).is_ok() {
return r;
impl Drop for Fd {
fn drop(&mut self) {
// SAFETY: The fd is valid by construction below and closed by this at
// most once.
unsafe {
// Return value is intentionally ignored as there's nothing that
// can be done on errors anyway.
let _ = close(self.0);
}
}
}
impl Read for Fd {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
// SAFETY: read() requires a valid fd and a mutable buffer with the given size.
// The fd is valid by construction as is the buffer.
//
// read() will return the number of bytes read or a negative value on errors.
let res = unsafe { read(self.0, buf.as_mut_ptr(), buf.len()) };
if res < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(res as usize)
}
}
}
let mut fd = loop {
// SAFETY: open() requires a NUL-terminated file path and will
// return an integer in any case. A negative value is an invalid fd
// and signals an error. On EINTR, opening can be retried.
let fd = unsafe { open(b"/dev/urandom\0".as_ptr(), O_RDONLY) };
if fd < 0 {
let err = std::io::Error::last_os_error();
if err.kind() == std::io::ErrorKind::Interrupted {
continue;
}
return Err(err);
}
break Fd(fd);
};
let mut r = [0u8; 8];
fd.read_exact(&mut r)?;
Ok(r)
}
#[cfg(windows)]
{
// Try BCryptGenRandom(), which is available since Windows Vista
//
#[cfg(test)]
mod test {
#[test]
fn test_dev_urandom() {
match super::dev_urandom() {
Ok(n) => {
assert_ne!(n, [0u8; 8]);
}
Err(err) if err.kind() != std::io::ErrorKind::NotFound => {
panic!("{}", err);
}
_ => (),
}
}
#[cfg(target_os = "linux")]
#[test]
fn test_getrandom() {
match super::getrandom() {
Ok(n) => {
assert_ne!(n, [0u8; 8]);
}
Err(err) if err.kind() != std::io::ErrorKind::Unsupported => {
panic!("{}", err);
}
_ => (),
}
}
}
}
#[cfg(windows)]
mod windows {
use std::io;
/// Call BCryptGenRandom(), which is available since Windows Vista.
pub fn bcrypt_gen_random() -> io::Result<[u8; 8]> {
// SAFETY: BCryptGenRandom() fills the provided memory with the requested number of bytes
// and returns 0 on success. In that case, all memory was written and is initialized now.
unsafe {
@ -125,12 +157,25 @@ pub fn rand() -> [u8; 8] {
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
);
if res == 0 {
return r.assume_init();
Ok(r.assume_init())
} else {
Err(io::Error::from_raw_os_error(res as i32))
}
}
}
// As fallback use a combination of the process ID and the current system time
#[cfg(test)]
mod test {
#[test]
fn test_bcrypt_gen_random() {
let n = super::bcrypt_gen_random().unwrap();
assert_ne!(n, [0u8; 8]);
}
}
}
/// As fallback use a combination of the process ID and the current system time.
fn fallback_rand() -> [u8; 8] {
let now = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
@ -148,3 +193,52 @@ pub fn rand() -> [u8; 8] {
now[7] ^ now[8] ^ pid[3],
]
}
/// Returns a random'ish 64 bit value.
pub fn rand() -> [u8; 8] {
#[cfg(unix)]
{
// Try getrandom syscall or otherwise first on Linux
#[cfg(target_os = "linux")]
{
if let Ok(r) = unix::getrandom() {
return r;
}
}
if let Ok(r) = unix::dev_urandom() {
return r;
}
}
#[cfg(windows)]
{
if let Ok(r) = windows::bcrypt_gen_random() {
return r;
}
}
fallback_rand()
}
#[cfg(test)]
mod test {
// While not a very useful test for randomness, we're mostly interested here
// in whether the memory is initialized correctly and nothing crashes because
// of the usage of unsafe code above. If the memory was not initialized fully
// then this test would fail in e.g. valgrind.
//
// Technically, all zeroes could be returned as a valid random number but that's
// extremely unlikely and more likely a bug in the code above.
#[test]
fn test_rand() {
let n = super::rand();
assert_ne!(n, [0u8; 8]);
}
#[test]
fn test_fallback_rand() {
let n = super::fallback_rand();
assert_ne!(n, [0u8; 8]);
}
}