summaryrefslogtreecommitdiff
path: root/vendor/getrandom/src/backends/efi_rng.rs
blob: 768c8cc8cab31584db4cd70bbdbdcbf33fc66608 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! Implementation for UEFI using EFI_RNG_PROTOCOL
use crate::Error;
use core::{
    mem::MaybeUninit,
    ptr::{self, null_mut, NonNull},
    sync::atomic::{AtomicPtr, Ordering::Relaxed},
};
use r_efi::{
    efi::{BootServices, Handle},
    protocols::rng,
};

extern crate std;

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

#[cfg(not(target_os = "uefi"))]
compile_error!("`efi_rng` backend can be enabled only for UEFI targets!");

static RNG_PROTOCOL: AtomicPtr<rng::Protocol> = AtomicPtr::new(null_mut());

#[cold]
#[inline(never)]
fn init() -> Result<NonNull<rng::Protocol>, Error> {
    const HANDLE_SIZE: usize = size_of::<Handle>();

    let boot_services = std::os::uefi::env::boot_services()
        .ok_or(Error::BOOT_SERVICES_UNAVAILABLE)?
        .cast::<BootServices>();

    let mut handles = [ptr::null_mut(); 16];
    // `locate_handle` operates with length in bytes
    let mut buf_size = handles.len() * HANDLE_SIZE;
    let mut guid = rng::PROTOCOL_GUID;
    let ret = unsafe {
        ((*boot_services.as_ptr()).locate_handle)(
            r_efi::efi::BY_PROTOCOL,
            &mut guid,
            null_mut(),
            &mut buf_size,
            handles.as_mut_ptr(),
        )
    };

    if ret.is_error() {
        return Err(Error::from_uefi_code(ret.as_usize()));
    }

    let handles_len = buf_size / HANDLE_SIZE;
    let handles = handles.get(..handles_len).ok_or(Error::UNEXPECTED)?;

    let system_handle = std::os::uefi::env::image_handle();
    for &handle in handles {
        let mut protocol: MaybeUninit<*mut rng::Protocol> = MaybeUninit::uninit();

        let mut protocol_guid = rng::PROTOCOL_GUID;
        let ret = unsafe {
            ((*boot_services.as_ptr()).open_protocol)(
                handle,
                &mut protocol_guid,
                protocol.as_mut_ptr().cast(),
                system_handle.as_ptr(),
                ptr::null_mut(),
                r_efi::system::OPEN_PROTOCOL_GET_PROTOCOL,
            )
        };

        let protocol = if ret.is_error() {
            continue;
        } else {
            let protocol = unsafe { protocol.assume_init() };
            NonNull::new(protocol).ok_or(Error::UNEXPECTED)?
        };

        // Try to use the acquired protocol handle
        let mut buf = [0u8; 8];
        let mut alg_guid = rng::ALGORITHM_RAW;
        let ret = unsafe {
            ((*protocol.as_ptr()).get_rng)(
                protocol.as_ptr(),
                &mut alg_guid,
                buf.len(),
                buf.as_mut_ptr(),
            )
        };

        if ret.is_error() {
            continue;
        }

        RNG_PROTOCOL.store(protocol.as_ptr(), Relaxed);
        return Ok(protocol);
    }
    Err(Error::NO_RNG_HANDLE)
}

#[inline]
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
    let protocol = match NonNull::new(RNG_PROTOCOL.load(Relaxed)) {
        Some(p) => p,
        None => init()?,
    };

    let mut alg_guid = rng::ALGORITHM_RAW;
    let ret = unsafe {
        ((*protocol.as_ptr()).get_rng)(
            protocol.as_ptr(),
            &mut alg_guid,
            dest.len(),
            dest.as_mut_ptr().cast::<u8>(),
        )
    };

    if ret.is_error() {
        Err(Error::from_uefi_code(ret.as_usize()))
    } else {
        Ok(())
    }
}

impl Error {
    pub(crate) const BOOT_SERVICES_UNAVAILABLE: Error = Self::new_internal(10);
    pub(crate) const NO_RNG_HANDLE: Error = Self::new_internal(11);
}