pvmfw: Encrypt instance.img entries
As the host owns the files backing the virtio-blk devices, encrypt the
entries in a tamper-evident way.
Derive the private key used for encryption from the sealing CDI. Note
that this patch uses a _randnonce() AEAD but doesn't provide entropy,
which will be added in a future patch.
Implement a wrapper for BoringSSL AEAD functions, key derivation,
hashing, and error handling. Implement the CRYPTO_sysrand* symbols those
require.
Add sterror(), required by ERR_reason_error_string, to vmbase instead of
using the Bionic version, which is harder to integrate due to
thread-safety support and TLS layout. Error reporting also requires the
standard bsearch() function.
Note: Entries added to an instance.img before applying this patch will
now be rejected.
Bug: 249723852
Test: atest MicrodroidHostTests
Change-Id: If41aa8e1961121d9aee116c14b54d983dd10f61e
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 193ffa9..d78f4f2 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -13,6 +13,7 @@
],
rustlibs: [
"libaarch64_paging",
+ "libbssl_ffi_nostd",
"libbuddy_system_allocator",
"libdiced_open_dice_nostd",
"libfdtpci",
diff --git a/pvmfw/src/crypto.rs b/pvmfw/src/crypto.rs
new file mode 100644
index 0000000..85dc6c9
--- /dev/null
+++ b/pvmfw/src/crypto.rs
@@ -0,0 +1,302 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Wrapper around BoringSSL/OpenSSL symbols.
+
+use core::convert::AsRef;
+use core::ffi::{c_char, c_int, CStr};
+use core::fmt;
+use core::mem::MaybeUninit;
+use core::num::NonZeroU32;
+use core::ptr;
+
+use bssl_ffi::ERR_get_error_line;
+use bssl_ffi::ERR_lib_error_string;
+use bssl_ffi::ERR_reason_error_string;
+use bssl_ffi::EVP_AEAD_CTX_aead;
+use bssl_ffi::EVP_AEAD_CTX_init;
+use bssl_ffi::EVP_AEAD_CTX_open;
+use bssl_ffi::EVP_AEAD_CTX_seal;
+use bssl_ffi::EVP_AEAD_max_overhead;
+use bssl_ffi::EVP_aead_aes_256_gcm_randnonce;
+use bssl_ffi::EVP_sha512;
+use bssl_ffi::EVP_AEAD;
+use bssl_ffi::EVP_AEAD_CTX;
+use bssl_ffi::HKDF;
+
+#[derive(Debug)]
+pub struct Error {
+ packed: NonZeroU32,
+ file: Option<&'static CStr>,
+ line: c_int,
+}
+
+impl Error {
+ fn get() -> Option<Self> {
+ let mut file = MaybeUninit::uninit();
+ let mut line = MaybeUninit::uninit();
+ // SAFETY - The function writes to the provided pointers, validated below.
+ let packed = unsafe { ERR_get_error_line(file.as_mut_ptr(), line.as_mut_ptr()) };
+ // SAFETY - Any possible value returned could be considered a valid *const c_char.
+ let file = unsafe { file.assume_init() };
+ // SAFETY - Any possible value returned could be considered a valid c_int.
+ let line = unsafe { line.assume_init() };
+
+ let packed = packed.try_into().ok()?;
+ // SAFETY - Any non-NULL result is expected to point to a global const C string.
+ let file = unsafe { as_static_cstr(file) };
+
+ Some(Self { packed, file, line })
+ }
+
+ fn packed_value(&self) -> u32 {
+ self.packed.get()
+ }
+
+ fn library_name(&self) -> Option<&'static CStr> {
+ // SAFETY - Call to a pure function.
+ let name = unsafe { ERR_lib_error_string(self.packed_value()) };
+ // SAFETY - Any non-NULL result is expected to point to a global const C string.
+ unsafe { as_static_cstr(name) }
+ }
+
+ fn reason(&self) -> Option<&'static CStr> {
+ // SAFETY - Call to a pure function.
+ let reason = unsafe { ERR_reason_error_string(self.packed_value()) };
+ // SAFETY - Any non-NULL result is expected to point to a global const C string.
+ unsafe { as_static_cstr(reason) }
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let unknown_library = CStr::from_bytes_with_nul(b"{unknown library}\0").unwrap();
+ let unknown_reason = CStr::from_bytes_with_nul(b"{unknown reason}\0").unwrap();
+ let unknown_file = CStr::from_bytes_with_nul(b"??\0").unwrap();
+
+ let packed = self.packed_value();
+ let library = self.library_name().unwrap_or(unknown_library).to_str().unwrap();
+ let reason = self.reason().unwrap_or(unknown_reason).to_str().unwrap();
+ let file = self.file.unwrap_or(unknown_file).to_str().unwrap();
+ let line = self.line;
+
+ write!(f, "{file}:{line}: {library}: {reason} ({packed:#x})")
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct ErrorIterator {}
+
+impl Iterator for ErrorIterator {
+ type Item = Error;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ Self::Item::get()
+ }
+}
+
+pub type Result<T> = core::result::Result<T, ErrorIterator>;
+
+#[repr(transparent)]
+pub struct Aead(EVP_AEAD);
+
+impl Aead {
+ pub fn aes_256_gcm_randnonce() -> Option<&'static Self> {
+ // SAFETY - Returned pointer is checked below.
+ let aead = unsafe { EVP_aead_aes_256_gcm_randnonce() };
+ if aead.is_null() {
+ None
+ } else {
+ // SAFETY - We assume that the non-NULL value points to a valid and static EVP_AEAD.
+ Some(unsafe { &*(aead as *const _) })
+ }
+ }
+
+ pub fn max_overhead(&self) -> usize {
+ // SAFETY - Function should only read from self.
+ unsafe { EVP_AEAD_max_overhead(self.as_ref() as *const _) }
+ }
+}
+
+#[repr(transparent)]
+pub struct AeadCtx(EVP_AEAD_CTX);
+
+impl AeadCtx {
+ pub fn new_aes_256_gcm_randnonce(key: &[u8]) -> Result<Self> {
+ let aead = Aead::aes_256_gcm_randnonce().unwrap();
+
+ Self::new(aead, key)
+ }
+
+ fn new(aead: &'static Aead, key: &[u8]) -> Result<Self> {
+ const DEFAULT_TAG_LENGTH: usize = 0;
+ let engine = ptr::null_mut(); // Use default implementation.
+ let mut ctx = MaybeUninit::zeroed();
+ // SAFETY - Initialize the EVP_AEAD_CTX with const pointers to the AEAD and key.
+ let result = unsafe {
+ EVP_AEAD_CTX_init(
+ ctx.as_mut_ptr(),
+ aead.as_ref() as *const _,
+ key.as_ptr(),
+ key.len(),
+ DEFAULT_TAG_LENGTH,
+ engine,
+ )
+ };
+
+ if result == 1 {
+ // SAFETY - We assume that the non-NULL value points to a valid and static EVP_AEAD.
+ Ok(Self(unsafe { ctx.assume_init() }))
+ } else {
+ Err(ErrorIterator {})
+ }
+ }
+
+ pub fn aead(&self) -> Option<&'static Aead> {
+ // SAFETY - The function should only read from self.
+ let aead = unsafe { EVP_AEAD_CTX_aead(self.as_ref() as *const _) };
+ if aead.is_null() {
+ None
+ } else {
+ // SAFETY - We assume that the non-NULL value points to a valid and static EVP_AEAD.
+ Some(unsafe { &*(aead as *const _) })
+ }
+ }
+
+ pub fn open<'b>(&self, out: &'b mut [u8], data: &[u8]) -> Result<&'b mut [u8]> {
+ let nonce = ptr::null_mut();
+ let nonce_len = 0;
+ let ad = ptr::null_mut();
+ let ad_len = 0;
+ let mut out_len = MaybeUninit::uninit();
+ // SAFETY - The function should only read from self and write to out (at most the provided
+ // number of bytes) and out_len while reading from data (at most the provided number of
+ // bytes), ignoring any NULL input.
+ let result = unsafe {
+ EVP_AEAD_CTX_open(
+ self.as_ref() as *const _,
+ out.as_mut_ptr(),
+ out_len.as_mut_ptr(),
+ out.len(),
+ nonce,
+ nonce_len,
+ data.as_ptr(),
+ data.len(),
+ ad,
+ ad_len,
+ )
+ };
+
+ if result == 1 {
+ // SAFETY - Any value written to out_len could be a valid usize. The value itself is
+ // validated as being a proper slice length by panicking in the following indexing
+ // otherwise.
+ let out_len = unsafe { out_len.assume_init() };
+ Ok(&mut out[..out_len])
+ } else {
+ Err(ErrorIterator {})
+ }
+ }
+
+ pub fn seal<'b>(&self, out: &'b mut [u8], data: &[u8]) -> Result<&'b mut [u8]> {
+ let nonce = ptr::null_mut();
+ let nonce_len = 0;
+ let ad = ptr::null_mut();
+ let ad_len = 0;
+ let mut out_len = MaybeUninit::uninit();
+ // SAFETY - The function should only read from self and write to out (at most the provided
+ // number of bytes) while reading from data (at most the provided number of bytes),
+ // ignoring any NULL input.
+ let result = unsafe {
+ EVP_AEAD_CTX_seal(
+ self.as_ref() as *const _,
+ out.as_mut_ptr(),
+ out_len.as_mut_ptr(),
+ out.len(),
+ nonce,
+ nonce_len,
+ data.as_ptr(),
+ data.len(),
+ ad,
+ ad_len,
+ )
+ };
+
+ if result == 1 {
+ // SAFETY - Any value written to out_len could be a valid usize. The value itself is
+ // validated as being a proper slice length by panicking in the following indexing
+ // otherwise.
+ let out_len = unsafe { out_len.assume_init() };
+ Ok(&mut out[..out_len])
+ } else {
+ Err(ErrorIterator {})
+ }
+ }
+}
+
+/// Cast a C string pointer to a static non-mutable reference.
+///
+/// # Safety
+///
+/// The caller needs to ensure that the pointer points to a valid C string and that the C lifetime
+/// of the string is compatible with a static Rust lifetime.
+unsafe fn as_static_cstr(p: *const c_char) -> Option<&'static CStr> {
+ if p.is_null() {
+ None
+ } else {
+ Some(CStr::from_ptr(p))
+ }
+}
+
+impl AsRef<EVP_AEAD> for Aead {
+ fn as_ref(&self) -> &EVP_AEAD {
+ &self.0
+ }
+}
+
+impl AsRef<EVP_AEAD_CTX> for AeadCtx {
+ fn as_ref(&self) -> &EVP_AEAD_CTX {
+ &self.0
+ }
+}
+
+pub fn hkdf_sh512<const N: usize>(secret: &[u8], salt: &[u8], info: &[u8]) -> Result<[u8; N]> {
+ let mut key = [0; N];
+ // SAFETY - The function shouldn't access any Rust variable and the returned value is accepted
+ // as a potentially NULL pointer.
+ let digest = unsafe { EVP_sha512() };
+
+ assert!(!digest.is_null());
+ // SAFETY - Only reads from/writes to the provided slices and supports digest was checked not
+ // be NULL.
+ let result = unsafe {
+ HKDF(
+ key.as_mut_ptr(),
+ key.len(),
+ digest,
+ secret.as_ptr(),
+ secret.len(),
+ salt.as_ptr(),
+ salt.len(),
+ info.as_ptr(),
+ info.len(),
+ )
+ };
+
+ if result == 1 {
+ Ok(key)
+ } else {
+ Err(ErrorIterator {})
+ }
+}
diff --git a/pvmfw/src/instance.rs b/pvmfw/src/instance.rs
index 3657fb6..6a54623 100644
--- a/pvmfw/src/instance.rs
+++ b/pvmfw/src/instance.rs
@@ -14,6 +14,9 @@
//! Support for reading and writing to the instance.img.
+use crate::crypto;
+use crate::crypto::hkdf_sh512;
+use crate::crypto::AeadCtx;
use crate::dice::PartialInputs;
use crate::gpt;
use crate::gpt::Partition;
@@ -33,6 +36,10 @@
pub enum Error {
/// Unexpected I/O error while accessing the underlying disk.
FailedIo(gpt::Error),
+ /// Failed to decrypt the entry.
+ FailedOpen(crypto::ErrorIterator),
+ /// Failed to encrypt the entry.
+ FailedSeal(crypto::ErrorIterator),
/// Impossible to create a new instance.img entry.
InstanceImageFull,
/// Badly formatted instance.img header block.
@@ -55,6 +62,20 @@
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
+ Self::FailedOpen(e_iter) => {
+ writeln!(f, "Failed to open the instance.img partition:")?;
+ for e in *e_iter {
+ writeln!(f, "\t{e}")?;
+ }
+ Ok(())
+ }
+ Self::FailedSeal(e_iter) => {
+ writeln!(f, "Failed to seal the instance.img partition:")?;
+ for e in *e_iter {
+ writeln!(f, "\t{e}")?;
+ }
+ Ok(())
+ }
Self::InstanceImageFull => write!(f, "Failed to obtain a free instance.img partition"),
Self::InvalidInstanceImageHeader => write!(f, "instance.img header is invalid"),
Self::MissingInstanceImage => write!(f, "Failed to find the instance.img partition"),
@@ -72,12 +93,14 @@
pub fn get_or_generate_instance_salt(
pci_root: &mut PciRoot,
dice_inputs: &PartialInputs,
+ secret: &[u8],
) -> Result<(bool, Hidden)> {
let mut instance_img = find_instance_img(pci_root)?;
let entry = locate_entry(&mut instance_img)?;
trace!("Found pvmfw instance.img entry: {entry:?}");
+ let key = hkdf_sh512::<32>(secret, /*salt=*/ &[], b"vm-instance");
let mut blk = [0; BLK_SIZE];
match entry {
PvmfwEntry::Existing { header_index, payload_size } => {
@@ -88,9 +111,13 @@
let payload_index = header_index + 1;
instance_img.read_block(payload_index, &mut blk).map_err(Error::FailedIo)?;
- let payload = &blk[..payload_size]; // TODO(b/249723852): Decrypt entries.
+ let payload = &blk[..payload_size];
+ let mut entry = [0; size_of::<EntryBody>()];
+ let key = key.map_err(Error::FailedOpen)?;
+ let aead = AeadCtx::new_aes_256_gcm_randnonce(&key).map_err(Error::FailedOpen)?;
+ let decrypted = aead.open(&mut entry, payload).map_err(Error::FailedOpen)?;
- let body: &EntryBody = payload.as_ref();
+ let body: &EntryBody = decrypted.as_ref();
if body.code_hash != dice_inputs.code_hash {
Err(Error::RecordedCodeHashMismatch)
} else if body.auth_hash != dice_inputs.auth_hash {
@@ -105,11 +132,13 @@
let salt = [0; size_of::<Hidden>()]; // TODO(b/262393451): Generate using TRNG.
let entry_body = EntryBody::new(dice_inputs, &salt);
let body = entry_body.as_ref();
- // We currently only support single-blk entries.
- assert!(body.len() < blk.len());
- let payload_size = body.len();
- blk[..payload_size].copy_from_slice(body); // TODO(b/249723852): Encrypt entries.
+ let key = key.map_err(Error::FailedSeal)?;
+ let aead = AeadCtx::new_aes_256_gcm_randnonce(&key).map_err(Error::FailedSeal)?;
+ // We currently only support single-blk entries.
+ assert!(body.len() + aead.aead().unwrap().max_overhead() < blk.len());
+ let encrypted = aead.seal(&mut blk, body).map_err(Error::FailedSeal)?;
+ let payload_size = encrypted.len();
let payload_index = header_index + 1;
instance_img.write_block(payload_index, &blk).map_err(Error::FailedIo)?;
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 6545a07..48bab0c 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -21,6 +21,7 @@
extern crate alloc;
mod config;
+mod crypto;
mod debug_policy;
mod dice;
mod entry;
@@ -34,6 +35,7 @@
mod memory;
mod mmio_guard;
mod mmu;
+mod rand;
mod smccc;
mod virtio;
@@ -49,6 +51,7 @@
use crate::virtio::pci;
use diced_open_dice::bcc_handover_main_flow;
use diced_open_dice::bcc_handover_parse;
+use diced_open_dice::DiceArtifacts;
use fdtpci::{PciError, PciInfo};
use libfdt::Fdt;
use log::{debug, error, info, trace};
@@ -100,8 +103,9 @@
error!("Failed to compute partial DICE inputs: {e:?}");
RebootReason::InternalError
})?;
- let (new_instance, salt) =
- get_or_generate_instance_salt(&mut pci_root, &dice_inputs).map_err(|e| {
+ let cdi_seal = DiceArtifacts::cdi_seal(&bcc_handover);
+ let (new_instance, salt) = get_or_generate_instance_salt(&mut pci_root, &dice_inputs, cdi_seal)
+ .map_err(|e| {
error!("Failed to get instance.img salt: {e}");
RebootReason::InternalError
})?;
diff --git a/pvmfw/src/rand.rs b/pvmfw/src/rand.rs
new file mode 100644
index 0000000..2824cbd
--- /dev/null
+++ b/pvmfw/src/rand.rs
@@ -0,0 +1,23 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[no_mangle]
+extern "C" fn CRYPTO_sysrand_for_seed(out: *mut u8, req: usize) {
+ CRYPTO_sysrand(out, req)
+}
+
+#[no_mangle]
+extern "C" fn CRYPTO_sysrand(out: *mut u8, req: usize) {
+ #![allow(unused_variables)] // TODO(b/262393451): Fill with actual entropy.
+}
diff --git a/vmbase/src/bionic.rs b/vmbase/src/bionic.rs
index 6f88cf6..69da521 100644
--- a/vmbase/src/bionic.rs
+++ b/vmbase/src/bionic.rs
@@ -126,3 +126,145 @@
0
}
}
+
+#[no_mangle]
+extern "C" fn strerror(n: c_int) -> *mut c_char {
+ // Messages taken from errno(1).
+ let s = match n {
+ 0 => "Success",
+ 1 => "Operation not permitted",
+ 2 => "No such file or directory",
+ 3 => "No such process",
+ 4 => "Interrupted system call",
+ 5 => "Input/output error",
+ 6 => "No such device or address",
+ 7 => "Argument list too long",
+ 8 => "Exec format error",
+ 9 => "Bad file descriptor",
+ 10 => "No child processes",
+ 11 => "Resource temporarily unavailable",
+ 12 => "Cannot allocate memory",
+ 13 => "Permission denied",
+ 14 => "Bad address",
+ 15 => "Block device required",
+ 16 => "Device or resource busy",
+ 17 => "File exists",
+ 18 => "Invalid cross-device link",
+ 19 => "No such device",
+ 20 => "Not a directory",
+ 21 => "Is a directory",
+ 22 => "Invalid argument",
+ 23 => "Too many open files in system",
+ 24 => "Too many open files",
+ 25 => "Inappropriate ioctl for device",
+ 26 => "Text file busy",
+ 27 => "File too large",
+ 28 => "No space left on device",
+ 29 => "Illegal seek",
+ 30 => "Read-only file system",
+ 31 => "Too many links",
+ 32 => "Broken pipe",
+ 33 => "Numerical argument out of domain",
+ 34 => "Numerical result out of range",
+ 35 => "Resource deadlock avoided",
+ 36 => "File name too long",
+ 37 => "No locks available",
+ 38 => "Function not implemented",
+ 39 => "Directory not empty",
+ 40 => "Too many levels of symbolic links",
+ 42 => "No message of desired type",
+ 43 => "Identifier removed",
+ 44 => "Channel number out of range",
+ 45 => "Level 2 not synchronized",
+ 46 => "Level 3 halted",
+ 47 => "Level 3 reset",
+ 48 => "Link number out of range",
+ 49 => "Protocol driver not attached",
+ 50 => "No CSI structure available",
+ 51 => "Level 2 halted",
+ 52 => "Invalid exchange",
+ 53 => "Invalid request descriptor",
+ 54 => "Exchange full",
+ 55 => "No anode",
+ 56 => "Invalid request code",
+ 57 => "Invalid slot",
+ 59 => "Bad font file format",
+ 60 => "Device not a stream",
+ 61 => "No data available",
+ 62 => "Timer expired",
+ 63 => "Out of streams resources",
+ 64 => "Machine is not on the network",
+ 65 => "Package not installed",
+ 66 => "Object is remote",
+ 67 => "Link has been severed",
+ 68 => "Advertise error",
+ 69 => "Srmount error",
+ 70 => "Communication error on send",
+ 71 => "Protocol error",
+ 72 => "Multihop attempted",
+ 73 => "RFS specific error",
+ 74 => "Bad message",
+ 75 => "Value too large for defined data type",
+ 76 => "Name not unique on network",
+ 77 => "File descriptor in bad state",
+ 78 => "Remote address changed",
+ 79 => "Can not access a needed shared library",
+ 80 => "Accessing a corrupted shared library",
+ 81 => ".lib section in a.out corrupted",
+ 82 => "Attempting to link in too many shared libraries",
+ 83 => "Cannot exec a shared library directly",
+ 84 => "Invalid or incomplete multibyte or wide character",
+ 85 => "Interrupted system call should be restarted",
+ 86 => "Streams pipe error",
+ 87 => "Too many users",
+ 88 => "Socket operation on non-socket",
+ 89 => "Destination address required",
+ 90 => "Message too long",
+ 91 => "Protocol wrong type for socket",
+ 92 => "Protocol not available",
+ 93 => "Protocol not supported",
+ 94 => "Socket type not supported",
+ 95 => "Operation not supported",
+ 96 => "Protocol family not supported",
+ 97 => "Address family not supported by protocol",
+ 98 => "Address already in use",
+ 99 => "Cannot assign requested address",
+ 100 => "Network is down",
+ 101 => "Network is unreachable",
+ 102 => "Network dropped connection on reset",
+ 103 => "Software caused connection abort",
+ 104 => "Connection reset by peer",
+ 105 => "No buffer space available",
+ 106 => "Transport endpoint is already connected",
+ 107 => "Transport endpoint is not connected",
+ 108 => "Cannot send after transport endpoint shutdown",
+ 109 => "Too many references: cannot splice",
+ 110 => "Connection timed out",
+ 111 => "Connection refused",
+ 112 => "Host is down",
+ 113 => "No route to host",
+ 114 => "Operation already in progress",
+ 115 => "Operation now in progress",
+ 116 => "Stale file handle",
+ 117 => "Structure needs cleaning",
+ 118 => "Not a XENIX named type file",
+ 119 => "No XENIX semaphores available",
+ 120 => "Is a named type file",
+ 121 => "Remote I/O error",
+ 122 => "Disk quota exceeded",
+ 123 => "No medium found",
+ 124 => "Wrong medium type",
+ 125 => "Operation canceled",
+ 126 => "Required key not available",
+ 127 => "Key has expired",
+ 128 => "Key has been revoked",
+ 129 => "Key was rejected by service",
+ 130 => "Owner died",
+ 131 => "State not recoverable",
+ 132 => "Operation not possible due to RF-kill",
+ 133 => "Memory page has hardware error",
+ _ => "Unknown errno value",
+ };
+
+ s.as_ptr().cast_mut().cast()
+}