FFI, Pointers and Structs

I am relatively new to Rust and trying to get a clean, lint-free version of a project working that I ported from C/C++. I managed to get everything ported, compiled and lightly tested on my machine. Before performing additional refactoring I wanted to try out fmt and clippy in order to see how well I did. While working through all of the issues I have come across one that has spiraled a bit and I want to see what I am missing and get advice from more experienced Rust users.

The issue I am dealing are a several pieces of code working with the Windows API with an idiom of where one calls a function twice. The first time requests the size of the memory for a struct (or some other blob of bytes) which will be the output parameter holding the result after the second call. In order to allocate this memory I have been using Vec<u8> and passing in a pointer to the memory through as_mut_ptr() and in some cases I need to cast to the type pointer and others it will be a pointer to a void* in yet others a pointer to a DWORD (Windows APIs are not particularly consistent). Depending on the cast this will trigger the cast_ptr_alignment lint. In some cases, after verifying the call succeeded I was trying to cast the Vec<u8> memory array through as_ptr() to the struct type and interact with the members of the struct like I would any other struct in rust. In others, I am just dumping the memory out to a file. I mention this as I am uncertain if each of these scenarios leads to different solutions or not.

Here's a portion of one piece of code that issued the lint, it won't compile on it's own but hopefully this is enough to give an idea

        //-------------------------------------------------------------------
        // Call CryptExportPublicKeyInfo to get the size of the returned
        // information.
        let mut public_key_info_size: DWORD = 0;
        if unsafe {
            wincrypt::CryptExportPublicKeyInfo(
                key.handle,                              // Provider handle
                AT_SIGNATURE,                            // Key spec
                PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, // Encoding type
                ptr::null_mut(),                         // pbPublicKeyInfo
                &mut public_key_info_size,
            )
        } == 0
        {
            // Size of PublicKeyInfo
            let last_error = unsafe { errhandlingapi::GetLastError() };
            return crypt_error!(last_error, "Error exporting public key info");
        }
        info!("The keyinfo structure is {} bytes.", public_key_info_size);
        // set-up the blob
        let mut public_key_blob: Vec<u8> = Vec::with_capacity(public_key_info_size as usize);
        //-------------------------------------------------------------------
        // Call CryptExportPublicKeyInfo to get pbPublicKeyInfo.
        unsafe {
            if wincrypt::CryptExportPublicKeyInfo(
                key.handle,
                AT_SIGNATURE,
                PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
                public_key_blob.as_mut_ptr() as PCERT_PUBLIC_KEY_INFO,
                &mut public_key_info_size,
            ) == 0
            {
                let last_error = errhandlingapi::GetLastError();
                return crypt_error!(last_error, "Error exporting public key info");
            }
            public_key_blob.set_len(public_key_info_size as usize);
        }
        info!("The public key info has been exported.");

The lint is on this line:

public_key_blob.as_mut_ptr() as PCERT_PUBLIC_KEY_INFO,

with error message:

error: casting from `*mut u8` to a more-strictly-aligned pointer (`*mut util::winapi::um::wincrypt::CERT_PUBLIC_KEY_INFO`) (1 < 8 bytes)

Based on the lints, I understand the code I have now is incorrect (or unsound or both). But what is the proper way of doing this? Do I use ptr:read_unaligned() for the case I need to read from a field of the struct? What is the correct way of handling the situation above? Do I need to allocate the memory in some other way? I had hit upon Vec since it seemed to work with a contiguous array of bytes which seemed to implement the semantics I needed, but based on the documentation I have read it appears that what I am doing is not correct/sound Rust.

I'm not sure what the commonly accepted solution is, but you could make a vector of u64 instead, which would guarantee an alignment of 8.

You can find the length by dividing and round up using (public_key_info_size + 7) / 8.

1 Like

Your suggestion put me on the right track I think.

I ended up with:

        let struct_size = mem::size_of::<CERT_PUBLIC_KEY_INFO>();
        let blob_blocks : usize = (public_key_info_size as usize + struct_size - 1) / struct_size;
        let mut public_key_blob: Vec<CERT_PUBLIC_KEY_INFO> = Vec::with_capacity(blob_blocks);

which changed the original code above to:

        // Call CryptExportPublicKeyInfo to get the size of the returned
        // information.
        let mut public_key_info_size: DWORD = 0;
        if unsafe {
            wincrypt::CryptExportPublicKeyInfo(
                key.handle,                              // Provider handle
                AT_SIGNATURE,                            // Key spec
                PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, // Encoding type
                ptr::null_mut(),                         // pbPublicKeyInfo
                &mut public_key_info_size,
            )
        } == 0
        {
            // Size of PublicKeyInfo
            let last_error = unsafe { errhandlingapi::GetLastError() };
            return crypt_error!(last_error, "Error exporting public key info");
        }
        info!("The keyinfo structure is {} bytes.", public_key_info_size);
        // set-up the blob
        let struct_size = mem::size_of::<CERT_PUBLIC_KEY_INFO>();
        let blob_blocks : usize = (public_key_info_size as usize + struct_size - 1) / struct_size;
        let mut public_key_blob: Vec<CERT_PUBLIC_KEY_INFO> = Vec::with_capacity(blob_blocks);
        //-------------------------------------------------------------------
        // Call CryptExportPublicKeyInfo to get pbPublicKeyInfo.
        unsafe {
            if wincrypt::CryptExportPublicKeyInfo(
                key.handle,
                AT_SIGNATURE,
                PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
                public_key_blob.as_mut_ptr(),
                &mut public_key_info_size,
            ) == 0
            {
                let last_error = errhandlingapi::GetLastError();
                return crypt_error!(last_error, "Error exporting public key info");
            }
            public_key_blob.set_len(blob_blocks as usize);
        }

This approach feels most correct to me and doesn't exhibit the problem in the original code. It also had the benefit in reducing some of the casts I was making and even allowed me to remove an unsafe code block.

A change from before was that I needed to call align_to on the buffer when accessing as just bytes in at least one of the use cases so something like:

public_key_blob.align_to::<u8>().1

was very useful.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.