From 9fd58f977d5779f8a695dd963e75cf3abee8231e Mon Sep 17 00:00:00 2001 From: Michael Graeb Date: Fri, 21 Feb 2025 14:41:52 -0800 Subject: [PATCH 1/2] aws_base64_compute_encoded_len() is now exact, doesn't add 1 extra for null-terminator (#1188) --- include/aws/common/encoding.h | 4 +- source/encoding.c | 53 ++++--- tests/encoding_test.c | 226 +++++++++++---------------- tests/fuzz/hex_encoding_transitive.c | 1 - 4 files changed, 116 insertions(+), 168 deletions(-) diff --git a/include/aws/common/encoding.h b/include/aws/common/encoding.h index 89e4df2e6..fef3de71d 100644 --- a/include/aws/common/encoding.h +++ b/include/aws/common/encoding.h @@ -25,7 +25,7 @@ int aws_hex_compute_encoded_len(size_t to_encode_len, size_t *encoded_length); /* * Base 16 (hex) encodes the contents of to_encode and stores the result in - * output. 0 terminates the result. Assumes the buffer is empty and does not resize on + * output. Assumes the buffer is empty and does not resize on * insufficient capacity. */ AWS_COMMON_API @@ -33,7 +33,7 @@ int aws_hex_encode(const struct aws_byte_cursor *AWS_RESTRICT to_encode, struct /* * Base 16 (hex) encodes the contents of to_encode and appends the result in - * output. Does not 0-terminate. Grows the destination buffer dynamically if necessary. + * output. Grows the destination buffer dynamically if necessary. */ AWS_COMMON_API int aws_hex_encode_append_dynamic( diff --git a/source/encoding.c b/source/encoding.c index 97b99145d..c9e14ee3b 100644 --- a/source/encoding.c +++ b/source/encoding.c @@ -66,7 +66,9 @@ static const uint8_t BASE64_DECODING_TABLE[256] = { int aws_hex_compute_encoded_len(size_t to_encode_len, size_t *encoded_length) { AWS_ASSERT(encoded_length); - size_t temp = (to_encode_len << 1) + 1; + /* For every byte of input, there will be 2 hex chars of encoded output */ + + size_t temp = to_encode_len << 1; if (AWS_UNLIKELY(temp < to_encode_len)) { return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED); @@ -98,7 +100,7 @@ int aws_hex_encode(const struct aws_byte_cursor *AWS_RESTRICT to_encode, struct output->buffer[written++] = HEX_CHARS[to_encode->ptr[i] & 0x0f]; } - output->buffer[written] = '\0'; + AWS_ASSERT(written == encoded_len); output->len = encoded_len; return AWS_OP_SUCCESS; @@ -153,6 +155,10 @@ static int s_hex_decode_char_to_int(char character, uint8_t *int_val) { int aws_hex_compute_decoded_len(size_t to_decode_len, size_t *decoded_len) { AWS_ASSERT(decoded_len); + /* For every 2 hex chars (rounded up) of encoded input, there will be 1 byte of decoded output. + * Rounding is because if buffer isn't even, we'll pretend there's an extra '0' at start of buffer */ + + /* adding 1 before dividing by 2 is a trick to round up during division */ size_t temp = (to_decode_len + 1); if (AWS_UNLIKELY(temp < to_decode_len)) { @@ -212,6 +218,10 @@ int aws_hex_decode(const struct aws_byte_cursor *AWS_RESTRICT to_decode, struct int aws_base64_compute_encoded_len(size_t to_encode_len, size_t *encoded_len) { AWS_ASSERT(encoded_len); + /* For every 3 bytes (rounded up) of unencoded input, there will be 4 ascii characters of encoded output. + * Rounding is because the output will be padded with '=' chars if necessary to make it divisible by 4. */ + + /* adding 2 before dividing by 3 is a trick to round up during division */ size_t tmp = to_encode_len + 2; if (AWS_UNLIKELY(tmp < to_encode_len)) { @@ -220,7 +230,7 @@ int aws_base64_compute_encoded_len(size_t to_encode_len, size_t *encoded_len) { tmp /= 3; size_t overflow_check = tmp; - tmp = 4 * tmp + 1; /* plus one for the NULL terminator */ + tmp = 4 * tmp; if (AWS_UNLIKELY(tmp < overflow_check)) { return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED); @@ -243,40 +253,40 @@ int aws_base64_compute_decoded_len(const struct aws_byte_cursor *AWS_RESTRICT to return AWS_OP_SUCCESS; } + /* ensure it's divisible by 4 */ if (AWS_UNLIKELY(len & 0x03)) { return aws_raise_error(AWS_ERROR_INVALID_BASE64_STR); } - size_t tmp = len * 3; - - if (AWS_UNLIKELY(tmp < len)) { - return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED); - } + /* For every 4 ascii characters of encoded input, there will be 3 bytes of decoded output (deal with padding later) + * decoded_len = 3/4 * len <-- note that result will be smaller then len, so overflow can be avoided + * = (len / 4) * 3 <-- divide before multiply to avoid overflow + */ + size_t decoded_len_tmp = (len / 4) * 3; + /* But last two ascii chars might be padding. */ + AWS_ASSERT(len >= 4); /* we checked earlier len != 0, and was divisible by 4 */ size_t padding = 0; - - if (len >= 2 && input[len - 1] == '=' && input[len - 2] == '=') { /*last two chars are = */ + if (input[len - 1] == '=' && input[len - 2] == '=') { /*last two chars are = */ padding = 2; } else if (input[len - 1] == '=') { /*last char is = */ padding = 1; } - *decoded_len = (tmp / 4 - padding); + *decoded_len = decoded_len_tmp - padding; return AWS_OP_SUCCESS; } int aws_base64_encode(const struct aws_byte_cursor *AWS_RESTRICT to_encode, struct aws_byte_buf *AWS_RESTRICT output) { - AWS_ASSERT(to_encode->ptr); - AWS_ASSERT(output->buffer); + AWS_ASSERT(to_encode->len == 0 || to_encode->ptr != NULL); - size_t terminated_length = 0; size_t encoded_length = 0; - if (AWS_UNLIKELY(aws_base64_compute_encoded_len(to_encode->len, &terminated_length))) { + if (AWS_UNLIKELY(aws_base64_compute_encoded_len(to_encode->len, &encoded_length))) { return AWS_OP_ERR; } size_t needed_capacity = 0; - if (AWS_UNLIKELY(aws_add_size_checked(output->len, terminated_length, &needed_capacity))) { + if (AWS_UNLIKELY(aws_add_size_checked(output->len, encoded_length, &needed_capacity))) { return AWS_OP_ERR; } @@ -284,16 +294,10 @@ int aws_base64_encode(const struct aws_byte_cursor *AWS_RESTRICT to_encode, stru return aws_raise_error(AWS_ERROR_SHORT_BUFFER); } - /* - * For convenience to standard C functions expecting a null-terminated - * string, the output is terminated. As the encoding itself can be used in - * various ways, however, its length should never account for that byte. - */ - encoded_length = (terminated_length - 1); + AWS_ASSERT(needed_capacity == 0 || output->buffer != NULL); if (aws_common_private_has_avx2()) { aws_common_private_base64_encode_sse41(to_encode->ptr, output->buffer + output->len, to_encode->len); - output->buffer[output->len + encoded_length] = 0; output->len += encoded_length; return AWS_OP_SUCCESS; } @@ -329,9 +333,6 @@ int aws_base64_encode(const struct aws_byte_cursor *AWS_RESTRICT to_encode, stru } } - /* it's a string add the null terminator. */ - output->buffer[output->len + encoded_length] = 0; - output->len += encoded_length; return AWS_OP_SUCCESS; diff --git a/tests/encoding_test.c b/tests/encoding_test.c index 23cd084d0..a0a94e577 100644 --- a/tests/encoding_test.c +++ b/tests/encoding_test.c @@ -15,66 +15,62 @@ static int s_run_hex_encoding_test_case( struct aws_allocator *allocator, const char *test_str, - size_t test_str_size, + size_t test_str_len, const char *expected, - size_t expected_size) { - size_t output_size = 0; + size_t expected_len) { + size_t output_len = 0; ASSERT_SUCCESS( - aws_hex_compute_encoded_len(test_str_size - 1, &output_size), + aws_hex_compute_encoded_len(test_str_len, &output_len), "compute hex encoded len failed with error %d", aws_last_error()); - ASSERT_INT_EQUALS(expected_size, output_size, "Output size on string should be %d", expected_size); + ASSERT_INT_EQUALS(expected_len, output_len, "Output len on buffer should be %d", expected_len); - struct aws_byte_cursor to_encode = aws_byte_cursor_from_array(test_str, test_str_size - 1); + struct aws_byte_cursor to_encode = aws_byte_cursor_from_array(test_str, test_str_len); + /* Create `allocation` buffer, with extra byte at start and end, + * so we can detect if writes go out of bounds */ struct aws_byte_buf allocation; - ASSERT_SUCCESS(aws_byte_buf_init(&allocation, allocator, output_size + 2)); + ASSERT_SUCCESS(aws_byte_buf_init(&allocation, allocator, output_len + 2)); memset(allocation.buffer, 0xdd, allocation.capacity); - struct aws_byte_buf output = aws_byte_buf_from_empty_array(allocation.buffer + 1, output_size); + struct aws_byte_buf output = aws_byte_buf_from_empty_array(allocation.buffer + 1, output_len); ASSERT_SUCCESS(aws_hex_encode(&to_encode, &output), "encode call should have succeeded"); ASSERT_BIN_ARRAYS_EQUALS( - expected, - expected_size, - output.buffer, - output_size, - "Encode output should have been {%s}, was {%s}.", - expected, - output.buffer); - ASSERT_INT_EQUALS(output_size, output.len); + expected, expected_len, output.buffer, output_len, "Encode output should have been {%s}", expected); + ASSERT_INT_EQUALS(output_len, output.len); ASSERT_INT_EQUALS( (unsigned char)*(allocation.buffer), (unsigned char)0xdd, "Write should not have occurred before the start of the buffer."); ASSERT_INT_EQUALS( - (unsigned char)*(allocation.buffer + output_size + 1), + (unsigned char)*(allocation.buffer + output_len + 1), (unsigned char)0xdd, - "Write should not have occurred after the start of the buffer."); + "Write should not have occurred after the end of the buffer."); ASSERT_SUCCESS( - aws_hex_compute_decoded_len(expected_size - 1, &output_size), + aws_hex_compute_decoded_len(expected_len, &output_len), "compute hex decoded len failed with error %d", aws_last_error()); memset(allocation.buffer, 0xdd, allocation.capacity); - ASSERT_INT_EQUALS(test_str_size - 1, output_size, "Output size on string should be %d", test_str_size - 1); + ASSERT_INT_EQUALS(test_str_len, output_len, "Output len on buffer should be %d", test_str_len); aws_byte_buf_reset(&output, false); - struct aws_byte_cursor expected_buf = aws_byte_cursor_from_array(expected, expected_size - 1); + struct aws_byte_cursor expected_buf = aws_byte_cursor_from_array(expected, expected_len); ASSERT_SUCCESS(aws_hex_decode(&expected_buf, &output), "decode call should have succeeded"); ASSERT_BIN_ARRAYS_EQUALS( - test_str, test_str_size - 1, output.buffer, output_size, "Decode output should have been %s.", test_str); - ASSERT_INT_EQUALS(output_size, output.len); + test_str, test_str_len, output.buffer, output_len, "Decode output should have been %s.", test_str); + ASSERT_INT_EQUALS(output_len, output.len); ASSERT_INT_EQUALS( (unsigned char)*(allocation.buffer), (unsigned char)0xdd, "Write should not have occurred before the start of the buffer."); ASSERT_INT_EQUALS( - (unsigned char)*(allocation.buffer + output_size + 1), + (unsigned char)*(allocation.buffer + output_len + 1), (unsigned char)0xdd, "Write should not have occurred after the start of the buffer."); @@ -88,7 +84,7 @@ static int s_hex_encoding_test_case_empty(struct aws_allocator *allocator, void char test_data[] = ""; char expected[] = ""; - return s_run_hex_encoding_test_case(allocator, test_data, sizeof(test_data), expected, sizeof(expected)); + return s_run_hex_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(hex_encoding_test_case_empty_test, s_hex_encoding_test_case_empty) @@ -99,7 +95,7 @@ static int s_hex_encoding_test_case_f(struct aws_allocator *allocator, void *ctx char test_data[] = "f"; char expected[] = "66"; - return s_run_hex_encoding_test_case(allocator, test_data, sizeof(test_data), expected, sizeof(expected)); + return s_run_hex_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(hex_encoding_test_case_f_test, s_hex_encoding_test_case_f) @@ -110,7 +106,7 @@ static int s_hex_encoding_test_case_fo(struct aws_allocator *allocator, void *ct char test_data[] = "fo"; char expected[] = "666f"; - return s_run_hex_encoding_test_case(allocator, test_data, sizeof(test_data), expected, sizeof(expected)); + return s_run_hex_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(hex_encoding_test_case_fo_test, s_hex_encoding_test_case_fo) @@ -121,7 +117,7 @@ static int s_hex_encoding_test_case_foo(struct aws_allocator *allocator, void *c char test_data[] = "foo"; char expected[] = "666f6f"; - return s_run_hex_encoding_test_case(allocator, test_data, sizeof(test_data), expected, sizeof(expected)); + return s_run_hex_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(hex_encoding_test_case_foo_test, s_hex_encoding_test_case_foo) @@ -132,7 +128,7 @@ static int s_hex_encoding_test_case_foob(struct aws_allocator *allocator, void * char test_data[] = "foob"; char expected[] = "666f6f62"; - return s_run_hex_encoding_test_case(allocator, test_data, sizeof(test_data), expected, sizeof(expected)); + return s_run_hex_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(hex_encoding_test_case_foob_test, s_hex_encoding_test_case_foob) @@ -143,7 +139,7 @@ static int s_hex_encoding_test_case_fooba(struct aws_allocator *allocator, void char test_data[] = "fooba"; char expected[] = "666f6f6261"; - return s_run_hex_encoding_test_case(allocator, test_data, sizeof(test_data), expected, sizeof(expected)); + return s_run_hex_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(hex_encoding_test_case_fooba_test, s_hex_encoding_test_case_fooba) @@ -154,22 +150,11 @@ static int s_hex_encoding_test_case_foobar(struct aws_allocator *allocator, void char test_data[] = "foobar"; char expected[] = "666f6f626172"; - return s_run_hex_encoding_test_case(allocator, test_data, sizeof(test_data), expected, sizeof(expected)); + return s_run_hex_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(hex_encoding_test_case_foobar_test, s_hex_encoding_test_case_foobar) -static int s_hex_encoding_append_test_case(struct aws_allocator *allocator, void *ctx) { - (void)ctx; - - char test_data[] = "foobar"; - char expected[] = "666f6f626172"; - - return s_run_hex_encoding_test_case(allocator, test_data, sizeof(test_data), expected, sizeof(expected) - 1); -} - -AWS_TEST_CASE(hex_encoding_append_test_case, s_hex_encoding_append_test_case) - static int s_hex_encoding_test_case_missing_leading_zero_fn(struct aws_allocator *allocator, void *ctx) { (void)allocator; (void)ctx; @@ -284,50 +269,46 @@ AWS_STATIC_STRING_FROM_LITERAL(s_base64_encode_prefix, "Prefix"); static int s_run_base64_encoding_test_case( struct aws_allocator *allocator, const char *test_str, - size_t test_str_size, + size_t test_str_len, const char *expected, - size_t expected_size) { - size_t output_size = 0; - size_t terminated_size = (expected_size + 1); + size_t expected_len) { + + size_t output_len = 0; /* Part 1: encoding */ ASSERT_SUCCESS( - aws_base64_compute_encoded_len(test_str_size, &output_size), + aws_base64_compute_encoded_len(test_str_len, &output_len), "Compute base64 encoded length failed with %d", aws_last_error()); - ASSERT_INT_EQUALS(terminated_size, output_size, "Output size on string should be %d", terminated_size); + ASSERT_INT_EQUALS(expected_len, output_len, "Output len on string should be %d", expected_len); - struct aws_byte_cursor to_encode = aws_byte_cursor_from_array(test_str, test_str_size); + struct aws_byte_cursor to_encode = aws_byte_cursor_from_array(test_str, test_str_len); + /* Create `allocation` buffer, with extra byte at start and end, + * so we can detect if writes go out of bounds */ struct aws_byte_buf allocation; - ASSERT_SUCCESS(aws_byte_buf_init(&allocation, allocator, output_size + 2)); + ASSERT_SUCCESS(aws_byte_buf_init(&allocation, allocator, output_len + 2)); memset(allocation.buffer, 0xdd, allocation.capacity); - struct aws_byte_buf output = aws_byte_buf_from_empty_array(allocation.buffer + 1, output_size); + struct aws_byte_buf output = aws_byte_buf_from_empty_array(allocation.buffer + 1, output_len); ASSERT_SUCCESS(aws_base64_encode(&to_encode, &output), "encode call should have succeeded"); ASSERT_BIN_ARRAYS_EQUALS( - expected, - expected_size, - output.buffer, - output.len, - "Encode output should have been {%s}, was {%s}.", - expected, - output.buffer); + expected, expected_len, output.buffer, output.len, "Encode output should have been {%s}", expected); ASSERT_INT_EQUALS( (unsigned char)*(allocation.buffer), (unsigned char)0xdd, "Write should not have occurred before the start of the buffer."); ASSERT_INT_EQUALS( - (unsigned char)*(allocation.buffer + output_size + 1), + (unsigned char)*(allocation.buffer + output_len + 1), (unsigned char)0xdd, - "Write should not have occurred after the start of the buffer."); + "Write should not have occurred after the end of the buffer."); aws_byte_buf_clean_up(&allocation); /* part 2 - encoding properly appends rather than overwrites */ - ASSERT_SUCCESS(aws_byte_buf_init(&allocation, allocator, output_size + s_base64_encode_prefix->len)); + ASSERT_SUCCESS(aws_byte_buf_init(&allocation, allocator, output_len + s_base64_encode_prefix->len)); struct aws_byte_cursor prefix_cursor = aws_byte_cursor_from_string(s_base64_encode_prefix); ASSERT_SUCCESS(aws_byte_buf_append(&allocation, &prefix_cursor)); @@ -335,12 +316,11 @@ static int s_run_base64_encoding_test_case( ASSERT_BIN_ARRAYS_EQUALS( expected, - expected_size, + expected_len, allocation.buffer + s_base64_encode_prefix->len, - expected_size, - "Encode output should have been {%s}, was {%s}.", - expected, - allocation.buffer + s_base64_encode_prefix->len); + expected_len, + "Encode output should have been {%s}", + expected); struct aws_byte_cursor prefix_output = {.ptr = allocation.buffer, .len = s_base64_encode_prefix->len}; ASSERT_BIN_ARRAYS_EQUALS( @@ -355,35 +335,35 @@ static int s_run_base64_encoding_test_case( aws_byte_buf_clean_up(&allocation); /* Part 3: decoding */ - struct aws_byte_cursor expected_cur = aws_byte_cursor_from_array(expected, expected_size); + struct aws_byte_cursor expected_cur = aws_byte_cursor_from_array(expected, expected_len); ASSERT_SUCCESS( - aws_base64_compute_decoded_len(&expected_cur, &output_size), + aws_base64_compute_decoded_len(&expected_cur, &output_len), "Compute base64 decoded length failed with %d", aws_last_error()); - ASSERT_INT_EQUALS(test_str_size, output_size, "Output size on string should be %d", test_str_size); + ASSERT_INT_EQUALS(test_str_len, output_len, "Output len on string should be %d", test_str_len); - ASSERT_SUCCESS(aws_byte_buf_init(&allocation, allocator, output_size + 2)); + ASSERT_SUCCESS(aws_byte_buf_init(&allocation, allocator, output_len + 2)); memset(allocation.buffer, 0xdd, allocation.capacity); - output = aws_byte_buf_from_empty_array(allocation.buffer + 1, output_size); + output = aws_byte_buf_from_empty_array(allocation.buffer + 1, output_len); - struct aws_byte_cursor expected_buf = aws_byte_cursor_from_array(expected, expected_size); + struct aws_byte_cursor expected_buf = aws_byte_cursor_from_array(expected, expected_len); ASSERT_SUCCESS(aws_base64_decode(&expected_buf, &output), "decode call should have succeeded"); ASSERT_BIN_ARRAYS_EQUALS( test_str, - test_str_size, + test_str_len, output.buffer, - output_size, + output_len, "Decode output should have been {%s} (len=%zu).", test_str, - test_str_size); + test_str_len); ASSERT_INT_EQUALS( (unsigned char)*(allocation.buffer), (unsigned char)0xdd, "Write should not have occurred before the start of the buffer."); ASSERT_INT_EQUALS( - (unsigned char)*(allocation.buffer + output_size + 1), + (unsigned char)*(allocation.buffer + output_len + 1), (unsigned char)0xdd, "Write should not have occurred after the start of the buffer."); @@ -398,7 +378,7 @@ static int s_base64_encoding_test_case_empty(struct aws_allocator *allocator, vo char test_data[] = ""; char expected[] = ""; - return s_run_base64_encoding_test_case(allocator, test_data, sizeof(test_data) - 1, expected, sizeof(expected) - 1); + return s_run_base64_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(base64_encoding_test_case_empty_test, s_base64_encoding_test_case_empty) @@ -409,7 +389,7 @@ static int s_base64_encoding_test_case_f(struct aws_allocator *allocator, void * char test_data[] = "f"; char expected[] = "Zg=="; - return s_run_base64_encoding_test_case(allocator, test_data, sizeof(test_data) - 1, expected, sizeof(expected) - 1); + return s_run_base64_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(base64_encoding_test_case_f_test, s_base64_encoding_test_case_f) @@ -420,7 +400,7 @@ static int s_base64_encoding_test_case_fo(struct aws_allocator *allocator, void char test_data[] = "fo"; char expected[] = "Zm8="; - return s_run_base64_encoding_test_case(allocator, test_data, sizeof(test_data) - 1, expected, sizeof(expected) - 1); + return s_run_base64_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(base64_encoding_test_case_fo_test, s_base64_encoding_test_case_fo) @@ -431,7 +411,7 @@ static int s_base64_encoding_test_case_foo(struct aws_allocator *allocator, void char test_data[] = "foo"; char expected[] = "Zm9v"; - return s_run_base64_encoding_test_case(allocator, test_data, sizeof(test_data) - 1, expected, sizeof(expected) - 1); + return s_run_base64_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(base64_encoding_test_case_foo_test, s_base64_encoding_test_case_foo) @@ -442,7 +422,7 @@ static int s_base64_encoding_test_case_foob(struct aws_allocator *allocator, voi char test_data[] = "foob"; char expected[] = "Zm9vYg=="; - return s_run_base64_encoding_test_case(allocator, test_data, sizeof(test_data) - 1, expected, sizeof(expected) - 1); + return s_run_base64_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(base64_encoding_test_case_foob_test, s_base64_encoding_test_case_foob) @@ -453,7 +433,7 @@ static int s_base64_encoding_test_case_fooba(struct aws_allocator *allocator, vo char test_data[] = "fooba"; char expected[] = "Zm9vYmE="; - return s_run_base64_encoding_test_case(allocator, test_data, sizeof(test_data) - 1, expected, sizeof(expected) - 1); + return s_run_base64_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(base64_encoding_test_case_fooba_test, s_base64_encoding_test_case_fooba) @@ -464,7 +444,7 @@ static int s_base64_encoding_test_case_foobar(struct aws_allocator *allocator, v char test_data[] = "foobar"; char expected[] = "Zm9vYmFy"; - return s_run_base64_encoding_test_case(allocator, test_data, sizeof(test_data) - 1, expected, sizeof(expected) - 1); + return s_run_base64_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(base64_encoding_test_case_foobar_test, s_base64_encoding_test_case_foobar) @@ -476,7 +456,7 @@ static int s_base64_encoding_test_case_32bytes(struct aws_allocator *allocator, char test_data[] = "this is a 32 byte long string!!!"; char expected[] = "dGhpcyBpcyBhIDMyIGJ5dGUgbG9uZyBzdHJpbmchISE="; - return s_run_base64_encoding_test_case(allocator, test_data, sizeof(test_data) - 1, expected, sizeof(expected) - 1); + return s_run_base64_encoding_test_case(allocator, test_data, strlen(test_data), expected, strlen(expected)); } AWS_TEST_CASE(base64_encoding_test_case_32bytes_test, s_base64_encoding_test_case_32bytes) @@ -487,8 +467,7 @@ static int s_base64_encoding_test_zeros_fn(struct aws_allocator *allocator, void uint8_t test_data[6] = {0}; char expected[] = "AAAAAAAA"; - return s_run_base64_encoding_test_case( - allocator, (char *)test_data, sizeof(test_data), expected, sizeof(expected) - 1); + return s_run_base64_encoding_test_case(allocator, (char *)test_data, sizeof(test_data), expected, strlen(expected)); } AWS_TEST_CASE(base64_encoding_test_zeros, s_base64_encoding_test_zeros_fn) @@ -511,8 +490,8 @@ static int s_base64_encoding_test_roundtrip(struct aws_allocator *allocator, voi } struct aws_byte_cursor original_data = aws_byte_cursor_from_array(test_data, sizeof(test_data)); - uint8_t test_hex[65] = {0}; - struct aws_byte_buf hex = aws_byte_buf_from_empty_array(test_hex, sizeof(test_hex)); + struct aws_byte_buf hex; + aws_byte_buf_init(&hex, allocator, 65); uint8_t test_b64[128] = {0}; struct aws_byte_buf b64_data = aws_byte_buf_from_empty_array(test_b64, sizeof(test_b64)); @@ -520,8 +499,8 @@ static int s_base64_encoding_test_roundtrip(struct aws_allocator *allocator, voi aws_base64_encode(&original_data, &b64_data); b64_data.len--; - uint8_t decoded_data[32] = {0}; - struct aws_byte_buf decoded_buf = aws_byte_buf_from_empty_array(decoded_data, sizeof(decoded_data)); + struct aws_byte_buf decoded_buf; + aws_byte_buf_init(&decoded_buf, allocator, 32); struct aws_byte_cursor b64_cur = aws_byte_cursor_from_buf(&b64_data); aws_base64_decode(&b64_cur, &decoded_buf); @@ -529,7 +508,7 @@ static int s_base64_encoding_test_roundtrip(struct aws_allocator *allocator, voi if (memcmp(decoded_buf.buffer, original_data.ptr, decoded_buf.len) != 0) { aws_hex_encode(&original_data, &hex); fprintf(stderr, "Base64 round-trip failed\n"); - fprintf(stderr, "Original: %s\n", (char *)test_hex); + fprintf(stderr, "Original: " PRInSTR "\n", AWS_BYTE_BUF_PRI(hex)); fprintf(stderr, "Base64 : "); for (size_t i = 0; i < sizeof(test_b64); i++) { if (!test_b64[i]) { @@ -538,13 +517,15 @@ static int s_base64_encoding_test_roundtrip(struct aws_allocator *allocator, voi fprintf(stderr, " %c", test_b64[i]); } fprintf(stderr, "\n"); - memset(test_hex, 0, sizeof(test_hex)); + aws_byte_buf_reset(&hex, true /*zero-contents*/); struct aws_byte_cursor decoded_cur = aws_byte_cursor_from_buf(&decoded_buf); aws_hex_encode(&decoded_cur, &hex); - fprintf(stderr, "Decoded : %s\n", (char *)test_hex); + fprintf(stderr, "Decoded : " PRInSTR "\n", AWS_BYTE_BUF_PRI(hex)); return 1; } + aws_byte_buf_clean_up(&hex); + aws_byte_buf_clean_up(&decoded_buf); return 0; } AWS_TEST_CASE(base64_encoding_test_roundtrip, s_base64_encoding_test_roundtrip) @@ -569,8 +550,7 @@ static int s_base64_encoding_test_all_values_fn(struct aws_allocator *allocator, "jY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0t" "PU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+"; - return s_run_base64_encoding_test_case( - allocator, (char *)test_data, sizeof(test_data), expected, sizeof(expected) - 1); + return s_run_base64_encoding_test_case(allocator, (char *)test_data, sizeof(test_data), expected, strlen(expected)); } AWS_TEST_CASE(base64_encoding_test_all_values, s_base64_encoding_test_all_values_fn) @@ -608,7 +588,6 @@ static int s_base64_encoding_buffer_size_overflow_test_fn(struct aws_allocator * (void)ctx; char test_data[] = "foobar"; - char encoded_data[] = "Zm9vYmFy"; /* kill off the last two bits, so the not a multiple of 4 check doesn't * trigger first */ size_t overflow = (SIZE_MAX - 1) & ~0x03; @@ -622,12 +601,8 @@ static int s_base64_encoding_buffer_size_overflow_test_fn(struct aws_allocator * aws_base64_encode(&test_buf, &output_buf), "overflow buffer size should have failed with AWS_ERROR_OVERFLOW_DETECTED"); - struct aws_byte_cursor encoded_buf = aws_byte_cursor_from_array(encoded_data, overflow); + /* NOTE: decode() math can't overflow, output.len ends up smaller than input.len */ - ASSERT_ERROR( - AWS_ERROR_OVERFLOW_DETECTED, - aws_base64_decode(&encoded_buf, &output_buf), - "overflow buffer size should have failed with AWS_ERROR_OVERFLOW_DETECTED"); return 0; } @@ -994,47 +969,20 @@ static int read_file_contents(struct aws_byte_buf *out_buf, struct aws_allocator FILE *fp = aws_fopen(filename, "r"); ASSERT_NOT_NULL(fp); - if (fp) { - if (fseek(fp, 0L, SEEK_END)) { - fclose(fp); - ASSERT_FALSE(true, "Failed to seek to end"); - return AWS_OP_ERR; - } + ASSERT_INT_EQUALS(fseek(fp, 0L, SEEK_END), 0); + size_t allocation_size = (size_t)ftell(fp); + ASSERT_SUCCESS(aws_byte_buf_init(out_buf, alloc, allocation_size)); + ASSERT_INT_EQUALS(fseek(fp, 0L, SEEK_SET), 0); - size_t allocation_size = (size_t)ftell(fp) + 1; - /* Tell the user that we allocate here and if success they're responsible for the free. */ - if (aws_byte_buf_init(out_buf, alloc, allocation_size)) { - fclose(fp); - ASSERT_FALSE(true, "Failed to init buffer"); - return AWS_OP_ERR; - } - - /* Ensure compatibility with null-terminated APIs, but don't consider - * the null terminator part of the length of the payload */ - out_buf->len = out_buf->capacity - 1; - out_buf->buffer[out_buf->len] = 0; - - if (fseek(fp, 0L, SEEK_SET)) { - aws_byte_buf_clean_up(out_buf); - fclose(fp); - ASSERT_FALSE(true, "Failed to seek to start"); - return AWS_OP_ERR; - } - - size_t read = fread(out_buf->buffer, 1, out_buf->len, fp); - fclose(fp); - if (read < (out_buf->len - 1)) { - ASSERT_INT_EQUALS(read, out_buf->len); - aws_byte_buf_clean_up(out_buf); - return AWS_OP_ERR; - } - - out_buf->len = read; - - return AWS_OP_SUCCESS; + size_t read = fread(out_buf->buffer, 1, allocation_size, fp); + /* size from doing seek-to-end is sometimes 1 byte more than what we get from read (observed on Windows) */ + if (read < (allocation_size - 1)) { + ASSERT_INT_EQUALS(read, allocation_size); } + out_buf->len = read; - return AWS_OP_ERR; + ASSERT_INT_EQUALS(fclose(fp), 0); + return AWS_OP_SUCCESS; } static int s_text_encoding_utf8(struct aws_allocator *allocator, void *ctx) { diff --git a/tests/fuzz/hex_encoding_transitive.c b/tests/fuzz/hex_encoding_transitive.c index b030ca72f..af0f18f72 100644 --- a/tests/fuzz/hex_encoding_transitive.c +++ b/tests/fuzz/hex_encoding_transitive.c @@ -22,7 +22,6 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { result = aws_hex_encode(&to_encode, &encode_output); AWS_ASSERT(result == AWS_OP_SUCCESS); - --encode_output.len; /* Remove null terminator */ result = aws_hex_compute_decoded_len(encode_output.len, &output_size); AWS_ASSERT(result == AWS_OP_SUCCESS); From 568f46b1c83e1f20ff869cfa8709660c68a67e24 Mon Sep 17 00:00:00 2001 From: Waqar Ahmed Khan Date: Tue, 4 Mar 2025 13:28:20 -0800 Subject: [PATCH 2/2] New Get_ENV Functions (#1141) --- include/aws/common/environment.h | 16 +++++++++++++ source/posix/environment.c | 20 ++++++++++++++++ source/windows/environment.c | 34 +++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/environment_test.c | 40 ++++++++++++++++++++++++++++++++ 5 files changed, 111 insertions(+) diff --git a/include/aws/common/environment.h b/include/aws/common/environment.h index ae712548e..2958e5154 100644 --- a/include/aws/common/environment.h +++ b/include/aws/common/environment.h @@ -24,6 +24,22 @@ AWS_EXTERN_C_BEGIN * Not thread-safe */ AWS_COMMON_API +struct aws_string *aws_get_env(struct aws_allocator *allocator, const char *name); + +/* + * Get the value of an environment variable. If the variable is not set or is empty, the output string will be set to + * NULL. Not thread-safe + */ +AWS_COMMON_API +struct aws_string *aws_get_env_nonempty(struct aws_allocator *allocator, const char *name); + +/* + * *DEPRECATED* + * Please use the `aws_get_env` or `aws_get_env_nonempty` instead. + * Get the value of an environment variable. If the variable is not set, the output string will be set to NULL. + * Not thread-safe + */ +AWS_COMMON_API int aws_get_environment_value( struct aws_allocator *allocator, const struct aws_string *variable_name, diff --git a/source/posix/environment.c b/source/posix/environment.c index f4b69caea..ba1f8bb44 100644 --- a/source/posix/environment.c +++ b/source/posix/environment.c @@ -8,6 +8,26 @@ #include #include +struct aws_string *aws_get_env(struct aws_allocator *allocator, const char *name) { + + const char *value = getenv(name); + if (value == NULL) { + return NULL; + } + + return aws_string_new_from_c_str(allocator, value); +} + +struct aws_string *aws_get_env_nonempty(struct aws_allocator *allocator, const char *name) { + + const char *value = getenv(name); + if (value == NULL || value[0] == '\0') { + return NULL; + } + + return aws_string_new_from_c_str(allocator, value); +} + int aws_get_environment_value( struct aws_allocator *allocator, const struct aws_string *variable_name, diff --git a/source/windows/environment.c b/source/windows/environment.c index 1ede1e8a7..4d7e16e1a 100644 --- a/source/windows/environment.c +++ b/source/windows/environment.c @@ -8,6 +8,40 @@ #include +struct aws_string *aws_get_env(struct aws_allocator *allocator, const char *name) { +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4996) +#endif + const char *value = getenv(name); +#ifdef _MSC_VER +# pragma warning(pop) +#endif + + if (value == NULL) { + return NULL; + } + + return aws_string_new_from_c_str(allocator, value); +} + +struct aws_string *aws_get_env_nonempty(struct aws_allocator *allocator, const char *name) { +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4996) +#endif + const char *value = getenv(name); +#ifdef _MSC_VER +# pragma warning(pop) +#endif + + if (value == NULL || value[0] == '\0') { + return NULL; + } + + return aws_string_new_from_c_str(allocator, value); +} + int aws_get_environment_value( struct aws_allocator *allocator, const struct aws_string *variable_name, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 41f6feeb9..e346f428b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -391,6 +391,7 @@ add_test_case(uuid_string_parse_too_short) add_test_case(uuid_string_parse_malformed) add_test_case(test_environment_functions) +add_test_case(test_env_functions) add_test_case(short_argument_parse) add_test_case(long_argument_parse) diff --git a/tests/environment_test.c b/tests/environment_test.c index 501a175c5..a834d8786 100644 --- a/tests/environment_test.c +++ b/tests/environment_test.c @@ -42,3 +42,43 @@ static int s_test_environment_functions_fn(struct aws_allocator *allocator, void } AWS_TEST_CASE(test_environment_functions, s_test_environment_functions_fn) + +static int s_test_env_functions_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + const char *env_name = aws_string_c_str(s_test_variable); + struct aws_string *value = aws_get_env(allocator, env_name); + ASSERT_TRUE(value == NULL); + + value = aws_get_env_nonempty(allocator, env_name); + ASSERT_TRUE(value == NULL); + + int result = aws_set_environment_value(s_test_variable, (struct aws_string *)s_test_value); + ASSERT_TRUE(result == AWS_OP_SUCCESS); + + value = aws_get_env(allocator, env_name); + ASSERT_TRUE(aws_string_compare(value, s_test_value) == 0); + aws_string_destroy(value); + + value = aws_get_env_nonempty(allocator, env_name); + ASSERT_TRUE(aws_string_compare(value, s_test_value) == 0); + aws_string_destroy(value); + + struct aws_string *empty_str = aws_string_new_from_c_str(allocator, ""); + result = aws_set_environment_value(s_test_variable, empty_str); + ASSERT_TRUE(result == AWS_OP_SUCCESS); + + value = aws_get_env(allocator, env_name); +#ifndef AWS_OS_WINDOWS + ASSERT_TRUE(aws_string_compare(value, empty_str) == 0); +#endif + aws_string_destroy(value); + + value = aws_get_env_nonempty(allocator, env_name); + ASSERT_TRUE(value == NULL); + + aws_string_destroy(empty_str); + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(test_env_functions, s_test_env_functions_fn)