mirror of
https://github.com/bitwarden/browser
synced 2026-02-07 12:13:45 +00:00
Add method to construct credential using COM allocator.
This commit is contained in:
@@ -289,3 +289,83 @@ pub(super) fn uninitialize() -> std::result::Result<(), WinWebAuthnError> {
|
||||
unsafe { CoUninitialize() };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(super) struct ComBuffer(NonNull<MaybeUninit<u8>>);
|
||||
|
||||
impl ComBuffer {
|
||||
/// Returns an COM-allocated buffer of `size`.
|
||||
fn alloc(size: usize, for_slice: bool) -> Self {
|
||||
#[expect(clippy::as_conversions)]
|
||||
{
|
||||
assert!(size <= isize::MAX as usize, "requested bad object size");
|
||||
}
|
||||
|
||||
// SAFETY: Any size is valid to pass to Windows, even `0`.
|
||||
let ptr = NonNull::new(unsafe { CoTaskMemAlloc(size) }).unwrap_or_else(|| {
|
||||
// XXX: This doesn't have to be correct, just close enough for an OK OOM error.
|
||||
let layout = alloc::Layout::from_size_align(size, align_of::<u8>()).unwrap();
|
||||
alloc::handle_alloc_error(layout)
|
||||
});
|
||||
|
||||
if for_slice {
|
||||
// Ininitialize the buffer so it can later be treated as `&mut [u8]`.
|
||||
// SAFETY: The pointer is valid and we are using a valid value for a byte-wise allocation.
|
||||
unsafe { ptr.write_bytes(0, size) };
|
||||
}
|
||||
|
||||
Self(ptr.cast())
|
||||
}
|
||||
|
||||
fn into_ptr<T>(self) -> *mut T {
|
||||
self.0.cast().as_ptr()
|
||||
}
|
||||
|
||||
/// Creates a new COM-allocated structure.
|
||||
///
|
||||
/// Note that `T` must be [Copy] to avoid any possible memory leaks.
|
||||
pub fn with_object<T: Copy>(object: T) -> *mut T {
|
||||
// NB: Vendored from Rust's alloc code since we can't yet allocate `Box` with a custom allocator.
|
||||
const MIN_ALIGN: usize = if cfg!(target_pointer_width = "64") {
|
||||
16
|
||||
} else if cfg!(target_pointer_width = "32") {
|
||||
8
|
||||
} else {
|
||||
panic!("unsupported arch")
|
||||
};
|
||||
|
||||
// SAFETY: Validate that our alignment works for a normal size-based allocation for soundness.
|
||||
let layout = const {
|
||||
let layout = alloc::Layout::new::<T>();
|
||||
assert!(layout.align() <= MIN_ALIGN);
|
||||
layout
|
||||
};
|
||||
|
||||
let buffer = Self::alloc(layout.size(), false);
|
||||
// SAFETY: `ptr` is valid for writes of `T` because we correctly allocated the right sized buffer that
|
||||
// accounts for any alignment requirements.
|
||||
//
|
||||
// Additionally, we ensure the value is treated as moved by forgetting the source.
|
||||
unsafe { buffer.0.cast::<T>().write(object) };
|
||||
buffer.into_ptr()
|
||||
}
|
||||
|
||||
pub fn from_buffer<T: AsRef<[u8]>>(buffer: T) -> (*mut u8, u32) {
|
||||
let buffer = buffer.as_ref();
|
||||
let len = buffer.len();
|
||||
let com_buffer = Self::alloc(len, true);
|
||||
|
||||
// SAFETY: `ptr` points to a valid allocation that `len` matches, and we made sure
|
||||
// the bytes were initialized. Additionally, bytes have no alignment requirements.
|
||||
unsafe {
|
||||
NonNull::slice_from_raw_parts(com_buffer.0.cast::<u8>(), len)
|
||||
.as_mut()
|
||||
.copy_from_slice(buffer)
|
||||
}
|
||||
|
||||
// Safety: The Windows API structures these buffers are placed into use `u32` (`DWORD`) to
|
||||
// represent length.
|
||||
#[expect(clippy::as_conversions)]
|
||||
(com_buffer.into_ptr(), len as u32)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,6 +645,34 @@ pub struct CredentialEx {
|
||||
inner: NonNull<WEBAUTHN_CREDENTIAL_EX>,
|
||||
}
|
||||
|
||||
impl CredentialEx {
|
||||
fn new_for_com(
|
||||
version: u32,
|
||||
id: &CredentialId,
|
||||
credential_type: &str,
|
||||
transports: &[CtapTransport],
|
||||
) -> Self {
|
||||
let (pwszCredentialType, _) = credential_type.to_com_utf16();
|
||||
let (pbId, cbId) = ComBuffer::from_buffer(&id);
|
||||
let ptr = unsafe {
|
||||
let mut uninit: MaybeUninit<WEBAUTHN_CREDENTIAL_EX> = MaybeUninit::uninit();
|
||||
let ptr = uninit.as_mut_ptr();
|
||||
std::ptr::write(
|
||||
ptr,
|
||||
WEBAUTHN_CREDENTIAL_EX {
|
||||
dwVersion: version,
|
||||
cbId,
|
||||
pbId,
|
||||
pwszCredentialType,
|
||||
dwTransports: transports.iter().map(|t| t.clone() as u32).sum(),
|
||||
},
|
||||
);
|
||||
NonNull::new_unchecked(ptr)
|
||||
};
|
||||
Self { inner: ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<WEBAUTHN_CREDENTIAL_EX> for CredentialEx {
|
||||
fn as_ref(&self) -> &WEBAUTHN_CREDENTIAL_EX {
|
||||
// SAFETY: We initialize memory manually in constructors.
|
||||
|
||||
@@ -6,7 +6,7 @@ use windows::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::win_webauthn::{ErrorKind, WinWebAuthnError};
|
||||
use crate::win_webauthn::{com::ComBuffer, ErrorKind, WinWebAuthnError};
|
||||
|
||||
pub(super) fn load_webauthn_lib() -> Result<HMODULE, WinWebAuthnError> {
|
||||
unsafe {
|
||||
@@ -30,6 +30,9 @@ pub(super) fn free_webauthn_lib(library: HMODULE) -> Result<(), WinWebAuthnError
|
||||
|
||||
pub(super) trait WindowsString {
|
||||
fn to_utf16(&self) -> Vec<u16>;
|
||||
|
||||
// Copies a string to a buffer from the OLE allocator
|
||||
fn to_com_utf16(&self) -> (*mut u16, u32);
|
||||
}
|
||||
|
||||
impl WindowsString for str {
|
||||
@@ -37,4 +40,14 @@ impl WindowsString for str {
|
||||
// null-terminated UTF-16
|
||||
self.encode_utf16().chain(std::iter::once(0)).collect()
|
||||
}
|
||||
|
||||
fn to_com_utf16(&self) -> (*mut u16, u32) {
|
||||
let wide_bytes: Vec<u8> = self
|
||||
.to_utf16()
|
||||
.into_iter()
|
||||
.flat_map(|x| x.to_le_bytes())
|
||||
.collect();
|
||||
let (ptr, byte_count) = ComBuffer::from_buffer(&wide_bytes);
|
||||
(ptr as *mut u16, byte_count)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user