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()
+}