Skip to content

Commit 8e3502a

Browse files
Introduce a mutex around the FFI calls to OpenSSL (#516)
This mutex is currently behind a new feature flag `openssl_ffi_mutex`, which is not enabled by default. OpenSSL code is not re-entrant; if tests run fast enough, that will cause unexpected behavior. Co-authored-by: Gavin Peacock <[email protected]>
1 parent 5f6121e commit 8e3502a

File tree

12 files changed

+154
-20
lines changed

12 files changed

+154
-20
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ The Rust library crate provides:
9292
* `fetch_remote_manifests` enables the verification step to retrieve externally referenced manifest stores. External manifests are only fetched if there is no embedded manifest store and no locally adjacent .c2pa manifest store file of the same name.
9393
* `json_schema` is used by `make schema` to produce a JSON schema document that represents the `ManifestStore` data structures.
9494
* `psxxx_ocsp_stapling_experimental` this is an demonstration feature that will attempt to fetch the OCSP data from the OCSP responders listed in the manifest signing certificate. The response becomes part of the manifest and is used to prove the certificate was not revoked at the time of signing. This is only implemented for PS256, PS384 and PS512 signatures and is intended as a demonstration.
95-
95+
* `openssl_ffi_mutex` prevents multiple threads from accessing the C OpenSSL library simultaneously. (This library is not re-entrant.) In a multi-threaded process (such as Cargo's test runner), this can lead to unpredictable behavior.
9696

9797
## Example code
9898

sdk/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ json_schema = ["dep:schemars"]
3737
pdf = ["dep:lopdf"]
3838
v1_api = []
3939
unstable_api = []
40+
openssl_ffi_mutex = []
4041

4142
# The diagnostics feature is unsupported and might be removed.
4243
# It enables some low-overhead timing features used in our development cycle.

sdk/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@ pub enum Error {
281281
#[error(transparent)]
282282
CborError(#[from] serde_cbor::Error),
283283

284+
#[error("could not acquire OpenSSL FFI mutex")]
285+
OpenSslMutexError,
286+
284287
#[error(transparent)]
285288
#[cfg(feature = "openssl")]
286289
OpenSslError(#[from] openssl::error::ErrorStack),

sdk/src/openssl/ec_signer.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ impl ConfigurableSigner for EcSigner {
4949
alg: SigningAlg,
5050
tsa_url: Option<String>,
5151
) -> Result<Self> {
52+
let _openssl = super::OpenSslMutex::acquire()?;
53+
5254
let certs_size = signcert.len();
5355
let pkey = EcKey::private_key_from_pem(pkey).map_err(Error::OpenSslError)?;
5456
let signcerts = X509::stack_from_pem(signcert).map_err(Error::OpenSslError)?;
@@ -73,6 +75,8 @@ impl ConfigurableSigner for EcSigner {
7375

7476
impl Signer for EcSigner {
7577
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
78+
let _openssl = super::OpenSslMutex::acquire()?;
79+
7680
let key = PKey::from_ec_key(self.pkey.clone()).map_err(Error::OpenSslError)?;
7781

7882
let mut signer = match self.alg {
@@ -93,6 +97,8 @@ impl Signer for EcSigner {
9397
}
9498

9599
fn certs(&self) -> Result<Vec<Vec<u8>>> {
100+
let _openssl = super::OpenSslMutex::acquire()?;
101+
96102
let mut certs: Vec<Vec<u8>> = Vec::new();
97103

98104
for c in &self.signcerts {
@@ -120,6 +126,10 @@ struct ECSigComps<'a> {
120126
}
121127

122128
fn parse_ec_sig(data: &[u8]) -> der_parser::error::BerResult<ECSigComps> {
129+
// IMPORTANT: OpenSslMutex::acquire() should have been called by calling fn.
130+
// Please don't make this pub or pub(crate) without finding a way to ensure
131+
// that precondition.
132+
123133
parse_der_sequence_defined_g(|content: &[u8], _| {
124134
let (rem1, r) = parse_der_integer(content)?;
125135
let (_rem2, s) = parse_der_integer(rem1)?;
@@ -135,6 +145,10 @@ fn parse_ec_sig(data: &[u8]) -> der_parser::error::BerResult<ECSigComps> {
135145
}
136146

137147
fn der_to_p1363(data: &[u8], alg: SigningAlg) -> Result<Vec<u8>> {
148+
// IMPORTANT: OpenSslMutex::acquire() should have been called by calling fn.
149+
// Please don't make this pub or pub(crate) without finding a way to ensure
150+
// that precondition.
151+
138152
// P1363 format: r | s
139153

140154
let (_, p) = parse_ec_sig(data).map_err(|_err| Error::InvalidEcdsaSignature)?;

sdk/src/openssl/ec_validator.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ impl EcValidator {
2727

2828
impl CoseValidator for EcValidator {
2929
fn validate(&self, sig: &[u8], data: &[u8], pkey: &[u8]) -> Result<bool> {
30+
let _openssl = super::OpenSslMutex::acquire()?;
31+
3032
let public_key = EcKey::public_key_from_der(pkey).map_err(|_err| Error::CoseSignature)?;
3133
let key = PKey::from_ec_key(public_key).map_err(wrap_openssl_err)?;
3234

sdk/src/openssl/ed_signer.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ impl ConfigurableSigner for EdSigner {
3939
alg: SigningAlg,
4040
tsa_url: Option<String>,
4141
) -> Result<Self> {
42+
let _openssl = super::OpenSslMutex::acquire()?;
43+
4244
let certs_size = signcert.len();
4345
let signcerts = X509::stack_from_pem(signcert).map_err(Error::OpenSslError)?;
4446
let pkey = PKey::private_key_from_pem(pkey).map_err(Error::OpenSslError)?;
@@ -67,6 +69,8 @@ impl ConfigurableSigner for EdSigner {
6769

6870
impl Signer for EdSigner {
6971
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
72+
let _openssl = super::OpenSslMutex::acquire()?;
73+
7074
let mut signer =
7175
openssl::sign::Signer::new_without_digest(&self.pkey).map_err(Error::OpenSslError)?;
7276

@@ -80,6 +84,8 @@ impl Signer for EdSigner {
8084
}
8185

8286
fn certs(&self) -> Result<Vec<Vec<u8>>> {
87+
let _openssl = super::OpenSslMutex::acquire()?;
88+
8389
let mut certs: Vec<Vec<u8>> = Vec::new();
8490

8591
for c in &self.signcerts {

sdk/src/openssl/ed_validator.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ impl EdValidator {
2727

2828
impl CoseValidator for EdValidator {
2929
fn validate(&self, sig: &[u8], data: &[u8], pkey: &[u8]) -> Result<bool> {
30+
let _openssl = super::OpenSslMutex::acquire()?;
31+
3032
let public_key = PKey::public_key_from_der(pkey).map_err(|_err| Error::CoseSignature)?;
3133

3234
let mut verifier = openssl::sign::Verifier::new_without_digest(&public_key)

sdk/src/openssl/ffi_mutex.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2024 Adobe. All rights reserved.
2+
// This file is licensed to you under the Apache License,
3+
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
4+
// or the MIT license (http://opensource.org/licenses/MIT),
5+
// at your option.
6+
7+
// Unless required by applicable law or agreed to in writing,
8+
// this software is distributed on an "AS IS" BASIS, WITHOUT
9+
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
10+
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
11+
// specific language governing permissions and limitations under
12+
// each license.
13+
14+
// OpenSSL code is not re-entrant. Use this to guard against race conditions.
15+
use crate::Result;
16+
17+
#[cfg(feature = "openssl_ffi_mutex")]
18+
static FFI_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
19+
20+
pub(crate) struct OpenSslMutex<'a> {
21+
// Dead code here is intentional. We don't need to read the () contents
22+
// of this guard. We only need to ensure that the guard is dropped when
23+
// this struct is dropped.
24+
#[allow(dead_code)]
25+
#[cfg(feature = "openssl_ffi_mutex")]
26+
guard: std::sync::MutexGuard<'a, ()>,
27+
28+
#[allow(dead_code)]
29+
#[cfg(not(feature = "openssl_ffi_mutex"))]
30+
guard: &'a str,
31+
}
32+
33+
impl<'a> OpenSslMutex<'a> {
34+
/// Acquire a mutex on OpenSSL FFI code.
35+
///
36+
/// WARNING: Calling code MUST NOT PANIC inside this function or
37+
/// anything called by it, even in test code. This will poison the FFI mutex
38+
/// and leave OpenSSL unusable for the remainder of the process lifetime.
39+
pub(crate) fn acquire() -> Result<Self> {
40+
// Useful for debugging.
41+
// eprintln!(
42+
// "ACQUIRING FFI MUTEX at\n{}",
43+
// std::backtrace::Backtrace::force_capture()
44+
// );
45+
46+
#[cfg(feature = "openssl_ffi_mutex")]
47+
match FFI_MUTEX.lock() {
48+
Ok(guard) => Ok(Self { guard }),
49+
Err(_) => Err(crate::Error::OpenSslMutexError),
50+
}
51+
52+
#[cfg(not(feature = "openssl_ffi_mutex"))]
53+
Ok(Self { guard: &"foo" })
54+
}
55+
}
56+
57+
// Useful for debugging.
58+
// impl<'a> Drop for OpenSslMutex<'a> {
59+
// fn drop(&mut self) {
60+
// eprintln!("Releasing FFI mutex\n\n\n");
61+
// }
62+
// }

sdk/src/openssl/mod.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ pub(crate) use openssl_trust_handler::verify_trust;
5151
#[cfg(feature = "openssl")]
5252
pub(crate) use openssl_trust_handler::OpenSSLTrustHandlerConfig;
5353

54+
mod ffi_mutex;
55+
pub(crate) use ffi_mutex::OpenSslMutex;
56+
5457
#[cfg(test)]
5558
pub(crate) mod temp_signer_async;
5659

@@ -60,8 +63,13 @@ use openssl::x509::X509;
6063
#[allow(unused_imports)]
6164
#[cfg(feature = "openssl")]
6265
pub(crate) use temp_signer_async::AsyncSignerAdapter;
66+
6367
#[cfg(feature = "openssl")]
64-
pub(crate) fn check_chain_order(certs: &[X509]) -> bool {
68+
fn check_chain_order(certs: &[X509]) -> bool {
69+
// IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please
70+
// don't make this pub or pub(crate) without finding a way to ensure that
71+
// precondition.
72+
6573
{
6674
if certs.len() > 1 {
6775
for (i, c) in certs.iter().enumerate() {
@@ -85,13 +93,17 @@ pub(crate) fn check_chain_order(certs: &[X509]) -> bool {
8593
}
8694

8795
#[cfg(not(feature = "openssl"))]
88-
pub(crate) fn check_chain_order(certs: &[X509]) -> bool {
96+
fn check_chain_order(certs: &[X509]) -> bool {
8997
true
9098
}
9199

92100
#[cfg(feature = "openssl")]
93101
#[allow(dead_code)]
94-
pub(crate) fn check_chain_order_der(cert_ders: &[Vec<u8>]) -> bool {
102+
fn check_chain_order_der(cert_ders: &[Vec<u8>]) -> bool {
103+
// IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please
104+
// don't make this pub or pub(crate) without finding a way to ensure that
105+
// precondition.
106+
95107
let mut certs: Vec<X509> = Vec::new();
96108
for cert_der in cert_ders {
97109
if let Ok(cert) = X509::from_der(cert_der) {
@@ -105,6 +117,6 @@ pub(crate) fn check_chain_order_der(cert_ders: &[Vec<u8>]) -> bool {
105117
}
106118

107119
#[cfg(not(feature = "openssl"))]
108-
pub(crate) fn check_chain_order_der(cert_ders: &[Vec<u8>]) -> bool {
120+
fn check_chain_order_der(cert_ders: &[Vec<u8>]) -> bool {
109121
true
110122
}

sdk/src/openssl/openssl_trust_handler.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ use crate::{
2727
};
2828

2929
fn certs_der_to_x509(ders: &[Vec<u8>]) -> Result<Vec<openssl::x509::X509>> {
30+
// IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please
31+
// don't make this pub or pub(crate) without finding a way to ensure that
32+
// precondition.
33+
3034
let mut certs: Vec<openssl::x509::X509> = Vec::new();
3135

3236
for d in ders {
@@ -38,6 +42,7 @@ fn certs_der_to_x509(ders: &[Vec<u8>]) -> Result<Vec<openssl::x509::X509>> {
3842
}
3943

4044
fn load_trust_from_pem_data(trust_data: &[u8]) -> Result<Vec<openssl::x509::X509>> {
45+
let _openssl = super::OpenSslMutex::acquire()?;
4146
openssl::x509::X509::stack_from_pem(trust_data).map_err(Error::OpenSslError)
4247
}
4348

@@ -69,6 +74,8 @@ impl OpenSSLTrustHandlerConfig {
6974
}
7075

7176
fn update_store(&mut self) -> Result<()> {
77+
let _openssl = super::OpenSslMutex::acquire()?;
78+
7279
let mut builder =
7380
openssl::x509::store::X509StoreBuilder::new().map_err(Error::OpenSslError)?;
7481

@@ -134,13 +141,16 @@ impl TrustHandlerConfig for OpenSSLTrustHandlerConfig {
134141
let mut buffer = Vec::new();
135142
allowed_list.read_to_end(&mut buffer)?;
136143

137-
if let Ok(cert_list) = openssl::x509::X509::stack_from_pem(&buffer) {
138-
for cert in &cert_list {
139-
let cert_der = cert.to_der().map_err(Error::OpenSslError)?;
140-
let cert_sha256 = hash_sha256(&cert_der);
141-
let cert_hash_base64 = base64::encode(&cert_sha256);
144+
{
145+
let _openssl = super::OpenSslMutex::acquire()?;
146+
if let Ok(cert_list) = openssl::x509::X509::stack_from_pem(&buffer) {
147+
for cert in &cert_list {
148+
let cert_der = cert.to_der().map_err(Error::OpenSslError)?;
149+
let cert_sha256 = hash_sha256(&cert_der);
150+
let cert_hash_base64 = base64::encode(&cert_sha256);
142151

143-
self.allowed_cert_set.insert(cert_hash_base64);
152+
self.allowed_cert_set.insert(cert_hash_base64);
153+
}
144154
}
145155
}
146156

@@ -237,6 +247,8 @@ pub(crate) fn verify_trust(
237247
return Ok(true);
238248
}
239249

250+
let _openssl = super::OpenSslMutex::acquire()?;
251+
240252
let mut cert_chain = openssl::stack::Stack::new().map_err(Error::OpenSslError)?;
241253
let mut store_ctx = openssl::x509::X509StoreContext::new().map_err(Error::OpenSslError)?;
242254

@@ -266,6 +278,7 @@ pub(crate) fn verify_trust(
266278
Err(_) => Ok(false),
267279
}
268280
}
281+
269282
#[cfg(test)]
270283
pub mod tests {
271284
#![allow(clippy::expect_used)]

sdk/src/openssl/rsa_signer.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ impl RsaSigner {
4444
// production use since there is no caching in the SDK and fetching is expensive. This is behind the
4545
// feature flag 'psxxx_ocsp_stapling_experimental'
4646
fn update_ocsp(&self) {
47+
// IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please
48+
// don't make this pub or pub(crate) without finding a way to ensure that
49+
// precondition.
50+
4751
// do we need an update
4852
let now = chrono::offset::Utc::now();
4953

@@ -54,7 +58,7 @@ impl RsaSigner {
5458
if now > next_update {
5559
#[cfg(feature = "psxxx_ocsp_stapling_experimental")]
5660
{
57-
if let Ok(certs) = self.certs() {
61+
if let Ok(certs) = self.certs_internal() {
5862
if let Some(ocsp_rsp) = crate::ocsp_utils::fetch_ocsp_response(&certs) {
5963
self.ocsp_size.set(ocsp_rsp.len());
6064
let mut validation_log =
@@ -71,6 +75,21 @@ impl RsaSigner {
7175
}
7276
}
7377
}
78+
79+
fn certs_internal(&self) -> Result<Vec<Vec<u8>>> {
80+
// IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please
81+
// don't make this pub or pub(crate) without finding a way to ensure that
82+
// precondition.
83+
84+
let mut certs: Vec<Vec<u8>> = Vec::new();
85+
86+
for c in &self.signcerts {
87+
let cert = c.to_der().map_err(wrap_openssl_err)?;
88+
certs.push(cert);
89+
}
90+
91+
Ok(certs)
92+
}
7493
}
7594

7695
impl ConfigurableSigner for RsaSigner {
@@ -80,6 +99,8 @@ impl ConfigurableSigner for RsaSigner {
8099
alg: SigningAlg,
81100
tsa_url: Option<String>,
82101
) -> Result<Self> {
102+
let _openssl = super::OpenSslMutex::acquire()?;
103+
83104
let signcerts = X509::stack_from_pem(signcert).map_err(wrap_openssl_err)?;
84105
let rsa = Rsa::private_key_from_pem(pkey).map_err(wrap_openssl_err)?;
85106

@@ -192,14 +213,8 @@ impl Signer for RsaSigner {
192213
}
193214

194215
fn certs(&self) -> Result<Vec<Vec<u8>>> {
195-
let mut certs: Vec<Vec<u8>> = Vec::new();
196-
197-
for c in &self.signcerts {
198-
let cert = c.to_der().map_err(wrap_openssl_err)?;
199-
certs.push(cert);
200-
}
201-
202-
Ok(certs)
216+
let _openssl = super::OpenSslMutex::acquire()?;
217+
self.certs_internal()
203218
}
204219

205220
fn alg(&self) -> SigningAlg {
@@ -211,6 +226,8 @@ impl Signer for RsaSigner {
211226
}
212227

213228
fn ocsp_val(&self) -> Option<Vec<u8>> {
229+
let _openssl = super::OpenSslMutex::acquire().ok()?;
230+
214231
// update OCSP if needed
215232
self.update_ocsp();
216233

sdk/src/openssl/rsa_validator.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ impl RsaValidator {
2727

2828
impl CoseValidator for RsaValidator {
2929
fn validate(&self, sig: &[u8], data: &[u8], pkey: &[u8]) -> Result<bool> {
30+
let _openssl = super::OpenSslMutex::acquire()?;
31+
3032
let rsa = Rsa::public_key_from_der(pkey)?;
3133

3234
// rebuild RSA keys to eliminate incompatible values

0 commit comments

Comments
 (0)