@@ -4,69 +4,53 @@ use crate::Error;
44use core:: {
55 ffi:: c_void,
66 mem:: { MaybeUninit , transmute} ,
7- ptr:: NonNull ,
8- sync:: atomic:: { AtomicPtr , Ordering } ,
7+ ptr,
98} ;
109use use_file:: util_libc;
1110
1211pub use crate :: util:: { inner_u32, inner_u64} ;
1312
14- type GetRandomFn = unsafe extern "C" fn ( * mut c_void , libc:: size_t , libc:: c_uint ) -> libc:: ssize_t ;
15-
16- /// Sentinel value which indicates that `libc::getrandom` either not available,
17- /// or not supported by kernel.
18- const NOT_AVAILABLE : NonNull < c_void > = unsafe { NonNull :: new_unchecked ( usize:: MAX as * mut c_void ) } ;
13+ #[ path = "../lazy.rs" ]
14+ mod lazy;
1915
20- static GETRANDOM_FN : AtomicPtr < c_void > = AtomicPtr :: new ( core :: ptr :: null_mut ( ) ) ;
16+ type GetRandomFn = unsafe extern "C" fn ( * mut c_void , libc :: size_t , libc :: c_uint ) -> libc :: ssize_t ;
2117
2218#[ cold]
2319#[ inline( never) ]
24- fn init ( ) -> NonNull < c_void > {
25- // Use static linking to `libc::getrandom` on MUSL targets and `dlsym` everywhere else
26- #[ cfg( not( target_env = "musl" ) ) ]
27- let raw_ptr = {
28- static NAME : & [ u8 ] = b"getrandom\0 " ;
29- let name_ptr = NAME . as_ptr ( ) . cast :: < libc:: c_char > ( ) ;
30- unsafe { libc:: dlsym ( libc:: RTLD_DEFAULT , name_ptr) }
31- } ;
32- #[ cfg( target_env = "musl" ) ]
33- let raw_ptr = {
34- let fptr: GetRandomFn = libc:: getrandom;
35- unsafe { transmute :: < GetRandomFn , * mut c_void > ( fptr) }
36- } ;
37-
38- let res_ptr = match NonNull :: new ( raw_ptr) {
39- Some ( fptr) => {
40- let getrandom_fn = unsafe { transmute :: < NonNull < c_void > , GetRandomFn > ( fptr) } ;
41- let dangling_ptr = NonNull :: dangling ( ) . as_ptr ( ) ;
42- // Check that `getrandom` syscall is supported by kernel
43- let res = unsafe { getrandom_fn ( dangling_ptr, 0 , 0 ) } ;
44- if cfg ! ( getrandom_test_linux_fallback) {
45- NOT_AVAILABLE
46- } else if res. is_negative ( ) {
47- match util_libc:: last_os_error ( ) . raw_os_error ( ) {
48- Some ( libc:: ENOSYS ) => NOT_AVAILABLE , // No kernel support
49- // The fallback on EPERM is intentionally not done on Android since this workaround
50- // seems to be needed only for specific Linux-based products that aren't based
51- // on Android. See https://github.com/rust-random/getrandom/issues/229.
52- #[ cfg( target_os = "linux" ) ]
53- Some ( libc:: EPERM ) => NOT_AVAILABLE , // Blocked by seccomp
54- _ => fptr,
55- }
56- } else {
57- fptr
20+ fn is_getrandom_good ( getrandom_fn : GetRandomFn ) -> bool {
21+ if cfg ! ( getrandom_test_linux_fallback) {
22+ false
23+ } else {
24+ // Check that `getrandom` syscall is supported by kernel
25+ let res = unsafe { getrandom_fn ( ptr:: dangling_mut ( ) , 0 , 0 ) } ;
26+ if !res. is_negative ( ) {
27+ true
28+ } else {
29+ match util_libc:: last_os_error ( ) . raw_os_error ( ) {
30+ Some ( libc:: ENOSYS ) => false , // No kernel support
31+ // The fallback on EPERM is intentionally not done on Android since this workaround
32+ // seems to be needed only for specific Linux-based products that aren't based
33+ // on Android. See https://github.com/rust-random/getrandom/issues/229.
34+ Some ( libc:: EPERM ) if cfg ! ( target_os = "linux" ) => false , // Blocked by seccomp
35+ _ => true ,
5836 }
5937 }
60- None => NOT_AVAILABLE ,
61- } ;
62-
63- #[ cfg( getrandom_test_linux_without_fallback) ]
64- if res_ptr == NOT_AVAILABLE {
65- panic ! ( "Fallback is triggered with enabled `getrandom_test_linux_without_fallback`" )
6638 }
39+ }
6740
68- GETRANDOM_FN . store ( res_ptr. as_ptr ( ) , Ordering :: Release ) ;
69- res_ptr
41+ fn to_getrandom_fn ( getrandom_fn : usize ) -> GetRandomFn {
42+ unsafe { transmute :: < usize , GetRandomFn > ( getrandom_fn) }
43+ }
44+
45+ #[ cold]
46+ #[ inline( never) ]
47+ fn init ( ) -> Option < usize > {
48+ ptr:: NonNull :: new ( unsafe { libc:: dlsym ( libc:: RTLD_DEFAULT , c"getrandom" . as_ptr ( ) ) } ) . and_then (
49+ |getrandom_fn| {
50+ let getrandom_fn = to_getrandom_fn ( getrandom_fn. as_ptr ( ) as usize ) ;
51+ is_getrandom_good ( getrandom_fn) . then_some ( getrandom_fn as usize )
52+ } ,
53+ )
7054}
7155
7256// Prevent inlining of the fallback implementation
@@ -75,29 +59,45 @@ fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
7559 use_file:: fill_inner ( dest)
7660}
7761
62+ fn with_unpoison_linux_gerandom_result (
63+ dest : & mut [ MaybeUninit < u8 > ] ,
64+ getrandom_fn : GetRandomFn ,
65+ ) -> Result < ( ) , Error > {
66+ util_libc:: sys_fill_exact ( dest, |buf| unsafe {
67+ let ret = getrandom_fn ( buf. as_mut_ptr ( ) . cast ( ) , buf. len ( ) , 0 ) ;
68+ sanitizer:: unpoison_linux_getrandom_result ( buf, ret) ;
69+ ret
70+ } )
71+ }
72+
7873#[ inline]
7974pub fn fill_inner ( dest : & mut [ MaybeUninit < u8 > ] ) -> Result < ( ) , Error > {
80- // Despite being only a single atomic variable, we still cannot always use
81- // Ordering::Relaxed, as we need to make sure a successful call to `init`
82- // is "ordered before" any data read through the returned pointer (which
83- // occurs when the function is called). Our implementation mirrors that of
84- // the one in libstd, meaning that the use of non-Relaxed operations is
85- // probably unnecessary.
86- let raw_ptr = GETRANDOM_FN . load ( Ordering :: Acquire ) ;
87- let fptr = match NonNull :: new ( raw_ptr) {
88- Some ( p) => p,
89- None => init ( ) ,
90- } ;
75+ if cfg ! ( not( target_feature = "crt-static" ) ) {
76+ static GETRANDOM_FN : lazy:: LazyUsize = lazy:: LazyUsize :: new ( ) ;
9177
92- if fptr == NOT_AVAILABLE {
93- use_file_fallback ( dest)
78+ const NOT_AVAILABLE : usize = usize:: MAX ;
79+
80+ match GETRANDOM_FN . unsync_init ( || init ( ) . unwrap_or ( NOT_AVAILABLE ) ) {
81+ NOT_AVAILABLE => {
82+ if cfg ! ( getrandom_test_linux_without_fallback) {
83+ panic ! ( "fallback is triggered with `getrandom_test_linux_without_fallback`" ) ;
84+ }
85+ use_file_fallback ( dest)
86+ }
87+ getrandom_fn => {
88+ let getrandom_fn = to_getrandom_fn ( getrandom_fn) ;
89+ with_unpoison_linux_gerandom_result ( dest, getrandom_fn)
90+ }
91+ }
92+ } else if cfg ! ( has_libc_getrandom) {
93+ use_file:: fill_inner ( dest)
9494 } else {
95- // note: `transmute` is currently the only way to convert a pointer into a function reference
96- let getrandom_fn = unsafe { transmute :: < NonNull < c_void > , GetRandomFn > ( fptr ) } ;
97- util_libc :: sys_fill_exact ( dest , |buf| unsafe {
98- let ret = getrandom_fn ( buf . as_mut_ptr ( ) . cast ( ) , buf . len ( ) , 0 ) ;
99- sanitizer :: unpoison_linux_getrandom_result ( buf , ret ) ;
100- ret
101- } )
95+ static GETRANDOM_GOOD : lazy :: LazyBool = lazy :: LazyBool :: new ( ) ;
96+
97+ if GETRANDOM_GOOD . unsync_init ( || is_getrandom_good ( libc :: getrandom ) ) {
98+ with_unpoison_linux_gerandom_result ( dest , libc :: getrandom )
99+ } else {
100+ use_file_fallback ( dest )
101+ }
102102 }
103103}
0 commit comments