@@ -30,8 +30,23 @@ mod imp {
30
30
use fs:: File ;
31
31
use io:: Read ;
32
32
use libc;
33
+ use sync:: atomic:: { AtomicBool , AtomicI32 , Ordering } ;
33
34
use sys:: os:: errno;
34
35
36
+ static GETRANDOM_URANDOM_FD : AtomicI32 = AtomicI32 :: new ( -1 ) ;
37
+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
38
+ static GETRANDOM_UNAVAILABLE : AtomicBool = AtomicBool :: new ( false ) ;
39
+
40
+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
41
+ fn is_getrandom_permanently_unavailable ( ) -> bool {
42
+ GETRANDOM_UNAVAILABLE . load ( Ordering :: Relaxed )
43
+ }
44
+
45
+ #[ cfg( not( any( target_os = "linux" , target_os = "android" ) ) ) ]
46
+ fn is_getrandom_permanently_unavailable ( ) -> bool {
47
+ true
48
+ }
49
+
35
50
#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
36
51
fn getrandom ( buf : & mut [ u8 ] ) -> libc:: c_long {
37
52
unsafe {
@@ -40,71 +55,74 @@ mod imp {
40
55
}
41
56
42
57
#[ cfg( not( any( target_os = "linux" , target_os = "android" ) ) ) ]
43
- fn getrandom ( _buf : & mut [ u8 ] ) -> libc :: c_long { - 1 }
58
+ fn getrandom_fill_bytes ( _buf : & mut [ u8 ] ) -> bool { false }
44
59
60
+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
45
61
fn getrandom_fill_bytes ( v : & mut [ u8 ] ) -> bool {
62
+ if is_getrandom_permanently_unavailable ( ) {
63
+ return false ;
64
+ }
65
+
46
66
let mut read = 0 ;
47
67
while read < v. len ( ) {
48
68
let result = getrandom ( & mut v[ read..] ) ;
49
69
if result == -1 {
50
70
let err = errno ( ) as libc:: c_int ;
51
71
if err == libc:: EINTR {
52
72
continue ;
73
+ } else if err == libc:: ENOSYS {
74
+ GETRANDOM_UNAVAILABLE . store ( true , Ordering :: Relaxed ) ;
53
75
} else if err == libc:: EAGAIN {
54
- return false
76
+ return false ;
55
77
} else {
56
78
panic ! ( "unexpected getrandom error: {}" , err) ;
57
79
}
58
80
} else {
59
81
read += result as usize ;
60
82
}
61
83
}
62
-
63
- return true
84
+ true
64
85
}
65
86
66
- #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
67
- fn is_getrandom_available ( ) -> bool {
68
- use io;
69
- use sync:: atomic:: { AtomicBool , Ordering } ;
70
- use sync:: Once ;
71
-
72
- static CHECKER : Once = Once :: new ( ) ;
73
- static AVAILABLE : AtomicBool = AtomicBool :: new ( false ) ;
74
-
75
- CHECKER . call_once ( || {
76
- let mut buf: [ u8 ; 0 ] = [ ] ;
77
- let result = getrandom ( & mut buf) ;
78
- let available = if result == -1 {
79
- let err = io:: Error :: last_os_error ( ) . raw_os_error ( ) ;
80
- err != Some ( libc:: ENOSYS )
81
- } else {
82
- true
83
- } ;
84
- AVAILABLE . store ( available, Ordering :: Relaxed ) ;
85
- } ) ;
86
-
87
- AVAILABLE . load ( Ordering :: Relaxed )
88
- }
89
-
90
- #[ cfg( not( any( target_os = "linux" , target_os = "android" ) ) ) ]
91
- fn is_getrandom_available ( ) -> bool { false }
92
-
93
87
pub fn fill_bytes ( v : & mut [ u8 ] ) {
94
88
// getrandom_fill_bytes here can fail if getrandom() returns EAGAIN,
95
89
// meaning it would have blocked because the non-blocking pool (urandom)
96
- // has not initialized in the kernel yet due to a lack of entropy the
90
+ // has not initialized in the kernel yet due to a lack of entropy. The
97
91
// fallback we do here is to avoid blocking applications which could
98
92
// depend on this call without ever knowing they do and don't have a
99
- // work around. The PRNG of /dev/urandom will still be used but not
100
- // over a completely full entropy pool
101
- if is_getrandom_available ( ) && getrandom_fill_bytes ( v) {
102
- return
93
+ // work around. The PRNG of /dev/urandom will still be used but over a
94
+ // possibly predictable entropy pool.
95
+ if getrandom_fill_bytes ( v) {
96
+ return ;
103
97
}
104
98
105
- let mut file = File :: open ( "/dev/urandom" )
106
- . expect ( "failed to open /dev/urandom" ) ;
107
- file. read_exact ( v) . expect ( "failed to read /dev/urandom" ) ;
99
+ // getrandom failed for some reason. If the getrandom call is
100
+ // permanently unavailable (OS without getrandom, or OS version without
101
+ // getrandom), we'll keep around the fd for /dev/urandom for future
102
+ // requests, to avoid re-opening the file on every call.
103
+ //
104
+ // Otherwise, open /dev/urandom, read from it, and close it again.
105
+ use super :: super :: ext:: io:: { FromRawFd , IntoRawFd } ;
106
+ let mut fd = GETRANDOM_URANDOM_FD . load ( Ordering :: Relaxed ) ;
107
+ let mut close_fd = false ;
108
+ if fd == -1 {
109
+ if !is_getrandom_permanently_unavailable ( ) {
110
+ close_fd = true ;
111
+ }
112
+ let file = File :: open ( "/dev/urandom" ) . expect ( "failed to open /dev/urandom" ) ;
113
+ fd = file. into_raw_fd ( ) ;
114
+ // If some other thread also opened /dev/urandom and set the global
115
+ // fd already, we close our fd at the end of the function.
116
+ if !close_fd && GETRANDOM_URANDOM_FD . compare_and_swap ( -1 , fd, Ordering :: Relaxed ) != -1 {
117
+ close_fd = true ;
118
+ }
119
+ }
120
+ let mut file = unsafe { File :: from_raw_fd ( fd) } ;
121
+ let res = file. read_exact ( v) ;
122
+ if !close_fd {
123
+ let _ = file. into_raw_fd ( ) ;
124
+ }
125
+ res. expect ( "failed to read /dev/urandom" ) ;
108
126
}
109
127
}
110
128
0 commit comments