Sebastian Dröge d55492add4 ptp: Fix compilation with Rust 1.48
Use `ErrorKind::NotFound` instead of `ErrorKind::Unsupported` if the
`getrandom` syscall is not available. `Unsupported` was added in 1.53.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4647>
2023-05-19 12:47:28 +00:00

247 lines
7.2 KiB
Rust

// GStreamer
//
// Copyright (C) 2015-2023 Sebastian Dröge <sebastian@centricular.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
#[cfg(unix)]
mod unix {
use std::io;
#[cfg(target_os = "linux")]
/// Try using the getrandom syscall on Linux.
pub fn getrandom() -> io::Result<[u8; 8]> {
use std::io::Read;
use crate::ffi::unix::linux::*;
// Depends on us knowing the syscall number
if SYS_getrandom == 0 {
// FIXME: Use Unsupported once we can depend on 1.53
return Err(io::Error::from(io::ErrorKind::NotFound));
}
struct GetRandom;
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];
GetRandom.read_exact(&mut r)?;
Ok(r)
}
/// 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};
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()) };
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(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]);
}
// FIXME: Use Unsupported once we can depend on 1.53
Err(err) if err.kind() != std::io::ErrorKind::NotFound => {
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 {
use std::{mem, ptr};
use crate::ffi::windows::*;
let mut r = mem::MaybeUninit::<[u8; 8]>::uninit();
let res = BCryptGenRandom(
ptr::null_mut(),
r.as_mut_ptr() as *mut u8,
8,
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
);
if res == 0 {
Ok(r.assume_init())
} else {
Err(io::Error::from_raw_os_error(res as i32))
}
}
}
#[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()
.as_nanos()
.to_be_bytes();
let pid = std::process::id().to_be_bytes();
[
now[0] ^ now[15] ^ pid[0],
now[1] ^ now[14] ^ pid[1],
now[2] ^ now[13] ^ pid[2],
now[3] ^ now[12] ^ pid[3],
now[4] ^ now[11] ^ pid[0],
now[5] ^ now[10] ^ pid[1],
now[6] ^ now[9] ^ pid[2],
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]);
}
}