1
0
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:
Isaiah Inuwa
2025-11-19 23:57:36 -06:00
parent a5ccb5e25d
commit f8e8d5fd3f
3 changed files with 122 additions and 1 deletions

View File

@@ -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)
}
}

View File

@@ -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.

View File

@@ -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)
}
}