Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ name: Build

on:
push:
branches: master
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are not relevant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, they're in a separate commit with reasoning given (it's a bummer you guys insist on squashing on merge).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tamird I think this is a reasonable thing to do, but changing on which branches CI runs is tricky from a security perspective, and it deserves a dedicated PR.

pull_request:
branches: master
schedule:
- cron: "0 12 * * 1"

Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/nopanic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ name: No panic

on:
push:
branches: master
pull_request:
branches: master

permissions:
contents: read
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ name: Test

on:
push:
branches: master
pull_request:
branches: master
schedule:
- cron: "0 12 * * 1"

Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/workspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ name: Workspace

on:
push:
branches: master
pull_request:
branches: master

permissions:
contents: read
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ check-cfg = [
'cfg(getrandom_test_linux_fallback)',
'cfg(getrandom_test_linux_without_fallback)',
'cfg(getrandom_test_netbsd_fallback)',
'cfg(has_libc_getrandom)',
'cfg(target_os, values("cygwin"))', # TODO(MSRV 1.86): Remove this.
]

Expand Down
57 changes: 57 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,61 @@ fn main() {
if sanitizers.contains("memory") {
println!("cargo:rustc-cfg=getrandom_msan");
}

if cfg!(target_feature = "crt-static") {
match std::process::Command::new(std::env::var_os("RUSTC").unwrap())
.arg("--target")
.arg(std::env::var("TARGET").unwrap())
.arg("--out-dir")
.arg(std::env::var("OUT_DIR").unwrap())
.args(["--crate-type=bin", "-"])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.spawn()
{
Err(err) => {
println!("cargo:warning=failed to spawn compiler: {}", err);
}
Ok(mut child) => {
use std::io::{BufRead as _, Write as _};

let std::process::Child { stdin, stderr, .. } = &mut child;
let mut stdin = stdin.take().unwrap();
stdin
.write_all(
r#"
use std::ffi::{c_uint, c_void};
extern "C" {
fn getrandom(buf: *mut c_void, buflen: usize, flags: c_uint) -> isize;
}
fn main() -> std::io::Result<()> {
use std::convert::TryFrom as _;
let mut buf = [0; 1];
let _: usize = usize::try_from(unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) })
.map_err(|std::num::TryFromIntError { .. }| std::io::Error::last_os_error())?;
Ok(())
}
"#
.as_bytes(),
)
.unwrap();

std::mem::drop(stdin); // Send EOF.

// Trampoline stdout to cargo warnings.
let stderr = stderr.take().unwrap();
let stderr = std::io::BufReader::new(stderr);
for line in stderr.lines() {
let line = line.unwrap();
println!("cargo:warning={line}");
}

let status = child.wait().unwrap();
if status.code() == Some(0) {
println!("cargo:rustc-cfg=has_libc_getrandom");
}
}
}
}
}
140 changes: 70 additions & 70 deletions src/backends/linux_android_with_fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,53 @@ use crate::Error;
use core::{
ffi::c_void,
mem::{MaybeUninit, transmute},
ptr::NonNull,
sync::atomic::{AtomicPtr, Ordering},
ptr,
};
use use_file::util_libc;

pub use crate::util::{inner_u32, inner_u64};

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) };
#[path = "../lazy.rs"]
mod lazy;

static GETRANDOM_FN: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());
type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;

#[cold]
#[inline(never)]
fn init() -> NonNull<c_void> {
// Use static linking to `libc::getrandom` on MUSL targets and `dlsym` everywhere else
#[cfg(not(target_env = "musl"))]
let raw_ptr = {
static NAME: &[u8] = b"getrandom\0";
let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) }
};
#[cfg(target_env = "musl")]
let raw_ptr = {
let fptr: GetRandomFn = libc::getrandom;
unsafe { transmute::<GetRandomFn, *mut c_void>(fptr) }
};

let res_ptr = match NonNull::new(raw_ptr) {
Some(fptr) => {
let getrandom_fn = unsafe { transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
let dangling_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
fn is_getrandom_good(getrandom_fn: GetRandomFn) -> bool {
if cfg!(getrandom_test_linux_fallback) {
false
} else {
// Check that `getrandom` syscall is supported by kernel
let res = unsafe { getrandom_fn(ptr::dangling_mut(), 0, 0) };
if !res.is_negative() {
true
} else {
match util_libc::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.
Some(libc::EPERM) if cfg!(target_os = "linux") => false, // Blocked by seccomp
_ => true,
}
}
None => NOT_AVAILABLE,
};

#[cfg(getrandom_test_linux_without_fallback)]
if res_ptr == NOT_AVAILABLE {
panic!("Fallback is triggered with enabled `getrandom_test_linux_without_fallback`")
}
}

GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release);
res_ptr
fn to_getrandom_fn(getrandom_fn: usize) -> GetRandomFn {
unsafe { transmute::<usize, GetRandomFn>(getrandom_fn) }
}

#[cold]
#[inline(never)]
fn init() -> Option<usize> {
ptr::NonNull::new(unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"getrandom".as_ptr()) }).and_then(
|getrandom_fn| {
let getrandom_fn = to_getrandom_fn(getrandom_fn.as_ptr() as usize);
is_getrandom_good(getrandom_fn).then_some(getrandom_fn as usize)
},
)
}

