Skip to content

Commit a5ac431

Browse files
committed
fix(connection): Make set_config validator replacement transactional
s2n_connection_set_config wiped the connection's x509_validator before initializing a replacement from the new config. If the replacement init failed (e.g. X509_STORE_CTX_new returning NULL under OOM, or an invalid max chain depth), the connection was left with a wiped validator while conn->config still referenced the old config. A subsequent handshake would fail in cert validation. Stage the new validator into a stack-local struct. If any init step fails, wipe the local and propagate the error, leaving the connection's existing validator intact. Only swap on full success. Add a regression test that triggers a validator init failure via an invalid max chain depth and verifies the old validator is preserved.
1 parent a1c3f65 commit a5ac431

2 files changed

Lines changed: 85 additions & 16 deletions

File tree

tests/unit/s2n_config_test.c

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "tls/s2n_security_policies.h"
3333
#include "tls/s2n_tls.h"
3434
#include "tls/s2n_tls13.h"
35+
#include "tls/s2n_x509_validator.h"
3536
#include "unstable/npn.h"
3637
#include "utils/s2n_map.h"
3738

@@ -92,7 +93,7 @@ int main(int argc, char **argv)
9293

9394
/* s2n_config_new() matches s2n_fetch_default_config() */
9495
if (default_config->security_policy != config->security_policy) {
95-
/* one possible cause for this is attempting to includes s2n_config.c
96+
/* one possible cause for this is attempting to includes s2n_config.c
9697
* for access to internal `static` functions. This causes two copies
9798
* of the default config to be created. The default_config in *this*
9899
* unit of translation doesn't get properly initialized. */
@@ -1107,7 +1108,7 @@ int main(int argc, char **argv)
11071108
struct s2n_security_policy rfc9151_applied_locally = security_policy_20250429;
11081109
rfc9151_applied_locally.certificate_preferences_apply_locally = true;
11091110

1110-
/* rfc9151 doesn't allow SHA256 signatures, but does allow SHA384 signatures,
1111+
/* rfc9151 doesn't allow SHA256 signatures, but does allow SHA384 signatures,
11111112
* so ecdsa_p384_sha256 is invalid and ecdsa_p384_sha384 is valid */
11121113

11131114
/* valid certs are accepted */
@@ -1134,8 +1135,8 @@ int main(int argc, char **argv)
11341135

11351136
/* certs in default_certs_by_type are validated */
11361137
{
1137-
/* s2n_config_set_cert_chain_and_key_defaults populates default_certs_by_type
1138-
* but doesn't populate domain_name_to_cert_map
1138+
/* s2n_config_set_cert_chain_and_key_defaults populates default_certs_by_type
1139+
* but doesn't populate domain_name_to_cert_map
11391140
*/
11401141
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new_minimal(), s2n_config_ptr_free);
11411142
EXPECT_SUCCESS(s2n_config_set_cert_chain_and_key_defaults(config, &invalid_cert, 1));
@@ -1296,5 +1297,52 @@ int main(int argc, char **argv)
12961297
EXPECT_FAILURE_WITH_ERRNO(s2n_config_set_subscriber(NULL, (s2n_event_on_handshake_cb) &fake_callback), S2N_ERR_NULL);
12971298
};
12981299

