rust-security-framework icon indicating copy to clipboard operation
rust-security-framework copied to clipboard

Implement old version fallback using `dlsym`

Open madsmtm opened this issue 9 months ago • 3 comments

rustls-platform-verifier (nowadays used in major Rust projects like Rustup) uses security-framework, and enables the "OSX_10_14" Cargo feature.

As such, trying to run rustup update on a machine with macOS 10.12 installed (which the Rust project otherwise still supports) results in the following dynamic linker error:

info: syncing channel updates for 'stable-x86_64-apple-darwin'
dyld: lazy symbol binding failed: Symbol not found: _SecTrustEvaluateWithError
  Referenced from: /Users/madsmtm/.cargo/bin/rustup
  Expected in: /System/Library/Frameworks/Security.framework/Versions/A/Security

dyld: Symbol not found: _SecTrustEvaluateWithError
  Referenced from: /Users/madsmtm/.cargo/bin/rustup
  Expected in: /System/Library/Frameworks/Security.framework/Versions/A/Security

Abort trap: 6

And trying to compile it manually results in a static linker error:

error: linking with `cc` failed: exit status: 1
  |
  = note: "cc" "$WORKSPACE/*.o" "$WORKSPACE/*.rlib" "-framework" "Security" "-liconv" "-framework" "CoreFoundation" "-lSystem" "-lc" "-lm" "-arch" "x86_64" "-mmacosx-version-min=10.12.0" "-o" "$WORKSPACE/target/debug/deps/rustls_platform_verifier-f3c84624becba9b5" "-Wl,-dead_strip" "-nodefaultlibs"
  = note: Undefined symbols for architecture x86_64:
            "_SecTrustEvaluateWithError", referenced from:
                security_framework::trust::SecTrust::evaluate_with_error::hb6ce8f9ed18e6bcb in libsecurity_framework-063e6ee960d31664.rlib(security_framework-063e6ee960d31664.security_framework.d276614c73a19b26-cgu.10.rcgu.o)
          ld: symbol(s) not found for architecture x86_64
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

error: could not compile `rustls-platform-verifier` (lib test) due to 1 previous error

This could of course be "fixed" in rustls-platform-verifier by simply not enabling the feature at all, but there's (probably) a reason why they've chosen to do so. Ideally, we should be able to use SecTrustEvaluateWithError when available, and only need to fall back to SecTrustEvaluate when not available.

Weak linking offers such a mechanism, though it has been unstable for years with difficult blockers, so another solution would be to look the symbol up dynamically using dlsym. Something like the following would work:

let fnptr = dlsym(RTLD_DEFAULT, c"SecTrustEvaluateWithError".as_ptr());
let fnptr = mem::transmute<*const c_void, Option<unsafe extern "C" fn(SecTrustRef, *mut CFErrorRef) -> bool>>();
if let Some(fnptr) = fnptr {
    // SecTrustEvaluateWithError available
    fnptr(...)
} else {
    // Not available, fall back to SecTrustEvaluate
}

madsmtm avatar Apr 07 '25 19:04 madsmtm

Does the library work on 10.12 without the OSX_10_14 feature?

It seems to me that if 10.12 support is required, then they shouldn't be configuring the library to require 10.14 as the minimum OS version. I get that Cargo features are a pain, but that's the interface we have to work with.

I could add some compatibility workarounds for older OSes when the OSX_10_14 feature is disabled. Having hacks for older OSes when the "I don't need the hacks for older OSes" feature is enabled goes against its purpose.

kornelski avatar Apr 08 '25 13:04 kornelski

Does the library work on 10.12 without the OSX_10_14 feature?

The tests do not pass because they expect specific error messages on cert validation failure (which SecTrustEvaluate won't give them). Too unfamiliar with rustls-platform-verifier to tell for certain, but I'm fairly sure it isn't required.

I could add some compatibility workarounds for older OSes when the OSX_10_14 feature is disabled. Having hacks for older OSes when the "I don't need the hacks for older OSes" feature is enabled goes against its purpose.

That's what I was arguing for, yeah. The problem with disabling the "OSX_10_14" feature right now is that it worsens the experience for users on macOS 10.14 or above.

To be clear, the ideal solution to me would be something like the following:

#[cfg(feature = "OSX_10_14")]
{
    SecTrustEvaluateWithError(...); // Use directly
}
#[cfg(not(feature = "OSX_10_14"))]
{
    let fnptr = dlsym(RTLD_DEFAULT, c"SecTrustEvaluateWithError".as_ptr());
    // ...
}

madsmtm avatar Apr 08 '25 13:04 madsmtm

(FYI, there's RFC 3750 for adding something like #[cfg(feature = "OSX_10_14")] built-in to the language, to allow setting this automatically with the MACOSX_DEPLOYMENT_TARGET env var instead of mucking around with Cargo features).

madsmtm avatar Apr 08 '25 13:04 madsmtm