// Prevent inlining of the fallback implementation
Expand All @@ -75,29 +59,45 @@ fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
use_file::fill_inner(dest)
}

fn with_unpoison_linux_gerandom_result(
dest: &mut [MaybeUninit<u8>],
getrandom_fn: GetRandomFn,
) -> Result<(), Error> {
util_libc::sys_fill_exact(dest, |buf| unsafe {
let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0);
sanitizer::unpoison_linux_getrandom_result(buf, ret);
ret
})
}

#[inline]
pub fn fill_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 cfg!(not(target_feature = "crt-static")) {
static GETRANDOM_FN: lazy::LazyUsize = lazy::LazyUsize::new();

if fptr == NOT_AVAILABLE {
use_file_fallback(dest)
const NOT_AVAILABLE: usize = usize::MAX;

match GETRANDOM_FN.unsync_init(|| init().unwrap_or(NOT_AVAILABLE)) {
NOT_AVAILABLE => {
if cfg!(getrandom_test_linux_without_fallback) {
panic!("fallback is triggered with `getrandom_test_linux_without_fallback`");
}
use_file_fallback(dest)
}
getrandom_fn => {
let getrandom_fn = to_getrandom_fn(getrandom_fn);
with_unpoison_linux_gerandom_result(dest, getrandom_fn)
}
}
} else if cfg!(has_libc_getrandom) {
use_file::fill_inner(dest)
} else {
// note: `transmute` is currently the only way to convert a pointer into a function reference
let getrandom_fn = unsafe { transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
util_libc::sys_fill_exact(dest, |buf| unsafe {
let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0);
sanitizer::unpoison_linux_getrandom_result(buf, ret);
ret
})
static GETRANDOM_GOOD: lazy::LazyBool = lazy::LazyBool::new();

if GETRANDOM_GOOD.unsync_init(|| is_getrandom_good(libc::getrandom)) {
with_unpoison_linux_gerandom_result(dest, libc::getrandom)
} else {
use_file_fallback(dest)
}
}
}
30 changes: 10 additions & 20 deletions src/backends/netbsd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ use core::{
ffi::c_void,
mem::{self, MaybeUninit},
ptr,
sync::atomic::{AtomicPtr, Ordering},
};

pub use crate::util::{inner_u32, inner_u64};

#[expect(dead_code, reason = "LazyBool is not used")]
#[path = "../lazy.rs"]
mod lazy;
#[path = "../util_libc.rs"]
mod util_libc;

Expand Down Expand Up @@ -42,36 +44,24 @@ unsafe extern "C" fn polyfill_using_kern_arand(

type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;

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

#[cold]
#[inline(never)]
fn init() -> *mut c_void {
static NAME: &[u8] = b"getrandom\0";
let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
let mut ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) };
fn init() -> usize {
let mut ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"getrandom".as_ptr()) };
if ptr.is_null() || cfg!(getrandom_test_netbsd_fallback) {
// Verify `polyfill_using_kern_arand` has the right signature.
const POLYFILL: GetRandomFn = polyfill_using_kern_arand;
ptr = POLYFILL as *mut c_void;
}
GETRANDOM.store(ptr, Ordering::Release);
ptr
ptr as usize
}

#[inline]
pub fn fill_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 mut fptr = GETRANDOM.load(Ordering::Acquire);
if fptr.is_null() {
fptr = init();
}
let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) };
static GETRANDOM_FN: lazy::LazyUsize = lazy::LazyUsize::new();

let fptr = GETRANDOM_FN.unsync_init(init);
let fptr = unsafe { mem::transmute::<usize, GetRandomFn>(fptr) };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this is probably incorrect from the strict pointer provenance point of view.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. The provenance of the original pointer is the same as the provenance of the pointer constructed here.

util_libc::sys_fill_exact(dest, |buf| unsafe {
fptr(buf.as_mut_ptr().cast::<c_void>(), buf.len(), 0)
})
Expand Down
6 changes: 3 additions & 3 deletions src/lazy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ use core::sync::atomic::{AtomicUsize, Ordering};
// }
// the effects of c() or writes to shared memory will not necessarily be
// observed and additional synchronization methods may be needed.
struct LazyUsize(AtomicUsize);
pub(crate) struct LazyUsize(AtomicUsize);

impl LazyUsize {
// The initialization is not completed.
const UNINIT: usize = usize::MAX;

const fn new() -> Self {
pub const fn new() -> Self {
Self(AtomicUsize::new(Self::UNINIT))
}

// Runs the init() function at most once, returning the value of some run of
// init(). Multiple callers can run their init() functions in parallel.
// init() should always return the same value, if it succeeds.
fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize {
pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize {
#[cold]
fn do_init(this: &LazyUsize, init: impl FnOnce() -> usize) -> usize {
let val = init();
Expand Down