1300+
/* Test: s2n_connection_set_config preserves the old validator on failure
1301+
*
1302+
* Regression test: s2n_connection_set_config wipes the connection's
1303+
* x509_validator before initializing a new one from the incoming config.
1304+
* If the new validator's initialization fails (e.g. set_max_chain_depth
1305+
* with an invalid depth), the connection was left with a wiped validator
1306+
* while conn->config still referenced the old config. The fix stages
1307+
* the new validator into a local and only swaps on full success.
1308+
*/
1309+
{
1310+
DEFER_CLEANUP(struct s2n_config *config_old = s2n_config_new(), s2n_config_ptr_free);
1311+
EXPECT_NOT_NULL(config_old);
1312+
1313+
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
1314+
s2n_connection_ptr_free);
1315+
EXPECT_NOT_NULL(conn);
1316+
EXPECT_SUCCESS(s2n_connection_set_config(conn, config_old));
1317+
EXPECT_EQUAL(conn->config, config_old);
1318+
1319+
/* The validator should be in the INIT state after a successful set_config. */
1320+
EXPECT_EQUAL(conn->x509_validator.state, INIT);
1321+
1322+
/* Create a second config that will cause set_config to fail during
1323+
* validator initialization. Setting max_verify_cert_chain_depth to 0
1324+
* (invalid) with the flag enabled triggers the failure inside
1325+
* s2n_x509_validator_set_max_chain_depth.
1326+
*/
1327+
DEFER_CLEANUP(struct s2n_config *config_bad = s2n_config_new(), s2n_config_ptr_free);
1328+
EXPECT_NOT_NULL(config_bad);
1329+
config_bad->max_verify_cert_chain_depth = 0;
1330+
config_bad->max_verify_cert_chain_depth_set = 1;
1331+
1332+
/* set_config should fail because of the invalid chain depth. */
1333+
EXPECT_FAILURE_WITH_ERRNO(s2n_connection_set_config(conn, config_bad),
1334+
S2N_ERR_INVALID_ARGUMENT);
1335+
1336+
/* The connection must still reference the old config. */
1337+
EXPECT_EQUAL(conn->config, config_old);
1338+
1339+
/* The validator must still be functional (INIT state, not UNINIT).
1340+
* Before the fix, the validator was wiped to UNINIT before the
1341+
* failed init attempt, leaving the connection in an inconsistent
1342+
* state.
1343+
*/
1344+
EXPECT_EQUAL(conn->x509_validator.state, INIT);
1345+
};
1346+
12991347
END_TEST();
13001348
}

tls/s2n_connection.c

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,11 @@ struct s2n_connection *s2n_connection_new(s2n_mode mode)
8484
conn->mode = mode;
8585

8686
/* Allocate the fixed-size stuffers */
87-
blob = (struct s2n_blob){ 0 };
87+
blob = (struct s2n_blob) { 0 };
8888
PTR_GUARD_POSIX(s2n_blob_init(&blob, conn->alert_in_data, S2N_ALERT_LENGTH));
8989
PTR_GUARD_POSIX(s2n_stuffer_init(&conn->alert_in, &blob));
9090

91-
blob = (struct s2n_blob){ 0 };
91+
blob = (struct s2n_blob) { 0 };
9292
PTR_GUARD_POSIX(s2n_blob_init(&blob, conn->ticket_ext_data, S2N_TLS12_TICKET_SIZE_IN_BYTES));
9393
PTR_GUARD_POSIX(s2n_stuffer_init(&conn->client_ticket_to_decrypt, &blob));
9494

@@ -99,7 +99,7 @@ struct s2n_connection *s2n_connection_new(s2n_mode mode)
9999
/* Initialize the growable stuffers. Zero length at first, but the resize
100100
* in _wipe will fix that
101101
*/
102-
blob = (struct s2n_blob){ 0 };
102+
blob = (struct s2n_blob) { 0 };
103103
PTR_GUARD_POSIX(s2n_blob_init(&blob, conn->header_in_data, S2N_TLS_RECORD_HEADER_LENGTH));
104104
PTR_GUARD_POSIX(s2n_stuffer_init(&conn->header_in, &blob));
105105
PTR_GUARD_POSIX(s2n_stuffer_growable_alloc(&conn->out, 0));
@@ -309,12 +309,24 @@ int s2n_connection_set_config(struct s2n_connection *conn, struct s2n_config *co
309309
POSIX_BAIL(S2N_ERR_TOO_MANY_CERTIFICATES);
310310
}
311311

312-
s2n_x509_validator_wipe(&conn->x509_validator);
312+
/* Build the new validator into a local so that the connection's existing
313+
* validator is not destroyed until the new one is fully initialized.
314+
* Without this staging, a failure mid-init (e.g. X509_STORE_CTX_new
315+
* returning NULL under OOM) would leave conn->x509_validator wiped
316+
* while conn->config still references the old config.
317+
*/
318+
struct s2n_x509_validator new_validator = { 0 };
313319

