@@ -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+
9671104TEST_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