Skip to content

Commit 56e4b0b

Browse files
committed
tests: fs: add regression test for metadata unsigned underflow bug
Signed-off-by: Eduardo Silva <[email protected]>
1 parent c5648ef commit 56e4b0b

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

tests/fs.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,143 @@ void test_legacy_failure()
964964
test_legacy_core(CIO_TRUE);
965965
}
966966

967+
/*
968+
* Test case: Prevent unsigned underflow when writing large metadata to a
969+
* chunk with small initial allocation.
970+
*
971+
* This test specifically validates the fix for the bug where calculating
972+
* content_av = alloc_size - CIO_FILE_HEADER_MIN - size could underflow
973+
* when size is large, causing the resize check to be skipped and leading
974+
* to out-of-bounds writes.
975+
*
976+
* Scenario:
977+
* - Create chunk with minimal initial size (e.g., ~100 bytes page-aligned)
978+
* - Write small metadata (10 bytes) and small content (20 bytes)
979+
* - Write large metadata (80 bytes) that would cause underflow:
980+
* old code: content_av = 100 - 24 - 80 = -4 (wraps to huge unsigned)
981+
* - Verify resize happens correctly and no buffer overrun occurs
982+
*/
983+
static void test_metadata_unsigned_underflow()
984+
{
985+
int ret;
986+
int err;
987+
char *meta_buf;
988+
int meta_len;
989+
void *content_buf;
990+
size_t content_size;
991+
struct cio_ctx *ctx;
992+
struct cio_chunk *chunk;
993+
struct cio_stream *stream;
994+
struct cio_options cio_opts;
995+
996+
/* Test data */
997+
const char *small_meta = "small";
998+
const char *large_meta = "this-is-a-very-large-metadata-string-that-would-cause-unsigned-underflow-in-old-code";
999+
const char *content_data = "test-content";
1000+
1001+
/* Cleanup any existing test directory */
1002+
cio_utils_recursive_delete(CIO_ENV);
1003+
1004+
/* Initialize options */
1005+
cio_options_init(&cio_opts);
1006+
cio_opts.root_path = CIO_ENV;
1007+
cio_opts.log_cb = log_cb;
1008+
cio_opts.log_level = CIO_LOG_INFO;
1009+
cio_opts.flags = CIO_CHECKSUM;
1010+
1011+
/* Create context */
1012+
ctx = cio_create(&cio_opts);
1013+
TEST_CHECK(ctx != NULL);
1014+
if (!ctx) {
1015+
printf("cannot create context\n");
1016+
exit(1);
1017+
}
1018+
1019+
/* Create stream */
1020+
stream = cio_stream_create(ctx, "test_stream_underflow", CIO_STORE_FS);
1021+
TEST_CHECK(stream != NULL);
1022+
if (!stream) {
1023+
printf("cannot create stream\n");
1024+
cio_destroy(ctx);
1025+
exit(1);
1026+
}
1027+
1028+
/* Create chunk with minimal initial size (forces small alloc_size) */
1029+
chunk = cio_chunk_open(ctx, stream, "test_chunk_underflow", CIO_OPEN, 100, &err);
1030+
TEST_CHECK(chunk != NULL);
1031+
if (!chunk) {
1032+
printf("cannot open chunk\n");
1033+
cio_destroy(ctx);
1034+
exit(1);
1035+
}
1036+
1037+
/* Step 1: Write small initial metadata */
1038+
ret = cio_meta_write(chunk, (char *) small_meta, strlen(small_meta));
1039+
TEST_CHECK(ret == CIO_OK);
1040+
1041+
/* Step 2: Write some content data */
1042+
ret = cio_chunk_write(chunk, content_data, strlen(content_data));
1043+
TEST_CHECK(ret == CIO_OK);
1044+
1045+
/* Verify initial state */
1046+
ret = cio_meta_read(chunk, &meta_buf, &meta_len);
1047+
TEST_CHECK(ret == CIO_OK);
1048+
TEST_CHECK(meta_len == (int) strlen(small_meta));
1049+
TEST_CHECK(memcmp(meta_buf, small_meta, strlen(small_meta)) == 0);
1050+
1051+
ret = cio_chunk_get_content_copy(chunk, &content_buf, &content_size);
1052+
TEST_CHECK(ret == CIO_OK);
1053+
TEST_CHECK(content_size == strlen(content_data));
1054+
TEST_CHECK(memcmp(content_buf, content_data, strlen(content_data)) == 0);
1055+
free(content_buf);
1056+
1057+
/* Step 3: Write large metadata that would cause underflow in old code
1058+
* This is the critical test - the new metadata (80 bytes) is larger than
1059+
* what would fit without resize, and with a small initial alloc_size,
1060+
* the old calculation would have underflowed.
1061+
*/
1062+
ret = cio_meta_write(chunk, (char *) large_meta, strlen(large_meta));
1063+
TEST_CHECK(ret == CIO_OK);
1064+
1065+
/* Step 4: Verify metadata was written correctly */
1066+
ret = cio_meta_read(chunk, &meta_buf, &meta_len);
1067+
TEST_CHECK(ret == CIO_OK);
1068+
TEST_CHECK(meta_len == (int) strlen(large_meta));
1069+
TEST_CHECK(memcmp(meta_buf, large_meta, strlen(large_meta)) == 0);
1070+
1071+
/* Step 5: Verify content data integrity - must be preserved */
1072+
ret = cio_chunk_get_content_copy(chunk, &content_buf, &content_size);
1073+
TEST_CHECK(ret == CIO_OK);
1074+
TEST_CHECK(content_size == strlen(content_data));
1075+
TEST_CHECK(memcmp(content_buf, content_data, strlen(content_data)) == 0);
1076+
1077+
/* Step 6: Sync to disk to ensure persistence */
1078+
ret = cio_chunk_sync(chunk);
1079+
TEST_CHECK(ret == CIO_OK);
1080+
1081+
/* Step 7: Put chunk down and up again to test persistence */
1082+
ret = cio_chunk_down(chunk);
1083+
TEST_CHECK(ret == CIO_OK);
1084+
1085+
ret = cio_chunk_up(chunk);
1086+
TEST_CHECK(ret == CIO_OK);
1087+
1088+
/* Step 8: Final validation after persistence cycle */
1089+
ret = cio_meta_read(chunk, &meta_buf, &meta_len);
1090+
TEST_CHECK(ret == CIO_OK);
1091+
TEST_CHECK(meta_len == (int) strlen(large_meta));
1092+
TEST_CHECK(memcmp(meta_buf, large_meta, strlen(large_meta)) == 0);
1093+
1094+
ret = cio_chunk_get_content_copy(chunk, &content_buf, &content_size);
1095+
TEST_CHECK(ret == CIO_OK);
1096+
TEST_CHECK(content_size == strlen(content_data));
1097+
TEST_CHECK(memcmp(content_buf, content_data, strlen(content_data)) == 0);
1098+
1099+
/* Cleanup */
1100+
free(content_buf);
1101+
cio_destroy(ctx);
1102+
}
1103+
9671104
TEST_LIST = {
9681105
{"fs_write", test_fs_write},
9691106
{"fs_checksum", test_fs_checksum},
@@ -976,5 +1113,6 @@ TEST_LIST = {
9761113
{"fs_deep_hierachy", test_deep_hierarchy},
9771114
{"legacy_success", test_legacy_success},
9781115
{"legacy_failure", test_legacy_failure},
1116+
{"metadata_unsigned_underflow", test_metadata_unsigned_underflow},
9791117
{ 0 }
9801118
};

0 commit comments

Comments
 (0)