diff --git a/UnitTest1/unittest1.cpp b/UnitTest1/unittest1.cpp index 158a925aa85f5aec40ec99732d15b9182f90b4fa..2a8cec84c8ccf54b43e49a8c58085ecd8a9f972c 100644 --- a/UnitTest1/unittest1.cpp +++ b/UnitTest1/unittest1.cpp @@ -604,5 +604,12 @@ namespace UnitTest1 Assert::AreEqual(ret, 0); } + + TEST_METHOD(fuzz) + { + int ret = fuzz_test(); + + Assert::AreEqual(ret, 0); + } }; } diff --git a/picoquic/picoquic.h b/picoquic/picoquic.h index 2e9aa4f26c18f9d87d39da0c5642c3fa135f16cd..49b5de2645339d37a5e5fd4844685f1e39c10908 100644 --- a/picoquic/picoquic.h +++ b/picoquic/picoquic.h @@ -272,6 +272,13 @@ typedef void (*picoquic_stream_data_cb_fn)(picoquic_cnx_t* cnx, typedef void (*cnx_id_cb_fn)(picoquic_connection_id_t cnx_id_local, picoquic_connection_id_t cnx_id_remote, void* cnx_id_cb_data, picoquic_connection_id_t * cnx_id_returned); +/* The fuzzer function is used to inject error in packets randomly. + * It is called just prior to sending a packet, and can randomly + * change the content or length of the packet. + */ +typedef uint32_t(*picoquic_fuzz_fn)(void * fuzz_ctx, picoquic_cnx_t* cnx, uint8_t * bytes, size_t bytes_max, size_t length); +void picoquic_set_fuzz(picoquic_quic_t* quic, picoquic_fuzz_fn fuzz_fn, void * fuzz_ctx); + /* Will be called to verify that the given data corresponds to the given signature. * This callback and the `verify_ctx` will be set by the `verify_certificate_cb_fn`. * If `data` and `sign` are empty buffers, an error occurred and `verify_ctx` should be freed. diff --git a/picoquic/picoquic_internal.h b/picoquic/picoquic_internal.h index f5c586a3ad61ab91253cc02c29f06c33acb92684..c4b4c6221c012ffd1cc8fad109b359134c6f2f25 100644 --- a/picoquic/picoquic_internal.h +++ b/picoquic/picoquic_internal.h @@ -211,6 +211,9 @@ typedef struct st_picoquic_quic_t { picoquic_free_verify_certificate_ctx free_verify_certificate_callback_fn; void* verify_certificate_ctx; uint8_t local_ctx_length; + + picoquic_fuzz_fn fuzz_fn; + void* fuzz_ctx; } picoquic_quic_t; picoquic_packet_context_enum picoquic_context_from_epoch(int epoch); @@ -679,13 +682,13 @@ uint32_t picoquic_protect_packet(picoquic_cnx_t* cnx, picoquic_packet_type_enum ptype, uint8_t * bytes, uint64_t sequence_number, uint32_t length, uint32_t header_length, - uint8_t* send_buffer, + uint8_t* send_buffer, uint32_t send_buffer_max, void * aead_context, void* pn_enc); void picoquic_finalize_and_protect_packet(picoquic_cnx_t *cnx, picoquic_packet * packet, int ret, uint32_t length, uint32_t header_length, uint32_t checksum_overhead, - size_t * send_length, uint8_t * send_buffer, picoquic_path_t * path_x, - uint64_t current_time); + size_t * send_length, uint8_t * send_buffer, uint32_t send_buffer_max, + picoquic_path_t * path_x, uint64_t current_time); int picoquic_parse_header_and_decrypt( picoquic_quic_t* quic, diff --git a/picoquic/quicctx.c b/picoquic/quicctx.c index 325646885e8cf49318dddae61224eda1ffd151aa..9919a00cf2e2216fb306afa46deed12d5ad688cc 100644 --- a/picoquic/quicctx.c +++ b/picoquic/quicctx.c @@ -1057,6 +1057,12 @@ uint64_t picoquic_get_quic_time(picoquic_quic_t* quic) return now; } +void picoquic_set_fuzz(picoquic_quic_t * quic, picoquic_fuzz_fn fuzz_fn, void * fuzz_ctx) +{ + quic->fuzz_fn = fuzz_fn; + quic->fuzz_ctx = fuzz_ctx; +} + void picoquic_set_callback(picoquic_cnx_t* cnx, diff --git a/picoquic/sender.c b/picoquic/sender.c index ea8cf658b7aaae749daa83f2e97773c2eceb899a..add1c0f840ebb553af5cc9ac2f09a2a10f1d2f67 100644 --- a/picoquic/sender.c +++ b/picoquic/sender.c @@ -385,7 +385,7 @@ uint32_t picoquic_protect_packet(picoquic_cnx_t* cnx, uint8_t * bytes, uint64_t sequence_number, uint32_t length, uint32_t header_length, - uint8_t* send_buffer, + uint8_t* send_buffer, uint32_t send_buffer_max, void * aead_context, void* pn_enc) { uint32_t send_length; @@ -394,7 +394,7 @@ uint32_t picoquic_protect_packet(picoquic_cnx_t* cnx, size_t sample_offset = 0; size_t sample_size = picoquic_pn_iv_size(pn_enc); uint32_t pn_length = 0; - size_t aead_checksum_length = picoquic_aead_get_checksum_length(aead_context); + uint32_t aead_checksum_length = (uint32_t)picoquic_aead_get_checksum_length(aead_context); /* Create the packet header just before encrypting the content */ h_length = picoquic_create_packet_header(cnx, ptype, @@ -403,6 +403,19 @@ uint32_t picoquic_protect_packet(picoquic_cnx_t* cnx, /* Using encryption, the "payload" length also includes the encrypted packet length */ picoquic_update_payload_length(send_buffer, pn_offset, h_length - pn_length, length + aead_checksum_length); + /* If fuzzing is required, apply it*/ + if (cnx->quic->fuzz_fn != NULL) { + if (h_length == header_length) { + memcpy(bytes, send_buffer, header_length); + } + length = cnx->quic->fuzz_fn(cnx->quic->fuzz_ctx, cnx, bytes, + send_buffer_max - aead_checksum_length, length); + if (h_length == header_length) { + memcpy(send_buffer, bytes, header_length); + } + } + + /* Encrypt the packet */ send_length = (uint32_t)picoquic_aead_encrypt_generic(send_buffer + /* header_length */ h_length, bytes + header_length, length - header_length, sequence_number, send_buffer, /* header_length */ h_length, aead_context); @@ -500,8 +513,8 @@ void picoquic_queue_for_retransmit(picoquic_cnx_t* cnx, picoquic_path_t * path_x void picoquic_finalize_and_protect_packet(picoquic_cnx_t *cnx, picoquic_packet * packet, int ret, uint32_t length, uint32_t header_length, uint32_t checksum_overhead, - size_t * send_length, uint8_t * send_buffer, picoquic_path_t * path_x, - uint64_t current_time) + size_t * send_length, uint8_t * send_buffer, uint32_t send_buffer_max, + picoquic_path_t * path_x, uint64_t current_time) { if (ret == 0 && length > 0) { @@ -515,28 +528,28 @@ void picoquic_finalize_and_protect_packet(picoquic_cnx_t *cnx, picoquic_packet * case picoquic_packet_initial: length = picoquic_protect_packet(cnx, packet->ptype, packet->bytes, packet->sequence_number, length, header_length, - send_buffer, cnx->crypto_context[0].aead_encrypt, cnx->crypto_context[0].pn_enc); + send_buffer, send_buffer_max, cnx->crypto_context[0].aead_encrypt, cnx->crypto_context[0].pn_enc); break; case picoquic_packet_handshake: length = picoquic_protect_packet(cnx, packet->ptype, packet->bytes, packet->sequence_number, length, header_length, - send_buffer, cnx->crypto_context[2].aead_encrypt, cnx->crypto_context[2].pn_enc); + send_buffer, send_buffer_max, cnx->crypto_context[2].aead_encrypt, cnx->crypto_context[2].pn_enc); break; case picoquic_packet_retry: length = picoquic_protect_packet(cnx, packet->ptype, packet->bytes, packet->sequence_number, length, header_length, - send_buffer, cnx->crypto_context[0].aead_encrypt, cnx->crypto_context[0].pn_enc); + send_buffer, send_buffer_max, cnx->crypto_context[0].aead_encrypt, cnx->crypto_context[0].pn_enc); break; case picoquic_packet_0rtt_protected: length = picoquic_protect_packet(cnx, packet->ptype, packet->bytes, packet->sequence_number, length, header_length, - send_buffer, cnx->crypto_context[1].aead_encrypt, cnx->crypto_context[1].pn_enc); + send_buffer, send_buffer_max, cnx->crypto_context[1].aead_encrypt, cnx->crypto_context[1].pn_enc); break; case picoquic_packet_1rtt_protected_phi0: case picoquic_packet_1rtt_protected_phi1: length = picoquic_protect_packet(cnx, packet->ptype, packet->bytes, packet->sequence_number, length, header_length, - send_buffer, cnx->crypto_context[3].aead_encrypt, cnx->crypto_context[3].pn_enc); + send_buffer, send_buffer_max, cnx->crypto_context[3].aead_encrypt, cnx->crypto_context[3].pn_enc); break; default: /* Packet type error. Do nothing at all. */ @@ -1028,7 +1041,7 @@ static void picoquic_cnx_set_next_wake_time_init(picoquic_cnx_t* cnx, uint64_t c /* TODO: tie with per path scheduling */ void picoquic_cnx_set_next_wake_time(picoquic_cnx_t* cnx, uint64_t current_time) { - uint64_t next_time = cnx->latest_progress_time + PICOQUIC_MICROSEC_SILENCE_MAX; + uint64_t next_time = cnx->latest_progress_time + PICOQUIC_MICROSEC_SILENCE_MAX * (2 - cnx->client_mode); picoquic_stream_head* stream = NULL; int timer_based = 0; int blocked = 1; @@ -1202,7 +1215,7 @@ int picoquic_prepare_packet_0rtt(picoquic_cnx_t* cnx, picoquic_path_t * path_x, picoquic_finalize_and_protect_packet(cnx, packet, ret, length, header_length, checksum_overhead, - send_length, send_buffer, path_x, current_time); + send_length, send_buffer, send_buffer_max, path_x, current_time); if (length > 0) { /* Accounting of zero rtt packets sent */ @@ -1497,7 +1510,7 @@ int picoquic_prepare_packet_client_init(picoquic_cnx_t* cnx, picoquic_path_t * p } else { picoquic_finalize_and_protect_packet(cnx, packet, ret, length, header_length, checksum_overhead, - send_length, send_buffer, path_x, current_time); + send_length, send_buffer, send_buffer_max, path_x, current_time); if (cnx->cnx_state != picoquic_state_draining) { picoquic_cnx_set_next_wake_time(cnx, current_time); @@ -1644,7 +1657,7 @@ int picoquic_prepare_packet_server_init(picoquic_cnx_t* cnx, picoquic_path_t * p picoquic_finalize_and_protect_packet(cnx, packet, ret, length, header_length, checksum_overhead, - send_length, send_buffer, path_x, current_time); + send_length, send_buffer, send_buffer_max, path_x, current_time); picoquic_cnx_set_next_wake_time(cnx, current_time); @@ -1865,7 +1878,7 @@ int picoquic_prepare_packet_closing(picoquic_cnx_t* cnx, picoquic_path_t * path_ picoquic_finalize_and_protect_packet(cnx, packet, ret, length, header_length, checksum_overhead, - send_length, send_buffer, path_x, current_time); + send_length, send_buffer, send_buffer_max, path_x, current_time); return ret; } @@ -2099,7 +2112,7 @@ int picoquic_prepare_packet_ready(picoquic_cnx_t* cnx, picoquic_path_t * path_x, picoquic_finalize_and_protect_packet(cnx, packet, ret, length, header_length, checksum_overhead, - send_length, send_buffer, path_x, current_time); + send_length, send_buffer, send_buffer_min_max, path_x, current_time); picoquic_cnx_set_next_wake_time(cnx, current_time); @@ -2114,7 +2127,7 @@ int picoquic_prepare_segment(picoquic_cnx_t* cnx, picoquic_path_t * path_x, pico /* Check that the connection is still alive -- the timer is asymmetric, so client will drop faster */ if ((cnx->cnx_state < picoquic_state_disconnecting && - (current_time - cnx->latest_progress_time) > (PICOQUIC_MICROSEC_SILENCE_MAX*(2 - cnx->client_mode))) || + (current_time - cnx->latest_progress_time) >= (PICOQUIC_MICROSEC_SILENCE_MAX*(2 - cnx->client_mode))) || (cnx->cnx_state < picoquic_state_client_ready && current_time >= cnx->start_time + PICOQUIC_MICROSEC_HANDSHAKE_MAX)) { diff --git a/picoquic_t/picoquic_t.c b/picoquic_t/picoquic_t.c index d0359cd44b024521b9936ecb2b2225712ca138a1..3e7499ed123e6c17d7ba04a46f9f4b218c4e82ea 100644 --- a/picoquic_t/picoquic_t.c +++ b/picoquic_t/picoquic_t.c @@ -126,7 +126,8 @@ static const picoquic_test_def_t test_table[] = { { "pn_vector", cleartext_pn_vector_test }, { "zero_rtt_spurious", zero_rtt_spurious_test }, { "zero_rtt_retry", zero_rtt_retry_test }, - { "stress", stress_test } + { "stress", stress_test }, + { "fuzz", fuzz_test } }; static size_t const nb_tests = sizeof(test_table) / sizeof(picoquic_test_def_t); @@ -174,6 +175,7 @@ int usage(char const * argv0) fprintf(stderr, "Options: \n"); fprintf(stderr, " -x test Do not run the specified test.\n"); fprintf(stderr, " -s nnn Run stress for nnn minutes.\n"); + fprintf(stderr, " -f nnn Run fuzz for nnn minutes.\n"); fprintf(stderr, " -h Print this help message\n"); return -1; @@ -201,6 +203,8 @@ int main(int argc, char** argv) int found_exclusion = 0; test_status_t * test_status = (test_status_t *) calloc(nb_tests, sizeof(test_status_t)); int opt; + int do_fuzz = 0; + int do_stress = 0; if (test_status == NULL) { @@ -209,7 +213,7 @@ int main(int argc, char** argv) } else { - while (ret == 0 && (opt = getopt(argc, argv, "s:x:h")) != -1) { + while (ret == 0 && (opt = getopt(argc, argv, "f:s:x:h")) != -1) { switch (opt) { case 'x': { int test_number = get_test_number(optarg); @@ -224,7 +228,16 @@ int main(int argc, char** argv) } break; } + case 'f': + do_fuzz = 1; + stress_minutes = atoi(optarg); + if (stress_minutes <= 0) { + fprintf(stderr, "Incorrect stress minutes: %s\n", optarg); + ret = usage(argv[0]); + } + break; case 's': + do_stress = 1; stress_minutes = atoi(optarg); if (stress_minutes <= 0) { fprintf(stderr, "Incorrect stress minutes: %s\n", optarg); @@ -244,7 +257,17 @@ int main(int argc, char** argv) if (ret == 0 && stress_minutes > 0) { if (optind >= argc && found_exclusion == 0) { for (size_t i = 0; i < nb_tests; i++) { - if (strcmp(test_table[i].test_name, "stress") != 0) { + if (strcmp(test_table[i].test_name, "stress") == 0) + { + if (do_stress == 0){ + test_status[i] = test_excluded; + } + } + else if (strcmp(test_table[i].test_name, "fuzz") == 0) { + if (do_fuzz == 0) { + test_status[i] = test_excluded; + } + } else { test_status[i] = test_excluded; } } diff --git a/picoquictest/parseheadertest.c b/picoquictest/parseheadertest.c index 905397c79457b22be69333fc6a0ebd3d82039fc6..e8daa1a749a02d0e975726b8458271f7d0e05c03 100644 --- a/picoquictest/parseheadertest.c +++ b/picoquictest/parseheadertest.c @@ -503,7 +503,7 @@ int test_packet_encrypt_one( /* Create a packet with specified parameters */ picoquic_finalize_and_protect_packet(cnx_client, packet, ret, length, header_length, checksum_overhead, - &send_length, send_buffer, path_x, current_time); + &send_length, send_buffer, PICOQUIC_MAX_PACKET_SIZE, path_x, current_time); expected_header.ptype = packet->ptype; expected_header.offset = packet->offset; diff --git a/picoquictest/picoquictest.h b/picoquictest/picoquictest.h index 33c22d0c4e93278f7bc63974f9a119876efe8e68..b2b6008fabf7ee434df6c1b2bdb2098bc206c560 100644 --- a/picoquictest/picoquictest.h +++ b/picoquictest/picoquictest.h @@ -119,6 +119,7 @@ int stress_test(); int splay_test(); int TlsStreamFrameTest(); int draft13_vector_test(); +int fuzz_test(); #ifdef __cplusplus } diff --git a/picoquictest/stresstest.c b/picoquictest/stresstest.c index 8953377e3da04022f727f74610ac71af7729b337..0ac81045f4281b3fa0abce7ef69e1cc71507e481 100644 --- a/picoquictest/stresstest.c +++ b/picoquictest/stresstest.c @@ -861,11 +861,12 @@ static void stress_delete_client_context(int client_index, picoquic_stress_ctx_t } } -int stress_test() +static int stress_or_fuzz_test(picoquic_fuzz_fn fuzz_fn, void * fuzz_ctx) { int ret = 0; picoquic_stress_ctx_t stress_ctx; double run_time_seconds = 0; + double target_seconds = 0; double wall_time_seconds = 0; uint64_t wall_time_start = picoquic_current_time(); uint64_t wall_time_max = wall_time_start + picoquic_stress_test_duration; @@ -895,6 +896,9 @@ int stress_test() else { for (int i = 0; ret == 0 && i < stress_ctx.nb_clients; i++) { ret = stress_create_client_context(i, &stress_ctx); + if (ret == 0 && fuzz_fn != NULL) { + picoquic_set_fuzz(stress_ctx.c_ctx[i]->qclient, fuzz_fn, fuzz_ctx); + } } } } @@ -946,9 +950,81 @@ int stress_test() /* Report */ run_time_seconds = ((double)stress_ctx.simulated_time) / 1000000.0; + target_seconds = ((double)picoquic_stress_test_duration) / 1000000.0; wall_time_seconds = ((double)(picoquic_current_time() - wall_time_start)) / 1000000.0; - DBG_PRINTF("Stress complete after simulating %3f s. in %3f s., returns %d\n", - run_time_seconds, wall_time_seconds, ret); + + if (stress_ctx.simulated_time < picoquic_stress_test_duration) { + DBG_PRINTF("Stress incomplete after simulating %3fs instead of %3fs in %3f s., returns %d\n", + run_time_seconds, target_seconds, wall_time_seconds, ret); + ret = -1; + } + else { + DBG_PRINTF("Stress complete after simulating %3f s. in %3f s., returns %d\n", + run_time_seconds, wall_time_seconds, ret); + } return ret; } + +int stress_test() +{ + return stress_or_fuzz_test(NULL, NULL); +} + +/* + * Basic fuzz test just tries to flip some bits in random packets + */ + +typedef struct st_basic_fuzzer_ctx_t { + uint64_t random_context; + picoquic_state_enum highest_state_fuzzed; +} basic_fuzzer_ctx_t; + +static uint32_t basic_fuzzer(void * fuzz_ctx, picoquic_cnx_t* cnx, uint8_t * bytes, size_t bytes_max, size_t length) +{ + basic_fuzzer_ctx_t * ctx = (basic_fuzzer_ctx_t *)fuzz_ctx; + uint64_t fuzz_pilot = picoquic_test_random(&ctx->random_context); + int should_fuzz = 0; + uint32_t fuzz_index = 0; + + if (cnx->cnx_state > ctx->highest_state_fuzzed) { + should_fuzz = 1; + ctx->highest_state_fuzzed = cnx->cnx_state; + } else { + /* if already fuzzed this state, fuzz one packet in 16 */ + should_fuzz = ((fuzz_pilot & 0xF) == 0xD); + fuzz_pilot >>= 4; + } + + if (should_fuzz) { + /* Once in 64, fuzz by changing the length */ + if (bytes_max > length + 16 && (fuzz_pilot & 0x3F) == 0x2B) { + fuzz_pilot >>= 6; + length = 16 + (uint32_t)((fuzz_pilot&0xFFFF) % length); + fuzz_pilot >>= 16; + } + /* Find the position that shall be fuzzed */ + fuzz_index = (uint32_t)((fuzz_pilot & 0xFFFF) % length); + fuzz_pilot >>= 16; + while (fuzz_pilot != 0 && fuzz_index < length) { + /* flip one byte */ + bytes[fuzz_index++] = (uint8_t)(fuzz_pilot & 0xFF); + fuzz_pilot >>= 8; + } + } + + return length; +} + +int fuzz_test() +{ + basic_fuzzer_ctx_t fuzz_ctx; + int ret = 0; + + fuzz_ctx.highest_state_fuzzed = 0; + fuzz_ctx.random_context = 0xDEADBEEFBABACAFEull; + + ret = stress_or_fuzz_test(basic_fuzzer, &fuzz_ctx); + + return ret; +} \ No newline at end of file