Put random() state behind mutex

This commit is contained in:
Peter Limkilde Svendsen
2024-08-15 23:26:16 +00:00
committed by Jeremy Soller
parent 092663c50a
commit 3193656f22
5 changed files with 150 additions and 121 deletions
+30 -34
View File
@@ -416,16 +416,15 @@ pub extern "C" fn grantpt(fildes: c_int) -> c_int {
0
}
// Ported from musl
#[no_mangle]
pub unsafe extern "C" fn initstate(seed: c_uint, state: *mut c_char, size: size_t) -> *mut c_char {
// Ported from musl
if size < 8 {
ptr::null_mut()
} else {
// TODO: lock?
let old_state = random::save_state();
random::N = match size {
let mut random_state = random::state_lock();
let old_state = random_state.save();
random_state.n = match size {
0..=7 => unreachable!(), // ensured above
8..=31 => 0,
32..=63 => 7,
@@ -434,10 +433,9 @@ pub unsafe extern "C" fn initstate(seed: c_uint, state: *mut c_char, size: size_
_ => 63,
};
random::X_PTR = (state.cast::<[u8; 4]>()).offset(1);
random::seed(seed);
random::save_state();
// TODO: unlock?
random_state.x_ptr = (state.cast::<[u8; 4]>()).offset(1);
random_state.seed(seed);
random_state.save();
old_state.cast::<_>()
}
@@ -905,39 +903,39 @@ pub unsafe extern "C" fn rand_r(seed: *mut c_uint) -> c_int {
}
}
// Ported from musl
#[no_mangle]
pub unsafe extern "C" fn random() -> c_long {
// Ported from musl
let mut random_state = random::state_lock();
let k: u32;
// TODO: lock?
random::ensure_x_ptr_init();
if random::N == 0 {
let x_old = u32::from_ne_bytes(*random::X_PTR);
random_state.ensure_x_ptr_init();
if random_state.n == 0 {
let x_old = u32::from_ne_bytes(*random_state.x_ptr);
let x_new = random::lcg31_step(x_old);
*random::X_PTR = x_new.to_ne_bytes();
*random_state.x_ptr = x_new.to_ne_bytes();
k = x_new;
} else {
// The non-u32-aligned way of saying x[i] += x[j]...
let x_i_old = u32::from_ne_bytes(*random::X_PTR.add(usize::from(random::I)));
let x_j = u32::from_ne_bytes(*random::X_PTR.add(usize::from(random::J)));
let x_i_old = u32::from_ne_bytes(*random_state.x_ptr.add(usize::from(random_state.i)));
let x_j = u32::from_ne_bytes(*random_state.x_ptr.add(usize::from(random_state.j)));
let x_i_new = x_i_old.wrapping_add(x_j);
*random::X_PTR.add(usize::from(random::I)) = x_i_new.to_ne_bytes();
*random_state.x_ptr.add(usize::from(random_state.i)) = x_i_new.to_ne_bytes();
k = x_i_new >> 1;
random::I += 1;
if random::I == random::N {
random::I = 0;
random_state.i += 1;
if random_state.i == random_state.n {
random_state.i = 0;
}
random::J += 1;
if random::J == random::N {
random::J = 0;
random_state.j += 1;
if random_state.j == random_state.n {
random_state.j = 0;
}
}
// TODO: unlock?
/* Both branches of this function result in a "u31", which will
* always fit in a c_long. */
@@ -1064,14 +1062,13 @@ pub unsafe extern "C" fn setkey(key: *const c_char) {
unimplemented!();
}
// Ported from musl. The state parameter is no longer const in newer versions of POSIX.
#[no_mangle]
pub unsafe extern "C" fn setstate(state: *mut c_char) -> *mut c_char {
/* Ported from musl. The state parameter is no longer const in newer
* versions of POSIX. */
let mut random_state = random::state_lock();
// TODO: lock?
let old_state = random::save_state();
random::load_state(state.cast::<_>());
let old_state = random_state.save();
random_state.load(state.cast::<_>());
// TODO: unlock?
old_state.cast::<_>()
}
@@ -1095,13 +1092,12 @@ pub extern "C" fn srand48(seedval: c_long) {
.unwrap();
}
// Ported from musl
#[no_mangle]
pub unsafe extern "C" fn srandom(seed: c_uint) {
// Ported from musl
let mut random_state = random::state_lock();
// TODO: lock?
random::seed(seed);
// TODO: unlock?
random_state.seed(seed);
}
#[no_mangle]
+101 -81
View File
@@ -1,44 +1,111 @@
//! Helper functions for random() and friends, see https://pubs.opengroup.org/onlinepubs/7908799/xsh/initstate.html
/* Ported from musl's implementation (src/prng/random.c). Does not
* currently implement locking, though. */
// Ported from musl's implementation (src/prng/random.c)
use crate::platform::types::*;
use crate::{
platform::types::*,
sync::{Mutex, MutexGuard},
};
use core::{convert::TryFrom, ptr};
#[rustfmt::skip]
static mut X_INIT: [u32; 32] = [
0x00000000, 0x5851f42d, 0xc0b18ccf, 0xcbb5f646,
0xc7033129, 0x30705b04, 0x20fd5db4, 0x9a8b7f78,
0x502959d8, 0xab894868, 0x6c0356a7, 0x88cdb7ff,
0xb477d43f, 0x70a3a52b, 0xa8e4baf1, 0xfd8341fc,
0x8ae16fd9, 0x742d2f7a, 0x0d1f0796, 0x76035e09,
0x40f7702c, 0x6fa72ca5, 0xaaa84157, 0x58a0df74,
0xc74a0364, 0xae533cc4, 0x04185faf, 0x6de3b115,
0x0cab8628, 0xf043bfa4, 0x398150e9, 0x37521657,
];
pub struct State {
pub x_init: [u32; 32],
pub x_ptr: *mut [u8; 4],
pub n: u8,
pub i: u8,
pub j: u8,
}
/* N needs to accommodate values up to 63, corresponding to the maximum
* state array size of 256 bytes. I and J must be able to accommodate
* values less than or equal to N. */
pub static mut N: u8 = 31;
pub static mut I: u8 = 3;
pub static mut J: u8 = 0;
// Necessary because raw pointers are not Send
unsafe impl Send for State {}
/* As such, random() and related functions work on u32 values, but POSIX
* allows the user to supply a custom state data array as a `char *`
* with no requirements on alignment. Thus, we must assume the worst in
* terms of alignment and convert back and forth from [u8; 4].
*
* Also, unlike in C, we can't take the address of the initializing
* array outside of a function. */
pub static mut X_PTR: *mut [u8; 4] = ptr::null_mut();
// To be called in any function that may read from X_PTR
pub unsafe fn ensure_x_ptr_init() {
if X_PTR.is_null() {
let x_u32_ptr: *mut u32 = &mut X_INIT[1];
X_PTR = x_u32_ptr.cast::<[u8; 4]>();
impl State {
/// To be called in any function that may read from X_PTR
pub fn ensure_x_ptr_init(&mut self) {
if self.x_ptr.is_null() {
let x_u32_ptr: *mut u32 = &mut self.x_init[1];
self.x_ptr = x_u32_ptr.cast::<[u8; 4]>();
}
}
pub unsafe fn save(&mut self) -> *mut [u8; 4] {
self.ensure_x_ptr_init();
let stash_value: u32 =
(u32::from(self.n) << 16) | (u32::from(self.i) << 8) | u32::from(self.j);
*self.x_ptr.offset(-1) = stash_value.to_ne_bytes();
self.x_ptr.offset(-1)
}
pub unsafe fn load(&mut self, state_ptr: *mut [u8; 4]) {
let stash_value = u32::from_ne_bytes(*state_ptr);
self.x_ptr = state_ptr.offset(1);
/* This calculation of n does not have a bit mask in the musl
* original, in principle resulting in a u16, but obtaining a value
* larger than 63 can probably be dismissed as pathological. */
self.n = u8::try_from((stash_value >> 16) & 0xff).unwrap();
// i and j calculations are straight from musl
self.i = u8::try_from((stash_value >> 8) & 0xff).unwrap();
self.j = u8::try_from(stash_value & 0xff).unwrap();
}
pub unsafe fn seed(&mut self, seed: c_uint) {
self.ensure_x_ptr_init();
let mut s = seed as u64;
if self.n == 0 {
*self.x_ptr = (s as u32).to_ne_bytes();
} else {
self.i = if self.n == 31 || self.n == 7 { 3 } else { 1 };
self.j = 0;
for k in 0..usize::from(self.n) {
s = lcg64_step(s);
// Conversion will always succeed (value is a 32-bit right-
// shift of a 64-bit integer).
*self.x_ptr.add(k) = u32::try_from(s >> 32).unwrap().to_ne_bytes();
}
// ensure X contains at least one odd number
*self.x_ptr = (u32::from_ne_bytes(*self.x_ptr) | 1).to_ne_bytes();
}
}
}
pub fn state_lock<'a>() -> MutexGuard<'a, State> {
static STATE: Mutex<State> = Mutex::new(State {
#[rustfmt::skip]
x_init: [
0x00000000, 0x5851f42d, 0xc0b18ccf, 0xcbb5f646,
0xc7033129, 0x30705b04, 0x20fd5db4, 0x9a8b7f78,
0x502959d8, 0xab894868, 0x6c0356a7, 0x88cdb7ff,
0xb477d43f, 0x70a3a52b, 0xa8e4baf1, 0xfd8341fc,
0x8ae16fd9, 0x742d2f7a, 0x0d1f0796, 0x76035e09,
0x40f7702c, 0x6fa72ca5, 0xaaa84157, 0x58a0df74,
0xc74a0364, 0xae533cc4, 0x04185faf, 0x6de3b115,
0x0cab8628, 0xf043bfa4, 0x398150e9, 0x37521657,
],
/* As such, random() and related functions work on u32 values, but POSIX
* allows the user to supply a custom state data array as a `char *`
* with no requirements on alignment. Thus, we must assume the worst in
* terms of alignment and convert back and forth from [u8; 4].
*
* Also, unlike in C, we can't take the address of the initializing
* array outside of a function. */
x_ptr: ptr::null_mut(),
/* N needs to accommodate values up to 63, corresponding to the maximum
* state array size of 256 bytes. I and J must be able to accommodate
* values less than or equal to N. */
n: 31,
i: 3,
j: 0,
});
STATE.try_lock().expect("unable to acquire PRNG lock")
}
pub fn lcg31_step(x: u32) -> u32 {
@@ -48,50 +115,3 @@ pub fn lcg31_step(x: u32) -> u32 {
pub fn lcg64_step(x: u64) -> u64 {
6364136223846793005_u64.wrapping_mul(x).wrapping_add(1_u64)
}
pub unsafe fn save_state() -> *mut [u8; 4] {
ensure_x_ptr_init();
let stash_value: u32 = (u32::from(N) << 16) | (u32::from(I) << 8) | u32::from(J);
*X_PTR.offset(-1) = stash_value.to_ne_bytes();
X_PTR.offset(-1)
}
pub unsafe fn load_state(state_ptr: *mut [u8; 4]) {
let stash_value = u32::from_ne_bytes(*state_ptr);
X_PTR = state_ptr.offset(1);
/* This calculation of N does not have a bit mask in the musl
* original, in principle resulting in a u16, but obtaining a value
* larger than 63 can probably be dismissed as pathological. */
N = u8::try_from((stash_value >> 16) & 0xff).unwrap();
// I and J calculations are straight from musl
I = u8::try_from((stash_value >> 8) & 0xff).unwrap();
J = u8::try_from(stash_value & 0xff).unwrap();
}
pub unsafe fn seed(seed: c_uint) {
ensure_x_ptr_init();
let mut s = seed as u64;
if N == 0 {
*X_PTR = (s as u32).to_ne_bytes();
} else {
I = if N == 31 || N == 7 { 3 } else { 1 };
J = 0;
for k in 0..usize::from(N) {
s = lcg64_step(s);
// Conversion will always succeed (value is a 32-bit right-
// shift of a 64-bit integer).
*X_PTR.add(k) = u32::try_from(s >> 32).unwrap().to_ne_bytes();
}
// ensure X contains at least one odd number
*X_PTR = (u32::from_ne_bytes(*X_PTR) | 1).to_ne_bytes();
}
}