diff --git a/src/libp11-int.h b/src/libp11-int.h index 8a18121c..288f5204 100644 --- a/src/libp11-int.h +++ b/src/libp11-int.h @@ -94,6 +94,8 @@ struct pkcs11_slot_private { pthread_mutex_t lock; pthread_cond_t cond; int8_t rw_mode, logged_in; + int transition_active; /* session-pool transition active */ + unsigned int sessions_in_use; /* sessions currently checked out */ CK_SLOT_ID id; CK_SESSION_HANDLE *session_pool; unsigned int session_head, session_tail, session_poolsize; @@ -279,14 +281,20 @@ extern void pkcs11_CTX_unload(PKCS11_CTX *ctx); /* Free a libp11 context */ extern void pkcs11_CTX_free(PKCS11_CTX *ctx); -/* Open a session in RO or RW mode */ -extern int pkcs11_open_session(PKCS11_SLOT_private *, int rw); +/* Set the R/O or R/W mode of the session pool */ +extern int pkcs11_session_pool_set_mode(PKCS11_SLOT_private *, int rw); -/* Acquire a session from the slot specific session pool */ -extern int pkcs11_get_session(PKCS11_SLOT_private *, int rw, CK_SESSION_HANDLE *sessionp); +/* Acquire a session from the slot-specific session pool */ +extern int pkcs11_session_pool_acquire(PKCS11_SLOT_private *, int rw, + CK_SESSION_HANDLE *sessionp); -/* Return a session the the slot specific session pool */ -extern void pkcs11_put_session(PKCS11_SLOT_private *, CK_SESSION_HANDLE session); +/* Switch to R/W mode, log in again if needed, and acquire a session */ +extern int pkcs11_session_pool_acquire_keygen(PKCS11_SLOT_private *, + CK_SESSION_HANDLE *sessionp); + +/* Release a session back to the slot-specific session pool */ +extern void pkcs11_session_pool_release(PKCS11_SLOT_private *, + CK_SESSION_HANDLE session); /* Get a list of all slots */ extern int pkcs11_enumerate_slots(PKCS11_CTX_private *ctx, diff --git a/src/p11_cert.c b/src/p11_cert.c index 6ece01a1..98675736 100644 --- a/src/p11_cert.c +++ b/src/p11_cert.c @@ -50,11 +50,11 @@ int pkcs11_enumerate_certs(PKCS11_SLOT_private *slot, const PKCS11_CERT *cert_te pkcs11_addattr_s(&tmpl, CKA_LABEL, cert_template->label); } - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; rv = pkcs11_find_certs(slot, &tmpl, session); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); if (rv < 0) { pkcs11_destroy_certs(slot); return -1; @@ -211,7 +211,7 @@ int pkcs11_store_certificate(PKCS11_SLOT_private *slot, X509 *x509, char *label, CK_MECHANISM_TYPE ckm_md; /* First, make sure we have a session */ - if (pkcs11_get_session(slot, 1, &session)) + if (pkcs11_session_pool_acquire(slot, 1, &session)) return -1; /* Now build the template */ @@ -295,7 +295,7 @@ int pkcs11_store_certificate(PKCS11_SLOT_private *slot, X509 *x509, char *label, if (rv == CKR_OK) { r = pkcs11_init_cert(slot, session, object, ret_cert); } - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); CRYPTOKI_checkerr(CKR_F_PKCS11_STORE_CERTIFICATE, rv); return r; diff --git a/src/p11_ckr.c b/src/p11_ckr.c index 68440e25..6bf2e679 100644 --- a/src/p11_ckr.c +++ b/src/p11_ckr.c @@ -48,7 +48,7 @@ static ERR_STRING_DATA CKR_str_functs[] = { {ERR_FUNC(CKR_F_PKCS11_LOGOUT), "pkcs11_logout"}, {ERR_FUNC(CKR_F_PKCS11_NEXT_CERT), "pkcs11_next_cert"}, {ERR_FUNC(CKR_F_PKCS11_NEXT_KEY), "pkcs11_next_key"}, - {ERR_FUNC(CKR_F_PKCS11_OPEN_SESSION), "pkcs11_open_session"}, + {ERR_FUNC(CKR_F_PKCS11_OPEN_SESSION), "pkcs11_session_pool_set_mode"}, {ERR_FUNC(CKR_F_PKCS11_PRIVATE_DECRYPT), "pkcs11_private_decrypt"}, {ERR_FUNC(CKR_F_PKCS11_PRIVATE_ENCRYPT), "pkcs11_private_encrypt"}, {ERR_FUNC(CKR_F_PKCS11_RELOAD_KEY), "pkcs11_reload_key"}, @@ -56,7 +56,7 @@ static ERR_STRING_DATA CKR_str_functs[] = { {ERR_FUNC(CKR_F_PKCS11_STORE_CERTIFICATE), "pkcs11_store_certificate"}, {ERR_FUNC(CKR_F_PKCS11_STORE_KEY), "pkcs11_store_key"}, {ERR_FUNC(CKR_F_PKCS11_RELOAD_CERTIFICATE), "pkcs11_reload_certificate"}, - {ERR_FUNC(CKR_F_PKCS11_GET_SESSION), "pkcs11_get_session"}, + {ERR_FUNC(CKR_F_PKCS11_GET_SESSION), "pkcs11_session_pool_acquire"}, {ERR_FUNC(CKR_F_PKCS11_EDDSA_SIGN), "pkcs11_eddsa_sign"}, {0, NULL} }; diff --git a/src/p11_ec.c b/src/p11_ec.c index 2a562a36..1a8e2041 100644 --- a/src/p11_ec.c +++ b/src/p11_ec.c @@ -325,7 +325,7 @@ static EC_KEY *pkcs11_get_ec(PKCS11_OBJECT_private *key) * Continue even if it fails, as the sign operation does not need * it if the PKCS#11 module or the hardware can figure this out */ - if (pkcs11_get_session(slot, 0, &session)) { + if (pkcs11_session_pool_acquire(slot, 0, &session)) { EC_KEY_free(ec); return NULL; } @@ -335,7 +335,7 @@ static EC_KEY *pkcs11_get_ec(PKCS11_OBJECT_private *key) no_point = pkcs11_get_point_associated(ec, key, CKO_PUBLIC_KEY, session); if (no_point && key->object_class == CKO_PRIVATE_KEY) /* Retry with the certificate */ no_point = pkcs11_get_point_associated(ec, key, CKO_CERTIFICATE, session); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); if (key->object_class == CKO_PRIVATE_KEY && EC_KEY_get0_private_key(ec) == NULL) { BIGNUM *bn = BN_new(); diff --git a/src/p11_eddsa.c b/src/p11_eddsa.c index 17fe827b..a31ebe5b 100644 --- a/src/p11_eddsa.c +++ b/src/p11_eddsa.c @@ -92,10 +92,10 @@ static int pkcs11_eddsa_pmeth_sign(EVP_PKEY_CTX *ctx, unsigned char *sig, if (!slot) return 0; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return 0; - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); if (!pkcs11_evp_pkey_eddsa_sign(key, sig, siglen, tbs, tbslen)) return 0; @@ -136,10 +136,10 @@ static int pkcs11_eddsa_pmeth_digestsign(EVP_MD_CTX *ctx, unsigned char *sig, if (!slot) return -1; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); /* Step 1: caller asks for signature length only */ if (sig == NULL) { @@ -351,10 +351,10 @@ static int pkcs11_xdh_pmeth_derive(EVP_PKEY_CTX *ctx, unsigned char *secret, if (!slot) return -1; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); type = EVP_PKEY_id(pkey); switch (type) { @@ -755,7 +755,7 @@ static int pkcs11_get_raw_public_key(PKCS11_OBJECT_private *key, slot = key->slot; ctx = slot->ctx; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; obj = pkcs11_choose_public_source(key, session, &obj_needs_free); @@ -775,7 +775,7 @@ static int pkcs11_get_raw_public_key(PKCS11_OBJECT_private *key, } end: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); if (!ok) { OPENSSL_free(*raw); diff --git a/src/p11_falcon.c b/src/p11_falcon.c index b425ca80..54cc1aad 100644 --- a/src/p11_falcon.c +++ b/src/p11_falcon.c @@ -217,7 +217,7 @@ static int pkcs11_get_raw_public_key(PKCS11_OBJECT_private *key, slot = key->slot; ctx = slot->ctx; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; obj = pkcs11_choose_public_source(key, session, &obj_needs_free); @@ -239,7 +239,7 @@ static int pkcs11_get_raw_public_key(PKCS11_OBJECT_private *key, } end: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); if (!ok) { OPENSSL_free(*raw); diff --git a/src/p11_front.c b/src/p11_front.c index 7cffe80e..6cd6f235 100644 --- a/src/p11_front.c +++ b/src/p11_front.c @@ -75,7 +75,7 @@ int PKCS11_open_session(PKCS11_SLOT *pslot, int rw) PKCS11_SLOT_private *slot = pslot->_private; if (check_slot_fork(slot) < 0) return -1; - return pkcs11_open_session(slot, rw); + return pkcs11_session_pool_set_mode(slot, rw); } int PKCS11_enumerate_slots(PKCS11_CTX *pctx, diff --git a/src/p11_key.c b/src/p11_key.c index 371a4843..350ccc32 100644 --- a/src/p11_key.c +++ b/src/p11_key.c @@ -125,7 +125,6 @@ static int pkcs11_find_keys(PKCS11_SLOT_private *, CK_SESSION_HANDLE, unsigned i PKCS11_TEMPLATE *); static int pkcs11_init_key(PKCS11_SLOT_private *, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE o, CK_OBJECT_CLASS type, PKCS11_KEY **); -static int pkcs11_init_keygen(PKCS11_SLOT_private *, CK_SESSION_HANDLE *); static int pkcs11_next_key(PKCS11_CTX_private *ctx, PKCS11_SLOT_private *, CK_SESSION_HANDLE session, CK_OBJECT_CLASS type); static int pkcs11_store_key(PKCS11_SLOT_private *, EVP_PKEY *, CK_OBJECT_CLASS, @@ -463,7 +462,7 @@ PKCS11_OBJECT_private *pkcs11_object_from_template(PKCS11_SLOT_private *slot, int release = 0; if (session == CK_INVALID_HANDLE) { - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return NULL; release = 1; } @@ -473,7 +472,7 @@ PKCS11_OBJECT_private *pkcs11_object_from_template(PKCS11_SLOT_private *slot, obj = pkcs11_object_from_handle(slot, session, object_handle); if (release) - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return obj; } @@ -548,7 +547,7 @@ int pkcs11_reload_object(PKCS11_OBJECT_private *obj) CK_SESSION_HANDLE session; PKCS11_TEMPLATE tmpl = {0}; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; pkcs11_addattr_var(&tmpl, CKA_CLASS, obj->object_class); @@ -560,7 +559,7 @@ int pkcs11_reload_object(PKCS11_OBJECT_private *obj) obj->object = pkcs11_handle_from_template(slot, session, &tmpl); pkcs11_zap_attrs(&tmpl); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); if (obj->object == CK_INVALID_HANDLE) CRYPTOKI_checkerr(CKR_F_PKCS11_RELOAD_KEY, CKR_OBJECT_HANDLE_INVALID); @@ -586,7 +585,7 @@ int pkcs11_rsa_keygen(PKCS11_SLOT_private *slot, unsigned int bits, CK_OBJECT_HANDLE pub_key_obj, priv_key_obj; int rv; - if (pkcs11_init_keygen(slot, &session)) + if (pkcs11_session_pool_acquire_keygen(slot, &session)) return -1; /* The following attributes are necessary for RSA encryption and DSA */ @@ -610,7 +609,7 @@ int pkcs11_rsa_keygen(PKCS11_SLOT_private *slot, unsigned int bits, privtmpl.attrs, privtmpl.nattr, &pub_key_obj, &priv_key_obj)); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); /* zap all memory allocated when building the template */ pkcs11_zap_attrs(&privtmpl); @@ -644,7 +643,7 @@ int pkcs11_ec_keygen(PKCS11_SLOT_private *slot, const char *curve, ASN1_OBJECT *curve_obj = NULL; int curve_nid = NID_undef; - if (pkcs11_init_keygen(slot, &session)) + if (pkcs11_session_pool_acquire_keygen(slot, &session)) return -1; curve_nid = EC_curve_nist2nid(curve); @@ -652,26 +651,18 @@ int pkcs11_ec_keygen(PKCS11_SLOT_private *slot, const char *curve, curve_nid = OBJ_sn2nid(curve); if (curve_nid == NID_undef) curve_nid = OBJ_ln2nid(curve); - if (curve_nid == NID_undef) { - pkcs11_put_session(slot, session); - return -1; - } + if (curve_nid == NID_undef) + goto error; curve_obj = OBJ_nid2obj(curve_nid); - if (!curve_obj) { - pkcs11_put_session(slot, session); - return -1; - } + if (!curve_obj) + goto error; /* convert to DER format and take just the length */ ec_params_len = i2d_ASN1_OBJECT(curve_obj, NULL); - if (ec_params_len < 0) { - pkcs11_put_session(slot, session); - return -1; - } + if (ec_params_len < 0) + goto error; ec_params = OPENSSL_malloc(ec_params_len); - if (!ec_params) { - pkcs11_put_session(slot, session); - return -1; - } + if (!ec_params) + goto error; /** * ec_params points to beginning of DER encoded object. Since we need this * location later and OpenSSL changes it in i2d_ASN1_OBJECT to point to 1 byte @@ -679,10 +670,8 @@ int pkcs11_ec_keygen(PKCS11_SLOT_private *slot, const char *curve, * pointer tmp */ tmp = ec_params; - if (i2d_ASN1_OBJECT(curve_obj, &tmp) < 0) { - pkcs11_put_session(slot, session); - return -1; - } + if (i2d_ASN1_OBJECT(curve_obj, &tmp) < 0) + goto error; /* The following attributes are necessary for ECDSA and ECDH mechanisms */ /* pubkey attributes */ @@ -703,7 +692,7 @@ int pkcs11_ec_keygen(PKCS11_SLOT_private *slot, const char *curve, privtmpl.attrs, privtmpl.nattr, &pub_key_obj, &priv_key_obj)); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); /* zap all memory allocated when building the template */ pkcs11_zap_attrs(&privtmpl); @@ -713,6 +702,11 @@ int pkcs11_ec_keygen(PKCS11_SLOT_private *slot, const char *curve, CRYPTOKI_checkerr(CKR_F_PKCS11_GENERATE_KEY, rv); return 0; + +error: + pkcs11_session_pool_release(slot, session); + OPENSSL_free(ec_params); + return -1; } #endif /* OPENSSL_NO_EC */ @@ -735,7 +729,7 @@ int pkcs11_eddsa_keygen(PKCS11_SLOT_private *slot, unsigned char *eddsa_params = NULL; size_t eddsa_params_len = 0; - if (pkcs11_init_keygen(slot, &session)) + if (pkcs11_session_pool_acquire_keygen(slot, &session)) return -1; if (nid == NID_ED25519) { @@ -745,7 +739,7 @@ int pkcs11_eddsa_keygen(PKCS11_SLOT_private *slot, eddsa_params = (unsigned char *)OID_ED448; eddsa_params_len = sizeof(OID_ED448); } else { - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return -1; /* unsupported */ } @@ -766,7 +760,7 @@ int pkcs11_eddsa_keygen(PKCS11_SLOT_private *slot, &pub_key_obj, &priv_key_obj)); /* cleanup */ - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); pkcs11_zap_attrs(&privtmpl); pkcs11_zap_attrs(&pubtmpl); @@ -792,7 +786,7 @@ int pkcs11_xdh_keygen(PKCS11_SLOT_private *slot, unsigned char *xdh_params = NULL; size_t xdh_params_len = 0; - if (pkcs11_init_keygen(slot, &session)) + if (pkcs11_session_pool_acquire_keygen(slot, &session)) return -1; if (nid == NID_X25519) { @@ -802,7 +796,7 @@ int pkcs11_xdh_keygen(PKCS11_SLOT_private *slot, xdh_params = (unsigned char *)OID_X448; xdh_params_len = sizeof(OID_X448); } else { - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return -1; /* unsupported */ } @@ -823,7 +817,7 @@ int pkcs11_xdh_keygen(PKCS11_SLOT_private *slot, &pub_key_obj, &priv_key_obj)); /* cleanup */ - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); pkcs11_zap_attrs(&privtmpl); pkcs11_zap_attrs(&pubtmpl); @@ -851,7 +845,7 @@ int pkcs11_mldsa_keygen(PKCS11_SLOT_private *slot, CK_OBJECT_HANDLE pub_key_obj, priv_key_obj; CK_RV rv; - if (pkcs11_init_keygen(slot, &session)) + if (pkcs11_session_pool_acquire_keygen(slot, &session)) return -1; switch (nid) { @@ -865,7 +859,7 @@ int pkcs11_mldsa_keygen(PKCS11_SLOT_private *slot, signParamSet = CKP_ML_DSA_87; break; default: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return -1; /* unsupported */ } @@ -914,7 +908,7 @@ int pkcs11_mldsa_keygen(PKCS11_SLOT_private *slot, } /* cleanup */ - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); pkcs11_zap_attrs(&privtmpl); pkcs11_zap_attrs(&pubtmpl); @@ -942,7 +936,7 @@ int pkcs11_mlkem_keygen(PKCS11_SLOT_private *slot, int nid, CK_OBJECT_HANDLE pub_key_obj, priv_key_obj; CK_RV rv; - if (pkcs11_init_keygen(slot, &session)) + if (pkcs11_session_pool_acquire_keygen(slot, &session)) return -1; switch (nid) { @@ -956,7 +950,7 @@ int pkcs11_mlkem_keygen(PKCS11_SLOT_private *slot, int nid, kemParamSet = CKP_ML_KEM_1024; break; default: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return -1; /* unsupported */ } @@ -1005,7 +999,7 @@ int pkcs11_mlkem_keygen(PKCS11_SLOT_private *slot, int nid, } /* cleanup */ - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); pkcs11_zap_attrs(&privtmpl); pkcs11_zap_attrs(&pubtmpl); @@ -1032,7 +1026,7 @@ int pkcs11_slhdsa_keygen(PKCS11_SLOT_private *slot, CK_OBJECT_HANDLE pub_key_obj, priv_key_obj; CK_RV rv; - if (pkcs11_init_keygen(slot, &session)) + if (pkcs11_session_pool_acquire_keygen(slot, &session)) return -1; switch (nid) { @@ -1073,7 +1067,7 @@ int pkcs11_slhdsa_keygen(PKCS11_SLOT_private *slot, signParamSet = CKP_SLH_DSA_SHAKE_256F; break; default: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return -1; /* unsupported */ } @@ -1095,7 +1089,7 @@ int pkcs11_slhdsa_keygen(PKCS11_SLOT_private *slot, &pub_key_obj, &priv_key_obj)); /* cleanup */ - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); pkcs11_zap_attrs(&privtmpl); pkcs11_zap_attrs(&pubtmpl); @@ -1125,7 +1119,7 @@ int pkcs11_falcon_keygen(PKCS11_SLOT_private *slot, CK_OBJECT_HANDLE pub_key_obj, priv_key_obj; CK_RV rv; - if (pkcs11_init_keygen(slot, &session)) + if (pkcs11_session_pool_acquire_keygen(slot, &session)) return -1; if (nid == NID_FALCON_512) { @@ -1133,7 +1127,7 @@ int pkcs11_falcon_keygen(PKCS11_SLOT_private *slot, } else if (nid == NID_FALCON_1024) { signParamSet = CKP_FALCON_1024; } else { - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return -1; /* unsupported */ } @@ -1155,7 +1149,7 @@ int pkcs11_falcon_keygen(PKCS11_SLOT_private *slot, &pub_key_obj, &priv_key_obj)); /* cleanup */ - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); pkcs11_zap_attrs(&privtmpl); pkcs11_zap_attrs(&pubtmpl); @@ -1255,7 +1249,7 @@ static int pkcs11_store_key(PKCS11_SLOT_private *slot, EVP_PKEY *pk, return -1; } - if (pkcs11_get_session(slot, 1, &session)) { + if (pkcs11_session_pool_acquire(slot, 1, &session)) { pkcs11_zap_attrs(&tmpl); return -1; } @@ -1270,7 +1264,7 @@ static int pkcs11_store_key(PKCS11_SLOT_private *slot, EVP_PKEY *pk, /* Gobble the key object */ r = pkcs11_init_key(slot, session, object, type, ret_key); } - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); CRYPTOKI_checkerr(CKR_F_PKCS11_STORE_KEY, rv); return r; @@ -1460,7 +1454,7 @@ int pkcs11_enumerate_keys(PKCS11_SLOT_private *slot, unsigned int type, const PK if (key_template->label) pkcs11_addattr_s(&tmpl, CKA_LABEL, key_template->label); } - if (pkcs11_get_session(slot, 0, &session)) { + if (pkcs11_session_pool_acquire(slot, 0, &session)) { pkcs11_zap_attrs(&tmpl); return -1; } @@ -1468,7 +1462,7 @@ int pkcs11_enumerate_keys(PKCS11_SLOT_private *slot, unsigned int type, const PK rv = pkcs11_find_keys(slot, session, type, &tmpl); pkcs11_zap_attrs(&tmpl); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); if (rv < 0) { pkcs11_destroy_keys(slot, type); return -1; @@ -1490,11 +1484,11 @@ int pkcs11_remove_object(PKCS11_OBJECT_private *obj) CK_SESSION_HANDLE session; int rv; - if (pkcs11_get_session(slot, 1, &session)) + if (pkcs11_session_pool_acquire(slot, 1, &session)) return -1; rv = CRYPTOKI_call(ctx, C_DestroyObject(session, obj->object)); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); CRYPTOKI_checkerr(CKR_F_PKCS11_REMOVE_KEY, rv); return 0; @@ -1654,22 +1648,6 @@ CK_RSA_PKCS_MGF_TYPE pkcs11_md2ckg(const EVP_MD *md) } } -static int pkcs11_init_keygen(PKCS11_SLOT_private *slot, CK_SESSION_HANDLE *session) -{ - pthread_mutex_lock(&slot->lock); - /* R/W session is mandatory for key generation. */ - if (slot->rw_mode != 1) { - pthread_mutex_unlock(&slot->lock); - if (pkcs11_open_session(slot, 1)) - return -1; - /* open_session will call C_CloseAllSessions which logs everyone out */ - if (pkcs11_login(slot, 0, slot->prev_pin)) - return -1; - } - pthread_mutex_unlock(&slot->lock); - return pkcs11_get_session(slot, 1, session); -} - static void pkcs11_common_pubkey_attr(PKCS11_TEMPLATE *pubtmpl, const char *label, const unsigned char *id, size_t id_len) { @@ -2049,10 +2027,10 @@ static int pkcs11_try_pkey_rsa_sign(EVP_PKEY_CTX *evp_pkey_ctx, if (!slot) return -1; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); /* retrieve PSS parameters */ if (EVP_PKEY_CTX_get_rsa_padding(evp_pkey_ctx, &padding) <= 0) @@ -2124,10 +2102,10 @@ static int pkcs11_try_pkey_rsa_decrypt(EVP_PKEY_CTX *evp_pkey_ctx, if (!slot) return -1; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); switch (padding) { case RSA_PKCS1_PADDING: diff --git a/src/p11_mldsa.c b/src/p11_mldsa.c index 9a04e390..9bc87d1c 100644 --- a/src/p11_mldsa.c +++ b/src/p11_mldsa.c @@ -231,7 +231,7 @@ static int pkcs11_get_raw_public_key(PKCS11_OBJECT_private *key, slot = key->slot; ctx = slot->ctx; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; obj = pkcs11_choose_public_source(key, session, &obj_needs_free); @@ -253,7 +253,7 @@ static int pkcs11_get_raw_public_key(PKCS11_OBJECT_private *key, } end: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); if (!ok) { OPENSSL_free(*raw); diff --git a/src/p11_pkey.c b/src/p11_pkey.c index 58d4180f..b0a72b12 100644 --- a/src/p11_pkey.c +++ b/src/p11_pkey.c @@ -391,7 +391,7 @@ static int pkcs11_sign_with_mechanism(PKCS11_OBJECT_private *key, ck_siglen = (CK_ULONG)*siglen; ck_tbslen = (CK_ULONG)tbslen; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return CKR_GENERAL_ERROR; rv = CRYPTOKI_call(ctx, C_SignInit(session, mechanism, key->object)); @@ -427,7 +427,7 @@ static int pkcs11_sign_with_mechanism(PKCS11_OBJECT_private *key, *siglen = (size_t)ck_siglen; end: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return rv; } @@ -470,7 +470,7 @@ static int pkcs11_verify_with_mechanism(PKCS11_OBJECT_private *key, ck_siglen = (CK_ULONG)siglen; ck_tbslen = (CK_ULONG)tbslen; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return CKR_GENERAL_ERROR; rv = CRYPTOKI_call(ctx, @@ -492,7 +492,7 @@ static int pkcs11_verify_with_mechanism(PKCS11_OBJECT_private *key, } end: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return rv; } #endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ @@ -534,7 +534,7 @@ static int pkcs11_decrypt_with_mechanism(PKCS11_OBJECT_private *key, ck_outlen = (CK_ULONG)*outlen; ck_inlen = (CK_ULONG)inlen; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return CKR_GENERAL_ERROR; rv = CRYPTOKI_call(ctx, C_DecryptInit(session, mechanism, key->object)); @@ -561,7 +561,7 @@ static int pkcs11_decrypt_with_mechanism(PKCS11_OBJECT_private *key, *outlen = (size_t)ck_outlen; end: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return rv; } @@ -617,7 +617,7 @@ static CK_RV pkcs11_derive_with_mechanism(PKCS11_OBJECT_private *key, pkcs11_mechanism_name(mechanism), secret, (unsigned long)*secretlen); #endif - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return CKR_GENERAL_ERROR; if (key->always_authenticate == CK_TRUE) { @@ -659,7 +659,7 @@ static CK_RV pkcs11_derive_with_mechanism(PKCS11_OBJECT_private *key, CRYPTOKI_call(ctx, C_DestroyObject(session, newkey)); OPENSSL_clear_free(value, value_len_alloc); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return rv; } @@ -729,7 +729,7 @@ static CK_RV pkcs11_decapsulate_with_mechanism( newkey_len = (CK_ULONG)len; ck_inlen = (CK_ULONG)inlen; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return CKR_GENERAL_ERROR; if (key->always_authenticate == CK_TRUE) { @@ -785,7 +785,7 @@ static CK_RV pkcs11_decapsulate_with_mechanism( CRYPTOKI_call(ctx, C_DestroyObject(session, newkey)); OPENSSL_clear_free(value, value_len_alloc); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return rv; } #endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ @@ -1485,10 +1485,10 @@ static int pkcs11_try_pkey_ec_sign(EVP_PKEY_CTX *evp_pkey_ctx, if (!slot) return -1; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return pkcs11_evp_pkey_ec_sign(key, sig, siglen, tbs, tbslen); } @@ -1506,10 +1506,10 @@ static int pkcs11_eddsa_sign(unsigned char *sig, size_t *siglen, if (!slot) return -1; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); return pkcs11_evp_pkey_eddsa_sign(key, sig, siglen, tbs, tbslen); } diff --git a/src/p11_pthread.h b/src/p11_pthread.h index 139e8ab0..62364634 100644 --- a/src/p11_pthread.h +++ b/src/p11_pthread.h @@ -87,6 +87,12 @@ static int pthread_cond_signal(pthread_cond_t *cond) return 0; } +static int pthread_cond_broadcast(pthread_cond_t *cond) +{ + WakeAllConditionVariable(cond); + return 0; +} + #else #error Locking not supported on this platform. diff --git a/src/p11_rsa.c b/src/p11_rsa.c index 818acbba..4fe3ee5a 100644 --- a/src/p11_rsa.c +++ b/src/p11_rsa.c @@ -82,10 +82,10 @@ int pkcs11_private_encrypt(int flen, if (!slot) return -1; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); siglen = pkcs11_get_key_size(key); if (pkcs11_evp_pkey_rsa_sign(key, @@ -118,10 +118,10 @@ int pkcs11_private_decrypt(int flen, if (!slot) return -1; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); /* Openssl API for RSA_private_decrypt() allows to use * RSA_PKCS1_OAEP_PADDING only with SHA_1 hash and and MGF1_SHA1 mask @@ -168,7 +168,7 @@ static RSA *pkcs11_get_rsa(PKCS11_OBJECT_private *key) RSA *rsa; BIGNUM *rsa_n = NULL, *rsa_e = NULL; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return NULL; /* Retrieve the modulus */ @@ -188,6 +188,7 @@ static RSA *pkcs11_get_rsa(PKCS11_OBJECT_private *key) pkcs11_addattr_var(&tmpl, CKA_CLASS, class_public_key); pkcs11_addattr_bn(&tmpl, CKA_MODULUS, rsa_n); pubkey = pkcs11_object_from_template(slot, session, &tmpl); + pkcs11_zap_attrs(&tmpl); if (pubkey && !pkcs11_getattr_bn(ctx, session, pubkey->object, CKA_PUBLIC_EXPONENT, &rsa_e)) { pkcs11_object_free(pubkey); @@ -201,14 +202,14 @@ static RSA *pkcs11_get_rsa(PKCS11_OBJECT_private *key) goto success; failure: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); /* BN_clear_free() is NULL-safe */ BN_clear_free(rsa_n); BN_clear_free(rsa_e); return NULL; success: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); rsa = RSA_new(); if (!rsa) { BN_clear_free(rsa_n); diff --git a/src/p11_slhdsa.c b/src/p11_slhdsa.c index 4e602e40..07161098 100644 --- a/src/p11_slhdsa.c +++ b/src/p11_slhdsa.c @@ -218,7 +218,7 @@ static int pkcs11_get_raw_public_key(PKCS11_OBJECT_private *key, slot = key->slot; ctx = slot->ctx; - if (pkcs11_get_session(slot, 0, &session)) + if (pkcs11_session_pool_acquire(slot, 0, &session)) return -1; obj = pkcs11_choose_public_source(key, session, &obj_needs_free); @@ -240,7 +240,7 @@ static int pkcs11_get_raw_public_key(PKCS11_OBJECT_private *key, } end: - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); if (!ok) { OPENSSL_free(*raw); diff --git a/src/p11_slot.c b/src/p11_slot.c index 56a5c4ad..2050cd66 100644 --- a/src/p11_slot.c +++ b/src/p11_slot.c @@ -105,108 +105,211 @@ int pkcs11_enumerate_slots(PKCS11_CTX_private *ctx, PKCS11_SLOT **slotp, return 0; } +static void pkcs11_wipe_cache(PKCS11_SLOT_private *slot) +{ + pkcs11_destroy_keys(slot, CKO_PRIVATE_KEY); + pkcs11_destroy_keys(slot, CKO_PUBLIC_KEY); + pkcs11_destroy_certs(slot); +} + +static void pkcs11_session_pool_reset_locked(PKCS11_SLOT_private *slot) +{ + slot->sessions_in_use = 0; + slot->num_sessions = 0; + slot->session_head = 0; + slot->session_tail = 0; +} + /* - * Open a session with this slot + * Keep transition ownership in plain slot state rather than a separate mutex: + * after fork, only the child thread survives, and pkcs11_reload_slot() can + * safely clear this reservation before the child opens new sessions. */ -int pkcs11_open_session(PKCS11_SLOT_private *slot, int rw) +static void pkcs11_session_pool_begin_transition(PKCS11_SLOT_private *slot) { - PKCS11_CTX_private *ctx = slot->ctx; + pthread_mutex_lock(&slot->lock); + while (slot->transition_active) + pthread_cond_wait(&slot->cond, &slot->lock); + slot->transition_active = 1; + while (slot->sessions_in_use != 0) + pthread_cond_wait(&slot->cond, &slot->lock); + pthread_mutex_unlock(&slot->lock); +} +static void pkcs11_session_pool_end_transition(PKCS11_SLOT_private *slot) +{ pthread_mutex_lock(&slot->lock); - /* If different mode requested, flush pool */ - if (rw != slot->rw_mode) { - CRYPTOKI_call(ctx, C_CloseAllSessions(slot->id)); - slot->rw_mode = rw; - slot->logged_in = -1; - } - slot->num_sessions = 0; - slot->session_head = slot->session_tail = 0; + slot->transition_active = 0; + pthread_cond_broadcast(&slot->cond); pthread_mutex_unlock(&slot->lock); +} + +/* The caller must hold slot->lock and own a drained transition. */ +static int pkcs11_session_pool_switch_mode_locked( + PKCS11_SLOT_private *slot, int rw, int *changed) +{ + PKCS11_CTX_private *ctx = slot->ctx; + CK_RV rv; + + if (changed) + *changed = 0; + if (rw == slot->rw_mode) + return 0; + if (slot->sessions_in_use != 0) { + CKRerr(CKR_F_PKCS11_OPEN_SESSION, CKR_GENERAL_ERROR); + return -1; + } + rv = CRYPTOKI_call(ctx, C_CloseAllSessions(slot->id)); + if (rv != CKR_OK) { + CKRerr(CKR_F_PKCS11_OPEN_SESSION, rv); + return -1; + } + pkcs11_session_pool_reset_locked(slot); + slot->rw_mode = rw; + slot->logged_in = -1; + if (changed) + *changed = 1; return 0; } - -static void pkcs11_wipe_cache(PKCS11_SLOT_private *slot) +int pkcs11_session_pool_set_mode(PKCS11_SLOT_private *slot, int rw) { - pkcs11_destroy_keys(slot, CKO_PRIVATE_KEY); - pkcs11_destroy_keys(slot, CKO_PUBLIC_KEY); - pkcs11_destroy_certs(slot); + int rv; + + pkcs11_session_pool_begin_transition(slot); + pthread_mutex_lock(&slot->lock); + rv = pkcs11_session_pool_switch_mode_locked(slot, rw, NULL); + pthread_mutex_unlock(&slot->lock); + pkcs11_session_pool_end_transition(slot); + + return rv; } -int pkcs11_get_session(PKCS11_SLOT_private *slot, int rw, CK_SESSION_HANDLE *sessionp) +enum pkcs11_session_select_result { + PKCS11_SESSION_SELECT_ERROR = -1, + PKCS11_SESSION_SELECT_SUCCESS = 0, + PKCS11_SESSION_SELECT_UNAVAILABLE = 1 +}; + +/* The caller must hold slot->lock. This helper never waits. */ +static int pkcs11_session_pool_select_locked(PKCS11_SLOT_private *slot, + CK_SESSION_HANDLE *sessionp) { PKCS11_CTX_private *ctx = slot->ctx; - int rv = CKR_OK; CK_SESSION_INFO session_info; + CK_RV rv; + + while (slot->session_head != slot->session_tail) { + *sessionp = slot->session_pool[slot->session_head]; + slot->session_head = + (slot->session_head + 1) % slot->session_poolsize; + + /* Discard sessions invalidated by the PKCS#11 module. */ + rv = CRYPTOKI_call(ctx, + C_GetSessionInfo(*sessionp, &session_info)); + if (rv == CKR_OK) + return PKCS11_SESSION_SELECT_SUCCESS; + if (slot->num_sessions == 0) { + slot->session_head = slot->session_tail = 0; + CKRerr(CKR_F_PKCS11_GET_SESSION, CKR_GENERAL_ERROR); + return PKCS11_SESSION_SELECT_ERROR; + } + slot->num_sessions--; + if (slot->num_sessions == 0) { + /* Object handles are valid across sessions, so only clear + * the cache when there are no valid sessions. */ + pkcs11_wipe_cache(slot); + } + } + + if (slot->num_sessions >= slot->max_sessions) + return PKCS11_SESSION_SELECT_UNAVAILABLE; + + rv = CRYPTOKI_call(ctx, + C_OpenSession(slot->id, + CKF_SERIAL_SESSION | + (slot->rw_mode ? CKF_RW_SESSION : 0), + NULL, NULL, sessionp)); + if (rv == CKR_OK) { + slot->num_sessions++; + return PKCS11_SESSION_SELECT_SUCCESS; + } + + /* If the module reports a lower limit than the configured one, + * wait for one of this pool's sessions when that can make progress. */ + if (rv == CKR_SESSION_COUNT && slot->num_sessions > 0) { + slot->max_sessions = slot->num_sessions; + return PKCS11_SESSION_SELECT_UNAVAILABLE; + } + CKRerr(CKR_F_PKCS11_GET_SESSION, rv); + return PKCS11_SESSION_SELECT_ERROR; +} + +int pkcs11_session_pool_acquire(PKCS11_SLOT_private *slot, int rw, + CK_SESSION_HANDLE *sessionp) +{ + int select_result; if (rw < 0) return -1; pthread_mutex_lock(&slot->lock); - if (slot->rw_mode < 0) - slot->rw_mode = rw; - rw = slot->rw_mode; - do { - /* Get session from the pool */ - if (slot->session_head != slot->session_tail) { - *sessionp = slot->session_pool[slot->session_head]; - slot->session_head = (slot->session_head + 1) % slot->session_poolsize; - - /* Check if session is valid */ - rv = CRYPTOKI_call(ctx, - C_GetSessionInfo(*sessionp, &session_info)); - if (rv == CKR_OK) { - break; - } else { - /* Forget this session */ - slot->num_sessions--; - if (slot->num_sessions == 0) { - /* Object handles are valid across - * sessions, so the cache should only be - * cleared when there are no valid - * sessions.*/ - pkcs11_wipe_cache(slot); - } - continue; - } + for (;;) { + while (slot->transition_active) + pthread_cond_wait(&slot->cond, &slot->lock); + if (slot->rw_mode < 0) + slot->rw_mode = rw; + + select_result = pkcs11_session_pool_select_locked(slot, sessionp); + if (select_result == PKCS11_SESSION_SELECT_SUCCESS) { + slot->sessions_in_use++; + pthread_mutex_unlock(&slot->lock); + return 0; } - - /* Check if new can be instantiated */ - if (slot->num_sessions < slot->max_sessions) { - rv = CRYPTOKI_call(ctx, - C_OpenSession(slot->id, - CKF_SERIAL_SESSION | (rw ? CKF_RW_SESSION : 0), - NULL, NULL, sessionp)); - if (rv == CKR_OK) { - slot->num_sessions++; - break; - } else { - pthread_mutex_unlock(&slot->lock); - return -1; - } - - /* Remember the maximum session count */ - if (rv == CKR_SESSION_COUNT) - slot->max_sessions = slot->num_sessions; + if (select_result == PKCS11_SESSION_SELECT_ERROR) { + pthread_mutex_unlock(&slot->lock); + return -1; } - /* Wait for a session to become available */ + /* The configured maximum is in use. Every wakeup must + * recheck both transition ownership and pool availability. */ pthread_cond_wait(&slot->cond, &slot->lock); - } while (1); - pthread_mutex_unlock(&slot->lock); - - return 0; + } } -void pkcs11_put_session(PKCS11_SLOT_private *slot, CK_SESSION_HANDLE session) +void pkcs11_session_pool_release(PKCS11_SLOT_private *slot, + CK_SESSION_HANDLE session) { - pthread_mutex_lock(&slot->lock); + PKCS11_CTX_private *ctx = slot->ctx; + unsigned int next_tail; + CK_RV rv; - slot->session_pool[slot->session_tail] = session; - slot->session_tail = (slot->session_tail + 1) % slot->session_poolsize; - pthread_cond_signal(&slot->cond); + pthread_mutex_lock(&slot->lock); + if (slot->sessions_in_use == 0 || slot->session_poolsize < 2) { + CKRerr(CKR_F_PKCS11_GET_SESSION, CKR_SESSION_HANDLE_INVALID); + pthread_cond_broadcast(&slot->cond); + pthread_mutex_unlock(&slot->lock); + return; + } + next_tail = (slot->session_tail + 1) % slot->session_poolsize; + slot->sessions_in_use--; + if (next_tail == slot->session_head) { + /* Do not overwrite an available session if accounting was + * corrupted by an invalid or duplicate release. */ + rv = CRYPTOKI_call(ctx, C_CloseSession(session)); + if (slot->num_sessions > 0) + slot->num_sessions--; + if (slot->num_sessions == 0) + pkcs11_wipe_cache(slot); + if (rv != CKR_OK) + CKRerr(CKR_F_PKCS11_GET_SESSION, rv); + } else { + slot->session_pool[slot->session_tail] = session; + slot->session_tail = next_tail; + } + pthread_cond_broadcast(&slot->cond); pthread_mutex_unlock(&slot->lock); } @@ -215,7 +318,47 @@ void pkcs11_put_session(PKCS11_SLOT_private *slot, CK_SESSION_HANDLE session) */ int pkcs11_is_logged_in(PKCS11_SLOT_private *slot, int so, int *res) { + pthread_mutex_lock(&slot->lock); *res = slot->logged_in == so; + pthread_mutex_unlock(&slot->lock); + return 0; +} + +/* Authenticate using a session already checked out from this slot. */ +static int pkcs11_login_on_session(PKCS11_SLOT_private *slot, + CK_SESSION_HANDLE session, int so, const char *pin) +{ + PKCS11_CTX_private *ctx = slot->ctx; + char *pin_copy = NULL; + CK_RV rv; + + if (pin) { + pin_copy = OPENSSL_strdup(pin); + if (!pin_copy) + return -1; + } + + rv = CRYPTOKI_call(ctx, + C_Login(session, so ? CKU_SO : CKU_USER, + (CK_UTF8CHAR *) pin_copy, + pin_copy ? (CK_ULONG)strlen(pin_copy) : 0)); + if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN) { + if (pin_copy) { + OPENSSL_cleanse(pin_copy, strlen(pin_copy)); + OPENSSL_free(pin_copy); + } + CKRerr(CKR_F_PKCS11_LOGIN, rv); + return -1; + } + + pthread_mutex_lock(&slot->lock); + if (slot->prev_pin) { + OPENSSL_cleanse(slot->prev_pin, strlen(slot->prev_pin)); + OPENSSL_free(slot->prev_pin); + } + slot->prev_pin = pin_copy; + slot->logged_in = so; + pthread_mutex_unlock(&slot->lock); return 0; } @@ -224,34 +367,63 @@ int pkcs11_is_logged_in(PKCS11_SLOT_private *slot, int so, int *res) */ int pkcs11_login(PKCS11_SLOT_private *slot, int so, const char *pin) { - PKCS11_CTX_private *ctx = slot->ctx; CK_SESSION_HANDLE session; - int rv; + int logged_in, rv; - if (slot->logged_in >= 0) + pthread_mutex_lock(&slot->lock); + logged_in = slot->logged_in; + pthread_mutex_unlock(&slot->lock); + if (logged_in >= 0) return 0; /* Nothing to do */ - /* SO needs a r/w session, user can be checked with a r/o session. */ - if (pkcs11_get_session(slot, so, &session)) + /* SO needs a r/w session, user can use a r/o session. */ + if (pkcs11_session_pool_acquire(slot, so, &session)) return -1; + rv = pkcs11_login_on_session(slot, session, so, pin); + pkcs11_session_pool_release(slot, session); + return rv; +} - rv = CRYPTOKI_call(ctx, - C_Login(session, so ? CKU_SO : CKU_USER, - (CK_UTF8CHAR *) pin, pin ? (unsigned long) strlen(pin) : 0)); - pkcs11_put_session(slot, session); +int pkcs11_session_pool_acquire_keygen(PKCS11_SLOT_private *slot, + CK_SESSION_HANDLE *sessionp) +{ + const char *pin = NULL; + int login_state, mode_changed = 0, select_result; + int session_acquired = 0, rv = -1; - if (rv && rv != CKR_USER_ALREADY_LOGGED_IN) { /* logged in -> OK */ - CRYPTOKI_checkerr(CKR_F_PKCS11_LOGIN, rv); + /* Keep normal acquisition gated until the R/W session has been + * acquired and any login invalidated by the mode switch is restored. */ + pkcs11_session_pool_begin_transition(slot); + pthread_mutex_lock(&slot->lock); + login_state = slot->logged_in; + if (pkcs11_session_pool_switch_mode_locked( + slot, 1, &mode_changed)) { + pthread_mutex_unlock(&slot->lock); + goto out; } - if (slot->prev_pin != pin) { - if (slot->prev_pin) { - OPENSSL_cleanse(slot->prev_pin, strlen(slot->prev_pin)); - OPENSSL_free(slot->prev_pin); - } - slot->prev_pin = OPENSSL_strdup(pin); + + select_result = pkcs11_session_pool_select_locked(slot, sessionp); + if (select_result != PKCS11_SESSION_SELECT_SUCCESS) { + if (select_result == PKCS11_SESSION_SELECT_UNAVAILABLE) + CKRerr(CKR_F_PKCS11_GET_SESSION, CKR_SESSION_COUNT); + pthread_mutex_unlock(&slot->lock); + goto out; } - slot->logged_in = so; - return 0; + slot->sessions_in_use++; + session_acquired = 1; + if (mode_changed && login_state >= 0) + pin = slot->prev_pin; + pthread_mutex_unlock(&slot->lock); + + if (mode_changed && login_state >= 0 && + pkcs11_login_on_session(slot, *sessionp, login_state, pin)) + goto out; + rv = 0; +out: + if (rv != 0 && session_acquired) + pkcs11_session_pool_release(slot, *sessionp); + pkcs11_session_pool_end_transition(slot); + return rv; } /* @@ -261,6 +433,9 @@ int pkcs11_reload_slot(PKCS11_SLOT_private *slot) { int logged_in = slot->logged_in; + /* No transition owner or checked-out session survives fork(). */ + slot->transition_active = 0; + slot->sessions_in_use = 0; slot->num_sessions = 0; slot->session_head = slot->session_tail = 0; if (logged_in >= 0) { @@ -279,18 +454,31 @@ int pkcs11_logout(PKCS11_SLOT_private *slot) { PKCS11_CTX_private *ctx = slot->ctx; CK_SESSION_HANDLE session; - int rv = CKR_OK; + int logged_in, session_acquired = 0, rv = CKR_OK; /* Calling PKCS11_logout invalidates all cached * keys we have */ pkcs11_wipe_cache(slot); - if (pkcs11_get_session(slot, slot->logged_in, &session) == 0) { + pthread_mutex_lock(&slot->lock); + logged_in = slot->logged_in; + pthread_mutex_unlock(&slot->lock); + if (pkcs11_session_pool_acquire(slot, logged_in, &session) == 0) { + session_acquired = 1; rv = CRYPTOKI_call(ctx, C_Logout(session)); - pkcs11_put_session(slot, session); + if (rv == CKR_OK) { + pthread_mutex_lock(&slot->lock); + slot->logged_in = -1; + pthread_mutex_unlock(&slot->lock); + } + pkcs11_session_pool_release(slot, session); } CRYPTOKI_checkerr(CKR_F_PKCS11_LOGOUT, rv); - slot->logged_in = -1; + if (!session_acquired) { + pthread_mutex_lock(&slot->lock); + slot->logged_in = -1; + pthread_mutex_unlock(&slot->lock); + } return 0; } @@ -342,14 +530,14 @@ int pkcs11_init_pin(PKCS11_SLOT_private *slot, const char *pin) CK_OBJECT_HANDLE session; int len, rv; - if (pkcs11_get_session(slot, 1, &session)) { + if (pkcs11_session_pool_acquire(slot, 1, &session)) { P11err(P11_F_PKCS11_INIT_PIN, P11_R_NO_SESSION); return -1; } len = pin ? (int) strlen(pin) : 0; rv = CRYPTOKI_call(ctx, C_InitPIN(session, (CK_UTF8CHAR *) pin, len)); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); CRYPTOKI_checkerr(CKR_F_PKCS11_INIT_PIN, rv); return 0; @@ -365,7 +553,7 @@ int pkcs11_change_pin(PKCS11_SLOT_private *slot, const char *old_pin, CK_SESSION_HANDLE session; int old_len, new_len, rv; - if (pkcs11_get_session(slot, 1, &session)) { + if (pkcs11_session_pool_acquire(slot, 1, &session)) { P11err(P11_F_PKCS11_CHANGE_PIN, P11_R_NO_SESSION); return -1; } @@ -375,7 +563,7 @@ int pkcs11_change_pin(PKCS11_SLOT_private *slot, const char *old_pin, rv = CRYPTOKI_call(ctx, C_SetPIN(session, (CK_UTF8CHAR *) old_pin, old_len, (CK_UTF8CHAR *) new_pin, new_len)); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); CRYPTOKI_checkerr(CKR_F_PKCS11_CHANGE_PIN, rv); return 0; @@ -391,14 +579,14 @@ int pkcs11_seed_random(PKCS11_SLOT_private *slot, const unsigned char *s, CK_SESSION_HANDLE session; int rv; - if (pkcs11_get_session(slot, 0, &session)) { + if (pkcs11_session_pool_acquire(slot, 0, &session)) { P11err(P11_F_PKCS11_SEED_RANDOM, P11_R_NO_SESSION); return -1; } rv = CRYPTOKI_call(ctx, C_SeedRandom(session, (CK_BYTE_PTR) s, s_len)); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); CRYPTOKI_checkerr(CKR_F_PKCS11_SEED_RANDOM, rv); return 0; @@ -414,14 +602,14 @@ int pkcs11_generate_random(PKCS11_SLOT_private *slot, unsigned char *r, CK_SESSION_HANDLE session; int rv; - if (pkcs11_get_session(slot, 0, &session)) { + if (pkcs11_session_pool_acquire(slot, 0, &session)) { P11err(P11_F_PKCS11_GENERATE_RANDOM, P11_R_NO_SESSION); return -1; } rv = CRYPTOKI_call(ctx, C_GenerateRandom(session, (CK_BYTE_PTR) r, r_len)); - pkcs11_put_session(slot, session); + pkcs11_session_pool_release(slot, session); CRYPTOKI_checkerr(CKR_F_PKCS11_GENERATE_RANDOM, rv); @@ -464,6 +652,8 @@ int pkcs11_slot_unref(PKCS11_SLOT_private *slot) if (pkcs11_atomic_add(&slot->refcnt, -1, &slot->lock) != 0) return 0; + /* Destruction also obeys the no-close-while-leased invariant. */ + pkcs11_session_pool_begin_transition(slot); pkcs11_wipe_cache(slot); if (slot->prev_pin) { OPENSSL_cleanse(slot->prev_pin, strlen(slot->prev_pin)); @@ -471,8 +661,8 @@ int pkcs11_slot_unref(PKCS11_SLOT_private *slot) } CRYPTOKI_call(slot->ctx, C_CloseAllSessions(slot->id)); OPENSSL_free(slot->session_pool); - pthread_mutex_destroy(&slot->lock); pthread_cond_destroy(&slot->cond); + pthread_mutex_destroy(&slot->lock); return 1; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 564a38fc..db820e34 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -35,7 +35,9 @@ check_PROGRAMS = \ ed25519-keygen-prov \ ed448-keygen-prov \ mldsa87-keygen-prov \ - mlkem768-keygen-prov + mlkem768-keygen-prov \ + session-pool-test \ + session-pool-stress dist_check_SCRIPTS = \ rsa-testpkcs11.softhsm \ rsa-testfork.softhsm \ @@ -54,6 +56,7 @@ dist_check_SCRIPTS = \ ec-copy.softhsm \ ec-keygen.softhsm \ ec-derive.softhsm \ + session-pool-stress.softhsm \ ed25519-keygen.softhsm \ ed448-keygen.softhsm \ fork-change-slot.softhsm \ @@ -107,8 +110,10 @@ rsa_pss_sign_prov_SOURCES = rsa-pss-sign-prov.c helpers_prov.c rsa_oaep_prov_SOURCES = rsa-oaep-prov.c helpers_prov.c check_all_prov_SOURCES = check-all-prov.c helpers_prov.c ec_derive_prov_SOURCES = ec-derive-prov.c helpers_prov.c +session_pool_test_SOURCES = session-pool-test.c session-pool-under-test.c +session_pool_test_LDADD = $(OPENSSL_LIBS) -TESTS = $(dist_check_SCRIPTS) +TESTS = session-pool-test$(EXEEXT) $(dist_check_SCRIPTS) TESTS_ENVIRONMENT = \ LC_ALL="C" \ diff --git a/tests/session-pool-stress.c b/tests/session-pool-stress.c new file mode 100644 index 00000000..a78151e2 --- /dev/null +++ b/tests/session-pool-stress.c @@ -0,0 +1,263 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _POSIX_C_SOURCE 200809L +#include "config.h" +#include + +#include +#include +#include +#include + +#ifdef HAVE_PTHREAD +#include + +#define SIGN_SUCCESSES 40 +#define SIGN_ATTEMPTS 400 +#define MODE_CHANGES 40 +#define KEYGEN_ATTEMPTS 40 + +struct stress_state { + pthread_mutex_t lock; + pthread_cond_t cond; + PKCS11_SLOT *slot; + const char *pin; + int start; + int sign_successes; + int keygen_success; + int mode_success; +}; + +static void wait_for_start(struct stress_state *state) +{ + pthread_mutex_lock(&state->lock); + while (!state->start) + pthread_cond_wait(&state->cond, &state->lock); + pthread_mutex_unlock(&state->lock); +} + +static void retry_login(struct stress_state *state) +{ + (void)PKCS11_login(state->slot, 0, state->pin); + ERR_clear_error(); +} + +static void *sign_thread(void *arg) +{ + struct stress_state *state = arg; + PKCS11_KEY *keys, *key; + unsigned char digest[32] = {0}; + unsigned char signature[512]; + unsigned int signature_len, nkeys, i; + int attempts; + + wait_for_start(state); + for (attempts = 0; attempts < SIGN_ATTEMPTS && + state->sign_successes < SIGN_SUCCESSES; attempts++) { + key = NULL; + if (PKCS11_enumerate_keys(state->slot->token, + &keys, &nkeys) == 0) { + for (i = nkeys; i > 0; i--) { + if (keys[i - 1].label && + strcmp(keys[i - 1].label, + "stress-signing-key") == 0) { + key = &keys[i - 1]; + break; + } + } + } + signature_len = sizeof(signature); + if (key && PKCS11_sign(NID_sha256, digest, sizeof(digest), + signature, &signature_len, key) == 1) { + state->sign_successes++; + } else { + retry_login(state); + } + } + if (state->sign_successes != SIGN_SUCCESSES) + ERR_print_errors_fp(stderr); + return NULL; +} + +static void *keygen_thread(void *arg) +{ + struct stress_state *state = arg; + unsigned char id[] = {0x66, 0x65, 0x05}; + int attempts; + + wait_for_start(state); + for (attempts = 0; attempts < KEYGEN_ATTEMPTS; attempts++) { + if (PKCS11_generate_key(state->slot->token, EVP_PKEY_RSA, 1024, + "session-pool-stress-key", id, sizeof(id)) == 0) { + state->keygen_success = 1; + break; + } + retry_login(state); + } + return NULL; +} + +static void *mode_thread(void *arg) +{ + struct stress_state *state = arg; + struct timespec delay = {0, 2000000L}; + int i; + + wait_for_start(state); + for (i = 0; i < MODE_CHANGES; i++) { + if (PKCS11_open_session(state->slot, i & 1) != 0) + break; + retry_login(state); + nanosleep(&delay, NULL); + } + if (i == MODE_CHANGES) + state->mode_success = 1; + return NULL; +} + +static PKCS11_SLOT *find_token(PKCS11_CTX *ctx, PKCS11_SLOT *slots, + unsigned int nslots, const char *label) +{ + PKCS11_SLOT *slot; + + for (slot = PKCS11_find_token(ctx, slots, nslots); slot; + slot = PKCS11_find_next_token(ctx, slots, nslots, slot)) { + if (slot->token && slot->token->label && + strcmp(slot->token->label, label) == 0) + return slot; + } + return NULL; +} + +int main(int argc, char **argv) +{ + struct stress_state state; + PKCS11_CTX *ctx = NULL; + PKCS11_SLOT *slots = NULL, *slot; + PKCS11_KEY *keys = NULL; + pthread_t sign_id, keygen_id, mode_id; + unsigned char initial_digest[32] = {0}; + unsigned char initial_signature[512]; + unsigned int initial_signature_len = sizeof(initial_signature); + unsigned int nslots = 0, nkeys = 0, i; + int sign_created = 0, keygen_created = 0, mode_created = 0; + int result = EXIT_FAILURE; + + if (argc != 4) { + fprintf(stderr, "usage: %s module token-label pin\n", argv[0]); + return EXIT_FAILURE; + } + + ctx = PKCS11_CTX_new(); + if (!ctx || PKCS11_CTX_load(ctx, argv[1]) != 0 || + PKCS11_enumerate_slots(ctx, &slots, &nslots) != 0) { + fprintf(stderr, "could not initialize the PKCS#11 context\n"); + goto out; + } + slot = find_token(ctx, slots, nslots, argv[2]); + if (!slot || PKCS11_open_session(slot, 0) != 0 || + PKCS11_login(slot, 0, argv[3]) != 0 || + PKCS11_enumerate_keys(slot->token, &keys, &nkeys) != 0) { + fprintf(stderr, "could not initialize the test token\n"); + goto out; + } + for (i = 0; i < nkeys; i++) { + if (PKCS11_get_key_type(&keys[i]) == EVP_PKEY_RSA) + break; + } + if (i == nkeys) { + fprintf(stderr, "no RSA private key available\n"); + goto out; + } + if (PKCS11_sign(NID_sha256, initial_digest, sizeof(initial_digest), + initial_signature, &initial_signature_len, &keys[i]) != 1) { + fprintf(stderr, "initial RSA signing operation failed\n"); + ERR_print_errors_fp(stderr); + goto out; + } + + memset(&state, 0, sizeof(state)); + state.slot = slot; + state.pin = argv[3]; + pthread_mutex_init(&state.lock, NULL); + pthread_cond_init(&state.cond, NULL); + if (pthread_create(&sign_id, NULL, sign_thread, &state) != 0) + goto threads_out; + sign_created = 1; + if (pthread_create(&keygen_id, NULL, keygen_thread, &state) != 0) + goto threads_out; + keygen_created = 1; + if (pthread_create(&mode_id, NULL, mode_thread, &state) != 0) + goto threads_out; + mode_created = 1; + + pthread_mutex_lock(&state.lock); + state.start = 1; + pthread_cond_broadcast(&state.cond); + pthread_mutex_unlock(&state.lock); + pthread_join(sign_id, NULL); + sign_created = 0; + pthread_join(keygen_id, NULL); + keygen_created = 0; + pthread_join(mode_id, NULL); + mode_created = 0; + if (state.sign_successes == SIGN_SUCCESSES && + state.keygen_success && state.mode_success) { + printf("session-pool stress test passed\n"); + result = EXIT_SUCCESS; + } else { + fprintf(stderr, + "stress test incomplete: signs=%d keygen=%d modes=%d\n", + state.sign_successes, state.keygen_success, + state.mode_success); + } + +threads_out: + if (sign_created || keygen_created || mode_created) { + pthread_mutex_lock(&state.lock); + state.start = 1; + pthread_cond_broadcast(&state.cond); + pthread_mutex_unlock(&state.lock); + } + if (sign_created) + pthread_join(sign_id, NULL); + if (keygen_created) + pthread_join(keygen_id, NULL); + if (mode_created) + pthread_join(mode_id, NULL); + pthread_cond_destroy(&state.cond); + pthread_mutex_destroy(&state.lock); +out: + if (slots) + PKCS11_release_all_slots(ctx, slots, nslots); + if (ctx) { + PKCS11_CTX_unload(ctx); + PKCS11_CTX_free(ctx); + } + return result; +} + +#else /* HAVE_PTHREAD */ + +int main(void) +{ + fprintf(stderr, "Skipped: pthread support not available\n"); + return 77; +} + +#endif /* HAVE_PTHREAD */ + +/* vim: set noexpandtab: */ diff --git a/tests/session-pool-stress.softhsm b/tests/session-pool-stress.softhsm new file mode 100755 index 00000000..575737b8 --- /dev/null +++ b/tests/session-pool-stress.softhsm @@ -0,0 +1,57 @@ +#!/bin/bash + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +outdir="output.$$" +child="" +watchdog="" + +. "${srcdir}/common.sh" + +cleanup_stress_test() +{ + if [[ -n "${child}" ]]; then + kill "${child}" 2>/dev/null || true + fi + if [[ -n "${watchdog}" ]]; then + kill "${watchdog}" 2>/dev/null || true + fi + cleanup + rm -rf "${outdir}" +} +trap cleanup_stress_test EXIT + +init_db +init_card "session-pool-stress" +generate_rsa_key_pair "stress-signing-key" "session-pool-stress" + +${WRAPPER} ./session-pool-stress \ + "${MODULE}" "session-pool-stress" "${PIN}" & +child=$! +( + sleep 60 + kill -TERM "${child}" 2>/dev/null || true +) & +watchdog=$! + +wait "${child}" +rc=$? +child="" +kill "${watchdog}" 2>/dev/null || true +wait "${watchdog}" 2>/dev/null || true +watchdog="" + +if [[ ${rc} -ne 0 ]]; then + echo "Concurrent session-pool stress test failed or timed out." + exit 1 +fi + +exit 0 diff --git a/tests/session-pool-test.c b/tests/session-pool-test.c new file mode 100644 index 00000000..20d0147b --- /dev/null +++ b/tests/session-pool-test.c @@ -0,0 +1,613 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _POSIX_C_SOURCE 200809L +#include "libp11-int.h" + +#include +#include +#include +#include + +/* p11_slot.c is compiled into this focused unit test. */ +void pkcs11_destroy_keys(PKCS11_SLOT_private *slot, unsigned int type) +{ + (void)slot; + (void)type; +} + +void pkcs11_destroy_certs(PKCS11_SLOT_private *slot) +{ + (void)slot; +} + +char *pkcs11_strdup(char *text, size_t size) +{ + (void)text; + (void)size; + return NULL; +} + +int pkcs11_atomic_add(int *value, int amount, pthread_mutex_t *lock) +{ + int result; + + pthread_mutex_lock(lock); + *value += amount; + result = *value; + pthread_mutex_unlock(lock); + return result; +} + +void ERR_CKR_error(int function, int reason, char *file, int line) +{ + (void)function; + (void)reason; + (void)file; + (void)line; +} + +void ERR_P11_error(int function, int reason, char *file, int line) +{ + (void)function; + (void)reason; + (void)file; + (void)line; +} + +#ifdef HAVE_PTHREAD + +#define TEST_TIMEOUT_SECONDS 5 + +void session_pool_test_delay_transition_unlock(PKCS11_SLOT_private *slot); + +struct fake_module_state { + pthread_mutex_t lock; + pthread_cond_t cond; + PKCS11_SLOT_private *slot; + CK_SESSION_HANDLE next_session; + unsigned int open_sessions; + unsigned int max_sessions; + int logged_in; + int pause_login; + int login_entered; + CK_RV login_result; + int close_while_in_use; + int reload_reset_missing; + int expect_reload_reset; +}; + +struct thread_state { + pthread_mutex_t lock; + pthread_cond_t cond; + PKCS11_SLOT_private *slot; + CK_SESSION_HANDLE session; + int done; + int acquired; + int delay_transition_unlock; + int rv; + int mode; +}; + +static struct fake_module_state fake; + +static CK_RV fake_open_session(CK_SLOT_ID slot_id, CK_FLAGS flags, + CK_VOID_PTR application, CK_NOTIFY notify, + CK_SESSION_HANDLE_PTR session) +{ + (void)slot_id; + (void)flags; + (void)application; + (void)notify; + + pthread_mutex_lock(&fake.lock); + if (fake.expect_reload_reset && + (fake.slot->transition_active != 0 || + fake.slot->sessions_in_use != 0 || + fake.slot->num_sessions != 0 || + fake.slot->session_head != 0 || + fake.slot->session_tail != 0)) + fake.reload_reset_missing = 1; + fake.expect_reload_reset = 0; + if (fake.open_sessions >= fake.max_sessions) { + pthread_mutex_unlock(&fake.lock); + return CKR_SESSION_COUNT; + } + *session = ++fake.next_session; + fake.open_sessions++; + pthread_mutex_unlock(&fake.lock); + return CKR_OK; +} + +static CK_RV fake_close_session(CK_SESSION_HANDLE session) +{ + (void)session; + pthread_mutex_lock(&fake.lock); + if (fake.open_sessions > 0) + fake.open_sessions--; + pthread_mutex_unlock(&fake.lock); + return CKR_OK; +} + +static CK_RV fake_close_all_sessions(CK_SLOT_ID slot_id) +{ + (void)slot_id; + pthread_mutex_lock(&fake.lock); + if (fake.slot->sessions_in_use != 0) + fake.close_while_in_use = 1; + fake.open_sessions = 0; + fake.logged_in = 0; + pthread_mutex_unlock(&fake.lock); + return CKR_OK; +} + +static CK_RV fake_get_session_info(CK_SESSION_HANDLE session, + CK_SESSION_INFO_PTR info) +{ + (void)session; + memset(info, 0, sizeof(*info)); + return CKR_OK; +} + +static CK_RV fake_login(CK_SESSION_HANDLE session, CK_USER_TYPE user_type, + CK_UTF8CHAR_PTR pin, CK_ULONG pin_len) +{ + CK_RV result; + + (void)session; + (void)user_type; + (void)pin; + (void)pin_len; + + pthread_mutex_lock(&fake.lock); + fake.login_entered = 1; + pthread_cond_broadcast(&fake.cond); + while (fake.pause_login) + pthread_cond_wait(&fake.cond, &fake.lock); + result = fake.login_result; + if (result == CKR_OK) + fake.logged_in = 1; + pthread_mutex_unlock(&fake.lock); + return result; +} + +static void deadline_after(struct timespec *deadline, long milliseconds) +{ + clock_gettime(CLOCK_REALTIME, deadline); + deadline->tv_sec += milliseconds / 1000; + deadline->tv_nsec += (milliseconds % 1000) * 1000000L; + if (deadline->tv_nsec >= 1000000000L) { + deadline->tv_sec++; + deadline->tv_nsec -= 1000000000L; + } +} + +static int wait_thread_done(struct thread_state *state, long milliseconds) +{ + struct timespec deadline; + int done; + + deadline_after(&deadline, milliseconds); + pthread_mutex_lock(&state->lock); + while (!state->done) { + if (pthread_cond_timedwait(&state->cond, &state->lock, + &deadline) != 0) + break; + } + done = state->done; + pthread_mutex_unlock(&state->lock); + return done; +} + +static int thread_done(struct thread_state *state) +{ + int done; + + pthread_mutex_lock(&state->lock); + done = state->done; + pthread_mutex_unlock(&state->lock); + return done; +} + +static int wait_login_entered(long milliseconds) +{ + struct timespec deadline; + int entered; + + deadline_after(&deadline, milliseconds); + pthread_mutex_lock(&fake.lock); + while (!fake.login_entered) { + if (pthread_cond_timedwait(&fake.cond, &fake.lock, + &deadline) != 0) + break; + } + entered = fake.login_entered; + pthread_mutex_unlock(&fake.lock); + return entered; +} + +static void thread_state_init(struct thread_state *state, + PKCS11_SLOT_private *slot) +{ + memset(state, 0, sizeof(*state)); + state->slot = slot; + pthread_mutex_init(&state->lock, NULL); + pthread_cond_init(&state->cond, NULL); +} + +static void thread_state_destroy(struct thread_state *state) +{ + pthread_cond_destroy(&state->cond); + pthread_mutex_destroy(&state->lock); +} + +static void thread_complete(struct thread_state *state, int rv, int acquired) +{ + pthread_mutex_lock(&state->lock); + state->rv = rv; + state->acquired = acquired; + state->done = 1; + pthread_cond_broadcast(&state->cond); + pthread_mutex_unlock(&state->lock); +} + +static void *keygen_thread(void *arg) +{ + struct thread_state *state = arg; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + int rv; + + if (state->delay_transition_unlock) + session_pool_test_delay_transition_unlock(state->slot); + rv = pkcs11_session_pool_acquire_keygen(state->slot, &session); + if (rv == 0) + pkcs11_session_pool_release(state->slot, session); + thread_complete(state, rv, rv == 0); + return NULL; +} + +static void *acquire_thread(void *arg) +{ + struct thread_state *state = arg; + CK_SESSION_HANDLE session = CK_INVALID_HANDLE; + int rv; + + rv = pkcs11_session_pool_acquire(state->slot, 0, &session); + if (rv == 0) + pkcs11_session_pool_release(state->slot, session); + thread_complete(state, rv, rv == 0); + return NULL; +} + +static void *mode_thread(void *arg) +{ + struct thread_state *state = arg; + + thread_complete(state, + pkcs11_session_pool_set_mode(state->slot, state->mode), 0); + return NULL; +} + +static void fake_init(PKCS11_SLOT_private *slot, unsigned int max_sessions) +{ + memset(&fake, 0, sizeof(fake)); + fake.slot = slot; + fake.max_sessions = max_sessions; + pthread_mutex_init(&fake.lock, NULL); + pthread_cond_init(&fake.cond, NULL); +} + +static void fake_destroy(void) +{ + pthread_cond_destroy(&fake.cond); + pthread_mutex_destroy(&fake.lock); +} + +static void slot_init(PKCS11_SLOT_private *slot, PKCS11_CTX_private *ctx, + CK_FUNCTION_LIST_PTR method, CK_SESSION_HANDLE *pool, + unsigned int max_sessions) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->method = method; + memset(slot, 0, sizeof(*slot)); + slot->ctx = ctx; + slot->rw_mode = -1; + slot->logged_in = -1; + slot->session_pool = pool; + slot->session_poolsize = max_sessions + 1; + slot->max_sessions = max_sessions; + pthread_mutex_init(&slot->lock, NULL); + pthread_cond_init(&slot->cond, NULL); +} + +static void slot_destroy(PKCS11_SLOT_private *slot) +{ + if (slot->prev_pin) { + OPENSSL_cleanse(slot->prev_pin, strlen(slot->prev_pin)); + OPENSSL_free(slot->prev_pin); + } + pthread_cond_destroy(&slot->cond); + pthread_mutex_destroy(&slot->lock); +} + +static int wait_for_transition(PKCS11_SLOT_private *slot, long milliseconds) +{ + struct timespec delay = {0, 1000000L}; + long elapsed; + int active; + + for (elapsed = 0; elapsed < milliseconds; elapsed++) { + pthread_mutex_lock(&slot->lock); + active = slot->transition_active; + pthread_mutex_unlock(&slot->lock); + if (active) + return 1; + nanosleep(&delay, NULL); + } + return 0; +} + +static int transition_waiter_test(CK_FUNCTION_LIST_PTR method) +{ + PKCS11_CTX_private ctx; + PKCS11_SLOT_private slot; + CK_SESSION_HANDLE pool[2], held; + struct thread_state keygen, mode; + pthread_t keygen_id, mode_id; + struct timespec delay = {0, 200000000L}; + int failed = 0; + + slot_init(&slot, &ctx, method, pool, 1); + fake_init(&slot, 1); + slot.rw_mode = 1; + if (pkcs11_session_pool_acquire(&slot, 1, &held) != 0) { + fprintf(stderr, "could not acquire the initial session\n"); + failed = 1; + goto out; + } + + thread_state_init(&keygen, &slot); + thread_state_init(&mode, &slot); + keygen.delay_transition_unlock = 1; + mode.mode = 0; + pthread_create(&keygen_id, NULL, keygen_thread, &keygen); + if (!wait_for_transition(&slot, 1000)) { + fprintf(stderr, "key-generation transition did not start\n"); + failed = 1; + goto threads_out; + } + pthread_create(&mode_id, NULL, mode_thread, &mode); + /* Give the second transition time to wait on the shared condition. */ + nanosleep(&delay, NULL); + pkcs11_session_pool_release(&slot, held); + + if (!wait_thread_done(&keygen, TEST_TIMEOUT_SECONDS * 1000) || + !wait_thread_done(&mode, TEST_TIMEOUT_SECONDS * 1000)) { + fprintf(stderr, "session-pool transition waiters deadlocked\n"); + failed = 1; + goto threads_out; + } + pthread_join(keygen_id, NULL); + pthread_join(mode_id, NULL); + if (keygen.rv != 0 || mode.rv != 0 || fake.close_while_in_use) { + fprintf(stderr, "session-pool transition waiter test failed\n"); + failed = 1; + } + +threads_out: + if (!thread_done(&keygen) || !thread_done(&mode)) + return 1; + thread_state_destroy(&keygen); + thread_state_destroy(&mode); +out: + fake_destroy(); + slot_destroy(&slot); + return failed; +} + +static int relogin_gate_test(CK_FUNCTION_LIST_PTR method) +{ + PKCS11_CTX_private ctx; + PKCS11_SLOT_private slot; + CK_SESSION_HANDLE pool[3]; + struct thread_state keygen, acquire; + pthread_t keygen_id, acquire_id; + unsigned int open_sessions; + int failed = 0; + + slot_init(&slot, &ctx, method, pool, 2); + fake_init(&slot, 2); + slot.rw_mode = 0; + if (pkcs11_login(&slot, 0, "1234") != 0) { + fprintf(stderr, "could not establish the initial login\n"); + failed = 1; + goto out; + } + + thread_state_init(&keygen, &slot); + thread_state_init(&acquire, &slot); + pthread_mutex_lock(&fake.lock); + fake.login_entered = 0; + fake.pause_login = 1; + pthread_mutex_unlock(&fake.lock); + pthread_create(&keygen_id, NULL, keygen_thread, &keygen); + if (!wait_login_entered(1000)) { + fprintf(stderr, "key-generation relogin did not start\n"); + failed = 1; + goto threads_out; + } + + pthread_create(&acquire_id, NULL, acquire_thread, &acquire); + if (wait_thread_done(&acquire, 200)) { + fprintf(stderr, "normal acquisition passed the relogin gate\n"); + failed = 1; + } + pthread_mutex_lock(&fake.lock); + open_sessions = fake.open_sessions; + fake.pause_login = 0; + pthread_cond_broadcast(&fake.cond); + pthread_mutex_unlock(&fake.lock); + if (open_sessions != 1) { + fprintf(stderr, "normal acquisition reached the module during relogin\n"); + failed = 1; + } + + if (!wait_thread_done(&keygen, TEST_TIMEOUT_SECONDS * 1000) || + !wait_thread_done(&acquire, TEST_TIMEOUT_SECONDS * 1000)) { + fprintf(stderr, "relogin gate test deadlocked\n"); + return 1; + } + pthread_join(keygen_id, NULL); + pthread_join(acquire_id, NULL); + if (keygen.rv != 0 || acquire.rv != 0 || + fake.close_while_in_use) { + fprintf(stderr, "relogin gate operations failed\n"); + failed = 1; + } + +threads_out: + if (!thread_done(&keygen) || !thread_done(&acquire)) + return 1; + thread_state_destroy(&keygen); + thread_state_destroy(&acquire); +out: + fake_destroy(); + slot_destroy(&slot); + return failed; +} + +static int transition_error_test(CK_FUNCTION_LIST_PTR method) +{ + PKCS11_CTX_private ctx; + PKCS11_SLOT_private slot; + CK_SESSION_HANDLE pool[3], session; + int failed = 0; + + slot_init(&slot, &ctx, method, pool, 2); + fake_init(&slot, 2); + slot.rw_mode = 0; + if (pkcs11_login(&slot, 0, "1234") != 0) { + fprintf(stderr, "could not establish login for error test\n"); + failed = 1; + goto out; + } + pthread_mutex_lock(&fake.lock); + fake.login_result = CKR_PIN_INCORRECT; + pthread_mutex_unlock(&fake.lock); + if (pkcs11_session_pool_acquire_keygen(&slot, &session) == 0) { + fprintf(stderr, "key-generation relogin unexpectedly succeeded\n"); + pkcs11_session_pool_release(&slot, session); + failed = 1; + } + pthread_mutex_lock(&slot.lock); + if (slot.transition_active != 0 || slot.sessions_in_use != 0) + failed = 1; + pthread_mutex_unlock(&slot.lock); + if (failed) + fprintf(stderr, "failed transition did not restore pool state\n"); + + pthread_mutex_lock(&fake.lock); + fake.login_result = CKR_OK; + pthread_mutex_unlock(&fake.lock); + if (pkcs11_session_pool_acquire(&slot, 1, &session) != 0) { + fprintf(stderr, "pool did not recover after failed relogin\n"); + failed = 1; + } else { + pkcs11_session_pool_release(&slot, session); + } + +out: + fake_destroy(); + slot_destroy(&slot); + return failed; +} + +static int fork_reload_test(CK_FUNCTION_LIST_PTR method) +{ + PKCS11_CTX_private ctx; + PKCS11_SLOT_private slot; + CK_SESSION_HANDLE pool[3]; + int failed = 0; + + slot_init(&slot, &ctx, method, pool, 2); + fake_init(&slot, 2); + slot.rw_mode = 0; + if (pkcs11_login(&slot, 0, "1234") != 0) { + fprintf(stderr, "could not establish login before reload\n"); + failed = 1; + goto out; + } + + /* Model inherited parent state. No parent thread or lease survives. */ + slot.transition_active = 1; + slot.sessions_in_use = 1; + slot.num_sessions = 2; + slot.session_head = 1; + slot.session_tail = 2; + pthread_mutex_lock(&fake.lock); + fake.expect_reload_reset = 1; + pthread_mutex_unlock(&fake.lock); + if (pkcs11_reload_slot(&slot) != 0) { + fprintf(stderr, "slot reload failed\n"); + failed = 1; + } + if (slot.transition_active != 0 || slot.sessions_in_use != 0 || + fake.reload_reset_missing) { + fprintf(stderr, "slot reload inherited stale transition state\n"); + failed = 1; + } + +out: + fake_destroy(); + slot_destroy(&slot); + return failed; +} + +int main(void) +{ + CK_FUNCTION_LIST method; + int failed = 0; + + memset(&method, 0, sizeof(method)); + method.C_OpenSession = fake_open_session; + method.C_CloseSession = fake_close_session; + method.C_CloseAllSessions = fake_close_all_sessions; + method.C_GetSessionInfo = fake_get_session_info; + method.C_Login = fake_login; + + failed = transition_waiter_test(&method); + if (!failed) + failed = relogin_gate_test(&method); + if (!failed) + failed = transition_error_test(&method); + if (!failed) + failed = fork_reload_test(&method); + if (failed) + return EXIT_FAILURE; + printf("session-pool concurrency tests passed\n"); + return EXIT_SUCCESS; +} + +#else /* HAVE_PTHREAD */ + +int main(void) +{ + fprintf(stderr, "Skipped: pthread support not available\n"); + return 77; +} + +#endif /* HAVE_PTHREAD */ + +/* vim: set noexpandtab: */ diff --git a/tests/session-pool-under-test.c b/tests/session-pool-under-test.c new file mode 100644 index 00000000..7d9f70d9 --- /dev/null +++ b/tests/session-pool-under-test.c @@ -0,0 +1,53 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Build the session-pool implementation with a controllable PKCS#11 method + * table for the focused concurrency test. + */ + +#define _POSIX_C_SOURCE 200809L +#include "libp11-int.h" +#include + +#ifdef HAVE_PTHREAD +static PKCS11_SLOT_private *delayed_slot; +static pthread_t delayed_thread; +static int delay_armed; + +/* Force the transition-owner scheduling window used by the mixed-wakeup + * regression: another transition queues before the owner selects a session. */ +void session_pool_test_delay_transition_unlock(PKCS11_SLOT_private *slot) +{ + delayed_slot = slot; + delayed_thread = pthread_self(); + delay_armed = 1; +} + +static int session_pool_test_mutex_unlock(pthread_mutex_t *mutex) +{ + struct timespec delay = {0, 100000000L}; + int delay_this_unlock, rv; + + delay_this_unlock = delay_armed && delayed_slot && + mutex == &delayed_slot->lock && delayed_slot->transition_active && + pthread_equal(delayed_thread, pthread_self()); + rv = pthread_mutex_unlock(mutex); + if (delay_this_unlock) { + delay_armed = 0; + nanosleep(&delay, NULL); + } + return rv; +} + +#define pthread_mutex_unlock session_pool_test_mutex_unlock +#else /* HAVE_PTHREAD */ +void session_pool_test_delay_transition_unlock(PKCS11_SLOT_private *slot) +{ + (void)slot; +} +#endif /* HAVE_PTHREAD */ + +#include "../src/p11_slot.c"