Skip to content

Commit

Permalink
linux_android: use libc::getrandom (#508)
Browse files Browse the repository at this point in the history
Use of `libc::getrandom` will automatically give us optimizations like
vDSO (#503) and can help with testing of fallback logic (#289).

It was also requested in #285. In that discussion we decided against
using this approach, but in the light of the vDSO optimization it may be
worth to reconsider it.

In `linux_android_with_fallback` use of `libc::syscall` is replaced by
`dlsym`-based code similar to what we use in the `netbsd` backend.
  • Loading branch information
newpavlov authored Oct 16, 2024
1 parent f622215 commit 869a4f0
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 47 deletions.
2 changes: 0 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,8 @@ cfg_if! {
),
)
))] {
mod lazy;
mod util_libc;
mod use_file;
mod linux_android;
#[path = "linux_android_with_fallback.rs"] mod imp;
} else if #[cfg(any(target_os = "android", target_os = "linux"))] {
mod util_libc;
Expand Down
20 changes: 3 additions & 17 deletions src/linux_android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,7 @@ use core::mem::MaybeUninit;
compile_error!("`linux_getrandom` backend can be enabled only for Linux/Android targets!");

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
util_libc::sys_fill_exact(dest, getrandom_syscall)
}

pub fn getrandom_syscall(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
let res: libc::c_long = unsafe {
libc::syscall(
libc::SYS_getrandom,
buf.as_mut_ptr().cast::<core::ffi::c_void>(),
buf.len(),
0,
)
};

const _: () =
assert!(core::mem::size_of::<libc::c_long>() == core::mem::size_of::<libc::ssize_t>());
res.try_into()
.expect("c_long to ssize_t conversion is lossless")
util_libc::sys_fill_exact(dest, |buf| unsafe {
libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0)
})
}
99 changes: 71 additions & 28 deletions src/linux_android_with_fallback.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,80 @@
//! Implementation for Linux / Android with `/dev/urandom` fallback
use crate::{lazy::LazyBool, linux_android, use_file, util_libc::last_os_error, Error};
use core::mem::MaybeUninit;
use crate::{use_file, util_libc, Error};
use core::{
ffi::c_void,
mem::{self, MaybeUninit},
ptr::{self, NonNull},
sync::atomic::{AtomicPtr, Ordering},
};

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// getrandom(2) was introduced in Linux 3.17
static HAS_GETRANDOM: LazyBool = LazyBool::new();
if HAS_GETRANDOM.unsync_init(is_getrandom_available) {
linux_android::getrandom_inner(dest)
} else {
// prevent inlining of the fallback implementation
#[inline(never)]
fn inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
use_file::getrandom_inner(dest)
type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;

/// Sentinel value which indicates that `libc::getrandom` either not available,
/// or not supported by kernel.
const NOT_AVAILABLE: NonNull<c_void> = unsafe { NonNull::new_unchecked(usize::MAX as *mut c_void) };

static GETRANDOM_FN: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());

#[cold]
fn init() -> NonNull<c_void> {
static NAME: &[u8] = b"getrandom\0";
let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
let raw_ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) };
let res_ptr = match NonNull::new(raw_ptr) {
Some(fptr) => {
let getrandom_fn = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
let dangling_ptr = ptr::NonNull::dangling().as_ptr();
// Check that `getrandom` syscall is supported by kernel
let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) };
if cfg!(getrandom_test_linux_fallback) {
NOT_AVAILABLE
} else if res.is_negative() {
match util_libc::last_os_error().raw_os_error() {
Some(libc::ENOSYS) => NOT_AVAILABLE, // No kernel support
// The fallback on EPERM is intentionally not done on Android since this workaround
// seems to be needed only for specific Linux-based products that aren't based
// on Android. See https://github.com/rust-random/getrandom/issues/229.
#[cfg(target_os = "linux")]
Some(libc::EPERM) => NOT_AVAILABLE, // Blocked by seccomp
_ => fptr,
}
} else {
fptr
}
}
None => NOT_AVAILABLE,
};

inner(dest)
}
GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release);
res_ptr
}

fn is_getrandom_available() -> bool {
if cfg!(getrandom_test_linux_fallback) {
false
} else if linux_android::getrandom_syscall(&mut []) < 0 {
match last_os_error().raw_os_error() {
Some(libc::ENOSYS) => false, // No kernel support
// The fallback on EPERM is intentionally not done on Android since this workaround
// seems to be needed only for specific Linux-based products that aren't based
// on Android. See https://github.com/rust-random/getrandom/issues/229.
#[cfg(target_os = "linux")]
Some(libc::EPERM) => false, // Blocked by seccomp
_ => true,
}
// prevent inlining of the fallback implementation
#[inline(never)]
fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
use_file::getrandom_inner(dest)
}

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// Despite being only a single atomic variable, we still cannot always use
// Ordering::Relaxed, as we need to make sure a successful call to `init`
// is "ordered before" any data read through the returned pointer (which
// occurs when the function is called). Our implementation mirrors that of
// the one in libstd, meaning that the use of non-Relaxed operations is
// probably unnecessary.
let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire);
let fptr = match NonNull::new(raw_ptr) {
Some(p) => p,
None => init(),
};

if fptr == NOT_AVAILABLE {
use_file_fallback(dest)
} else {
true
// note: `transume` is currently the only way to convert pointer into function reference
let getrandom_fn = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
util_libc::sys_fill_exact(dest, |buf| unsafe {
getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0)
})
}
}

0 comments on commit 869a4f0

Please sign in to comment.