314320
if (config->disable_x509_validation) {
315-
POSIX_GUARD(s2n_x509_validator_init_no_x509_validation(&conn->x509_validator));
321+
POSIX_GUARD(s2n_x509_validator_init_no_x509_validation(&new_validator));
316322
} else {
317-
POSIX_GUARD(s2n_x509_validator_init(&conn->x509_validator, &config->trust_store, config->check_ocsp));
323+
int ret = s2n_x509_validator_init(&new_validator, &config->trust_store, config->check_ocsp);
324+
if (ret != S2N_SUCCESS) {
325+
/* init may have partially populated new_validator before failing */
326+
s2n_x509_validator_wipe(&new_validator);
327+
POSIX_GUARD(ret);
328+
}
329+
318330
if (!conn->verify_host_fn_overridden) {
319331
if (config->verify_host_fn != NULL) {
320332
conn->verify_host_fn = config->verify_host_fn;
@@ -326,9 +338,18 @@ int s2n_connection_set_config(struct s2n_connection *conn, struct s2n_config *co
326338
}
327339

328340
if (config->max_verify_cert_chain_depth_set) {
329-
POSIX_GUARD(s2n_x509_validator_set_max_chain_depth(&conn->x509_validator, config->max_verify_cert_chain_depth));
341+
ret = s2n_x509_validator_set_max_chain_depth(&new_validator, config->max_verify_cert_chain_depth);
342+
if (ret != S2N_SUCCESS) {
343+
s2n_x509_validator_wipe(&new_validator);
344+
POSIX_GUARD(ret);
345+
}
330346
}
331347
}
348+
349+
/* New validator is fully initialized. Swap it in. */
350+
s2n_x509_validator_wipe(&conn->x509_validator);
351+
conn->x509_validator = new_validator;
352+
332353
conn->tickets_to_send = config->initial_tickets_to_send;
333354

334355
if (conn->psk_params.psk_list.len == 0 && !conn->psk_mode_overridden) {
@@ -369,7 +390,7 @@ int s2n_connection_set_config(struct s2n_connection *conn, struct s2n_config *co
369390
* However, the s2n_config_set_verification_ca_location behavior predates client authentication
370391
* support for OCSP stapling, so could only affect whether clients requested OCSP stapling. We
371392
* therefore only have to maintain the legacy behavior for clients, not servers.
372-
*
393+
*
373394
* Note: The Rust bindings do not maintain the legacy behavior.
374395
*/
375396
conn->request_ocsp_status = config->ocsp_status_requested_by_user;
@@ -1020,7 +1041,7 @@ int s2n_connection_get_key_exchange_group(struct s2n_connection *conn, const cha
10201041
POSIX_ENSURE_REF(conn);
10211042
POSIX_ENSURE_REF(group_name);
10221043

1023-
/* s2n_connection_get_curve returns only the ECDH curve portion of a named group, even if
1044+
/* s2n_connection_get_curve returns only the ECDH curve portion of a named group, even if
10241045
the negotiated group was a hybrid PQ key exchange also containing a KEM. Therefore,
10251046
we use the result of s2n_connection_get_kem_group_name if the connection supports PQ. */
10261047
if (s2n_tls13_pq_hybrid_supported(conn)) {
@@ -1256,11 +1277,11 @@ uint64_t s2n_connection_get_delay(struct s2n_connection *conn)
12561277

12571278
/* s2n-tls has a random delay that will trigger for sensitive errors. This is a mitigation
12581279
* for possible timing sidechannels.
1259-
*
1280+
*
12601281
* The historical sidechannel that inspired s2n-tls blinding was the Lucky 13 attack, which takes
12611282
* advantage of potential timing differences when removing padding from a record encrypted in CBC mode.
1262-
* The attack is only theoretical in TLS; the attack criteria is unlikely to ever occur
1263-
* (See: Fardan, N. J. A., & Paterson, K. G. (2013, May 1). Lucky Thirteen: Breaking the TLS and
1283+
* The attack is only theoretical in TLS; the attack criteria is unlikely to ever occur
1284+
* (See: Fardan, N. J. A., & Paterson, K. G. (2013, May 1). Lucky Thirteen: Breaking the TLS and
12641285
* DTLS Record Protocols.) However, we still include blinding to provide a defense in depth mitigation.
12651286
*/
12661287
S2N_RESULT s2n_connection_calculate_blinding(struct s2n_connection *conn, int64_t *min, int64_t *max)

0 commit comments

Comments
 (0)