windows-rs: Wrong HRESULT error code if the call was made from a different facility

Summary

The GetLastError calls internally the HRESULT::from_win32 which set the facility bits of the HRESULT to 7 aka win32. However the facility is not always win32.

In this example I call the GetLastError however the same holds true for functions that already return the windows::core::Result like Cryptography::CryptAcquireCertificatePrivateKey I provided a simple example that should look for a fingerprint in the CurrentUser/Personal Cert store if the fingerprint exists. The is maybe not the simplest version of the bug but hopefully verbose enough.

Easy solutions would be a way to specify the facility on the GetLastError call or provide different windows::core::Error::from_ implementations for each facility. E.g windows::core::Error::from_sspi

Crate manifest

[package]
name = "hresult_example"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
hex = "0.4"
windows = { version = "0.51", features = [
    "Win32_Security_Cryptography",
    "Win32_Foundation",
] }

Crate code

use hex::FromHex;
use windows::Win32::Security::Cryptography;

// looks for a fingerprint in CurrentUser\My (Personal)
fn main() {
    let path_as_wstr = "My"
        .encode_utf16()
        .chain(Some(0))
        .collect::<Vec<_>>();
    let dwflags = Cryptography::CERT_OPEN_STORE_FLAGS(Cryptography::CERT_SYSTEM_STORE_CURRENT_USER_ID << Cryptography::CERT_SYSTEM_STORE_LOCATION_SHIFT);
    let handle = unsafe {
        Cryptography::CertOpenStore(
            Cryptography::CERT_STORE_PROV_SYSTEM_W,
            Cryptography::CERT_QUERY_ENCODING_TYPE::default(),
            Cryptography::HCRYPTPROV_LEGACY::default(),
            dwflags,
            Some(path_as_wstr.as_ptr() as _),
        )
    }.unwrap();

    let hash = <[u8; 20]>::from_hex(&"ABF777B90E01AFFDC21BCCA4BB98D7D884DF702E").unwrap();
    let fingerprint_blob = Cryptography::CRYPT_INTEGER_BLOB {
        cbData: hash.len() as _,
        pbData: hash.as_ptr() as _,
    };

    let cert_context = unsafe {
        Cryptography::CertFindCertificateInStore(
            handle,
            Cryptography::X509_ASN_ENCODING,
            0,
            Cryptography::CERT_FIND_HASH,
            Some(&fingerprint_blob as *const _ as _),
            None,
        )
    };
    assert_eq!(cert_context.is_null(), true, "the fingerprint should not be in the store");

    // wrong facility as the error is from SSPI not win32
    // let error = windows::core::Error::from_win32();
    // this is the same as it internally calls the from_win32() function
    let error = unsafe { windows::Win32::Foundation::GetLastError() }.unwrap_err();
    match error {
        // HACK: HRESULT conversion is wrong for crypto error codes
        // HACK: Add 2 to the facility
        e if e.code().0 + 0x0002_0000
            == windows::Win32::Foundation::CRYPT_E_NOT_FOUND.0 =>
        {
            println!("error codes match");
        }
        _ => {
            eprintln!("error codes did not match");
        },
    };

}

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 21

Most upvoted comments

We can potentially improve the situation for option 2 by updating windows::core::Error to store either HRESULT or u32 so that it still uses the same error type but in a lossless manner and thereby supporting error propagation seamlessly.

More seriously, the (HRESULT)(x) <= 0 in the macro exists so that if GetLastError actually returns an HRESULT - as in the case of CertFindCertificateInStore - rather than a traditional Win32 error code, then the severity bit will be detected here as a negative long value and not reencoded. We should probably just do the same careful dance and call it a day.

I’m starting to think “returns whatever” is a fair description of GetLastError 😆

Oh that’s just a tag for internal housekeeping. This is not strictly a crate bug but rather an external one.

I think we have all the info we need on this, it’s just going to require a bit of work upstream in the metadata project. We’ll ping this thread when there’s more to report!

Right, the problem is again that the Win32 metadata assumes GetLastError returns an invented WIN32_ERROR type rather than just DWORD. So then when you ask the resulting WIN32_ERROR to convert to an HRESULT it naturally assumes the Win32 facility.