BUG/MINOR: quic: Wrong cluster secret initialization

The function generate_random_cluster_secret() which initializes the cluster secret
when not supplied by configuration is buggy. There 1/256 that the cluster secret
string is empty.

To fix this, one stores the cluster as a reduced size first 128 bits of its own
SHA1 (160 bits) digest, if defined by configuration. If this is not the case, it
is initialized with a 128 bits random value. Furthermore, thus the cluster secret
is always initialized.

As the cluster secret is always initialized, there are several tests which
are for now on useless. This patch removes such tests (if(global.cluster_secret))
in the QUIC code part and at parsing time: no need to check that a cluster
secret was initialized with "quic-force-retry" option.

Must be backported as far as 2.6.
This commit is contained in:
Frédéric Lécaille 2023-09-07 18:43:52 +02:00
parent 15e591b6e0
commit 292dfdd78d
7 changed files with 43 additions and 64 deletions

View File

@ -85,6 +85,8 @@
#define GTUNE_LISTENER_MQ_OPT (1<<28) #define GTUNE_LISTENER_MQ_OPT (1<<28)
#define GTUNE_LISTENER_MQ_ANY (GTUNE_LISTENER_MQ_FAIR | GTUNE_LISTENER_MQ_OPT) #define GTUNE_LISTENER_MQ_ANY (GTUNE_LISTENER_MQ_FAIR | GTUNE_LISTENER_MQ_OPT)
extern int cluster_secret_isset; /* non zero means a cluster secret was initiliazed */
/* SSL server verify mode */ /* SSL server verify mode */
enum { enum {
SSL_SERVER_VERIFY_NONE = 0, SSL_SERVER_VERIFY_NONE = 0,
@ -139,7 +141,7 @@ struct global {
char *log_send_hostname; /* set hostname in syslog header */ char *log_send_hostname; /* set hostname in syslog header */
char *server_state_base; /* path to a directory where server state files can be found */ char *server_state_base; /* path to a directory where server state files can be found */
char *server_state_file; /* path to the file where server states are loaded from */ char *server_state_file; /* path to the file where server states are loaded from */
char *cluster_secret; /* Secret defined as ASCII string */ unsigned char cluster_secret[16]; /* 128 bits of an SHA1 digest of a secret defined as ASCII string */
struct { struct {
int maxpollevents; /* max number of poll events at once */ int maxpollevents; /* max number of poll events at once */
int maxaccept; /* max number of consecutive accept() */ int maxaccept; /* max number of consecutive accept() */

View File

@ -11,6 +11,8 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <import/sha1.h>
#include <haproxy/buf.h> #include <haproxy/buf.h>
#include <haproxy/cfgparse.h> #include <haproxy/cfgparse.h>
#ifdef USE_CPU_AFFINITY #ifdef USE_CPU_AFFINITY
@ -23,6 +25,8 @@
#include <haproxy/protocol.h> #include <haproxy/protocol.h>
#include <haproxy/tools.h> #include <haproxy/tools.h>
int cluster_secret_isset;
/* some keywords that are still being parsed using strcmp() and are not /* some keywords that are still being parsed using strcmp() and are not
* registered anywhere. They are used as suggestions for mistyped words. * registered anywhere. They are used as suggestions for mistyped words.
*/ */
@ -514,6 +518,9 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
global.tune.options &= GTUNE_USE_FAST_FWD; global.tune.options &= GTUNE_USE_FAST_FWD;
} }
else if (strcmp(args[0], "cluster-secret") == 0) { else if (strcmp(args[0], "cluster-secret") == 0) {
blk_SHA_CTX sha1_ctx;
unsigned char sha1_out[20];
if (alertif_too_many_args(1, file, linenum, args, &err_code)) if (alertif_too_many_args(1, file, linenum, args, &err_code))
goto out; goto out;
if (*args[1] == 0) { if (*args[1] == 0) {
@ -521,13 +528,18 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
goto out; goto out;
} }
if (global.cluster_secret != NULL) { if (cluster_secret_isset) {
ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); ha_alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]);
err_code |= ERR_ALERT; err_code |= ERR_ALERT;
goto out; goto out;
} }
ha_free(&global.cluster_secret);
global.cluster_secret = strdup(args[1]); blk_SHA1_Init(&sha1_ctx);
blk_SHA1_Update(&sha1_ctx, args[1], strlen(args[1]));
blk_SHA1_Final(sha1_out, &sha1_ctx);
BUG_ON(sizeof sha1_out < sizeof global.cluster_secret);
memcpy(global.cluster_secret, sha1_out, sizeof global.cluster_secret);
cluster_secret_isset = 1;
} }
else if (strcmp(args[0], "uid") == 0) { else if (strcmp(args[0], "uid") == 0) {
if (alertif_too_many_args(1, file, linenum, args, &err_code)) if (alertif_too_many_args(1, file, linenum, args, &err_code))

View File

@ -2803,7 +2803,6 @@ int check_config_validity()
struct cfg_postparser *postparser; struct cfg_postparser *postparser;
struct resolvers *curr_resolvers = NULL; struct resolvers *curr_resolvers = NULL;
int i; int i;
int diag_no_cluster_secret = 0;
bind_conf = NULL; bind_conf = NULL;
/* /*
@ -4318,13 +4317,6 @@ init_proxies_list_stage2:
#ifdef USE_QUIC #ifdef USE_QUIC
if (listener->bind_conf->xprt == xprt_get(XPRT_QUIC)) { if (listener->bind_conf->xprt == xprt_get(XPRT_QUIC)) {
if (!global.cluster_secret) {
diag_no_cluster_secret = 1;
if (listener->bind_conf->options & BC_O_QUIC_FORCE_RETRY) {
ha_alert("QUIC listener with quic-force-retry requires global cluster-secret to be set.\n");
cfgerr++;
}
}
# ifdef USE_QUIC_OPENSSL_COMPAT # ifdef USE_QUIC_OPENSSL_COMPAT
/* store the last checked bind_conf in bind_conf */ /* store the last checked bind_conf in bind_conf */
if (!(global.tune.options & GTUNE_NO_QUIC) && if (!(global.tune.options & GTUNE_NO_QUIC) &&
@ -4375,12 +4367,6 @@ init_proxies_list_stage2:
goto init_proxies_list_stage2; goto init_proxies_list_stage2;
} }
if (diag_no_cluster_secret) {
ha_diag_warning("Generating a random cluster secret. "
"You should define your own one in the configuration to ensure consistency "
"after reload/restart or across your whole cluster.\n");
}
/* /*
* Recount currently required checks. * Recount currently required checks.
*/ */

View File

@ -1925,17 +1925,16 @@ static void dump_registered_keywords(void)
static void generate_random_cluster_secret() static void generate_random_cluster_secret()
{ {
/* used as a default random cluster-secret if none defined. */ /* used as a default random cluster-secret if none defined. */
uint64_t rand = ha_random64(); uint64_t rand;
/* The caller must not overwrite an already defined secret. */ /* The caller must not overwrite an already defined secret. */
BUG_ON(global.cluster_secret); BUG_ON(cluster_secret_isset);
global.cluster_secret = malloc(8);
if (!global.cluster_secret)
return;
rand = ha_random64();
memcpy(global.cluster_secret, &rand, sizeof(rand)); memcpy(global.cluster_secret, &rand, sizeof(rand));
global.cluster_secret[7] = '\0'; rand = ha_random64();
memcpy(global.cluster_secret + sizeof(rand), &rand, sizeof(rand));
cluster_secret_isset = 1;
} }
/* /*
@ -2657,7 +2656,7 @@ static void init(int argc, char **argv)
exit(1); exit(1);
} }
if (!global.cluster_secret) if (!cluster_secret_isset)
generate_random_cluster_secret(); generate_random_cluster_secret();
/* /*
@ -2850,7 +2849,6 @@ void deinit(void)
ha_free(&global.log_send_hostname); ha_free(&global.log_send_hostname);
chunk_destroy(&global.log_tag); chunk_destroy(&global.log_tag);
ha_free(&global.chroot); ha_free(&global.chroot);
ha_free(&global.cluster_secret);
ha_free(&global.pidfile); ha_free(&global.pidfile);
ha_free(&global.node); ha_free(&global.node);
ha_free(&global.desc); ha_free(&global.desc);

View File

@ -477,8 +477,8 @@ int quic_stateless_reset_token_cpy(unsigned char *pos, size_t len,
const unsigned char *salt, size_t saltlen) const unsigned char *salt, size_t saltlen)
{ {
/* Input secret */ /* Input secret */
const unsigned char *key = (const unsigned char *)global.cluster_secret; const unsigned char *key = global.cluster_secret;
size_t keylen = strlen(global.cluster_secret); size_t keylen = sizeof global.cluster_secret;
/* Info */ /* Info */
const unsigned char label[] = "stateless token"; const unsigned char label[] = "stateless token";
size_t labellen = sizeof label - 1; size_t labellen = sizeof label - 1;
@ -494,9 +494,6 @@ int quic_stateless_reset_token_cpy(unsigned char *pos, size_t len,
*/ */
static int quic_stateless_reset_token_init(struct quic_connection_id *conn_id) static int quic_stateless_reset_token_init(struct quic_connection_id *conn_id)
{ {
int ret;
if (global.cluster_secret) {
/* Output secret */ /* Output secret */
unsigned char *token = conn_id->stateless_reset_token; unsigned char *token = conn_id->stateless_reset_token;
size_t tokenlen = sizeof conn_id->stateless_reset_token; size_t tokenlen = sizeof conn_id->stateless_reset_token;
@ -504,15 +501,7 @@ static int quic_stateless_reset_token_init(struct quic_connection_id *conn_id)
const unsigned char *cid = conn_id->cid.data; const unsigned char *cid = conn_id->cid.data;
size_t cidlen = conn_id->cid.len; size_t cidlen = conn_id->cid.len;
ret = quic_stateless_reset_token_cpy(token, tokenlen, cid, cidlen); return quic_stateless_reset_token_cpy(token, tokenlen, cid, cidlen);
}
else {
/* TODO: RAND_bytes() should be replaced */
ret = RAND_bytes(conn_id->stateless_reset_token,
sizeof conn_id->stateless_reset_token) == 1;
}
return ret;
} }
/* Generate a CID directly derived from <orig> CID and <addr> address. /* Generate a CID directly derived from <orig> CID and <addr> address.

View File

@ -1722,8 +1722,8 @@ static int quic_retry_token_check(struct quic_rx_packet *pkt,
const unsigned char *salt; const unsigned char *salt;
unsigned char key[QUIC_TLS_KEY_LEN]; unsigned char key[QUIC_TLS_KEY_LEN];
unsigned char iv[QUIC_TLS_IV_LEN]; unsigned char iv[QUIC_TLS_IV_LEN];
const unsigned char *sec = (const unsigned char *)global.cluster_secret; const unsigned char *sec = global.cluster_secret;
size_t seclen = strlen(global.cluster_secret); size_t seclen = sizeof global.cluster_secret;
EVP_CIPHER_CTX *ctx = NULL; EVP_CIPHER_CTX *ctx = NULL;
const EVP_CIPHER *aead = EVP_aes_128_gcm(); const EVP_CIPHER *aead = EVP_aes_128_gcm();
const struct quic_version *qv = qc ? qc->original_version : const struct quic_version *qv = qc ? qc->original_version :
@ -1732,7 +1732,7 @@ static int quic_retry_token_check(struct quic_rx_packet *pkt,
TRACE_ENTER(QUIC_EV_CONN_LPKT, qc); TRACE_ENTER(QUIC_EV_CONN_LPKT, qc);
/* The caller must ensure this. */ /* The caller must ensure this. */
BUG_ON(!global.cluster_secret || !pkt->token_len); BUG_ON(!pkt->token_len);
prx = l->bind_conf->frontend; prx = l->bind_conf->frontend;
prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe, &quic_stats_module); prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe, &quic_stats_module);
@ -1906,7 +1906,7 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt,
if (pkt->type == QUIC_PACKET_TYPE_INITIAL) { if (pkt->type == QUIC_PACKET_TYPE_INITIAL) {
BUG_ON(!pkt->version); /* This must not happen. */ BUG_ON(!pkt->version); /* This must not happen. */
if (global.cluster_secret && pkt->token_len) { if (pkt->token_len) {
if (!quic_retry_token_check(pkt, dgram, l, qc, &token_odcid)) if (!quic_retry_token_check(pkt, dgram, l, qc, &token_odcid))
goto err; goto err;
} }
@ -1917,7 +1917,7 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt,
struct quic_connection_id *conn_id; struct quic_connection_id *conn_id;
int ipv4; int ipv4;
if (global.cluster_secret && !pkt->token_len && !(l->bind_conf->options & BC_O_QUIC_FORCE_RETRY) && if (!pkt->token_len && !(l->bind_conf->options & BC_O_QUIC_FORCE_RETRY) &&
HA_ATOMIC_LOAD(&prx_counters->half_open_conn) >= global.tune.quic_retry_threshold) { HA_ATOMIC_LOAD(&prx_counters->half_open_conn) >= global.tune.quic_retry_threshold) {
TRACE_PROTO("Initial without token, sending retry", TRACE_PROTO("Initial without token, sending retry",
QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version); QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
@ -1994,7 +1994,7 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt,
} }
else if (!qc) { else if (!qc) {
TRACE_PROTO("RX non Initial pkt without connection", QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version); TRACE_PROTO("RX non Initial pkt without connection", QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
if (global.cluster_secret && !send_stateless_reset(l, &dgram->saddr, pkt)) if (!send_stateless_reset(l, &dgram->saddr, pkt))
TRACE_ERROR("stateless reset not sent", QUIC_EV_CONN_LPKT, qc); TRACE_ERROR("stateless reset not sent", QUIC_EV_CONN_LPKT, qc);
goto err; goto err;
} }
@ -2117,7 +2117,7 @@ static int quic_rx_pkt_parse(struct quic_rx_packet *pkt,
/* TODO Retry should be automatically activated if /* TODO Retry should be automatically activated if
* suspect network usage is detected. * suspect network usage is detected.
*/ */
if (global.cluster_secret && !token_len) { if (!token_len) {
if (l->bind_conf->options & BC_O_QUIC_FORCE_RETRY) { if (l->bind_conf->options & BC_O_QUIC_FORCE_RETRY) {
TRACE_PROTO("Initial without token, sending retry", TRACE_PROTO("Initial without token, sending retry",
QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version); QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
@ -2131,14 +2131,6 @@ static int quic_rx_pkt_parse(struct quic_rx_packet *pkt,
goto drop_silent; goto drop_silent;
} }
} }
else if (!global.cluster_secret && token_len) {
/* Impossible case: a token was received without configured
* cluster secret.
*/
TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT,
NULL, NULL, NULL, pkt->version);
goto drop;
}
pkt->token = pos; pkt->token = pos;
pkt->token_len = token_len; pkt->token_len = token_len;

View File

@ -1610,8 +1610,8 @@ static int quic_generate_retry_token(unsigned char *token, size_t len,
unsigned char salt[QUIC_RETRY_TOKEN_SALTLEN]; unsigned char salt[QUIC_RETRY_TOKEN_SALTLEN];
unsigned char key[QUIC_TLS_KEY_LEN]; unsigned char key[QUIC_TLS_KEY_LEN];
unsigned char iv[QUIC_TLS_IV_LEN]; unsigned char iv[QUIC_TLS_IV_LEN];
const unsigned char *sec = (const unsigned char *)global.cluster_secret; const unsigned char *sec = global.cluster_secret;
size_t seclen = strlen(global.cluster_secret); size_t seclen = sizeof global.cluster_secret;
EVP_CIPHER_CTX *ctx = NULL; EVP_CIPHER_CTX *ctx = NULL;
const EVP_CIPHER *aead = EVP_aes_128_gcm(); const EVP_CIPHER *aead = EVP_aes_128_gcm();
uint32_t timestamp = (uint32_t)date.tv_sec; uint32_t timestamp = (uint32_t)date.tv_sec;