From dfe7efcb34dc6747dfc9b942f47b190a73c9ba5e Mon Sep 17 00:00:00 2001 From: Danny Smith Date: Wed, 7 Jan 2026 17:05:48 +1100 Subject: [PATCH 1/2] Validate long string value label key length against storage width in SAV writer --- src/spss/readstat_sav_write.c | 5 +++++ src/test/test_list.h | 28 ++++++++++++++++++++++++++++ src/test/test_types.h | 1 + src/test/test_write.c | 28 ++++++++++++++++------------ 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/spss/readstat_sav_write.c b/src/spss/readstat_sav_write.c index 4355a7aa..bb2a6b00 100644 --- a/src/spss/readstat_sav_write.c +++ b/src/spss/readstat_sav_write.c @@ -928,6 +928,11 @@ static readstat_error_t sav_emit_long_string_value_labels_record(readstat_writer if (label_len > MAX_VALUE_LABEL_SIZE) label_len = MAX_VALUE_LABEL_SIZE; + if (r_value_label->string_key_len > storage_width) { + retval = READSTAT_ERROR_STRING_VALUE_IS_TOO_LONG; + goto cleanup; + } + info_header.count += sizeof(int32_t); // value length info_header.count += storage_width; info_header.count += sizeof(int32_t); // label length diff --git a/src/test/test_list.h b/src/test/test_list.h index 90f4f6d1..e831f7ff 100644 --- a/src/test/test_list.h +++ b/src/test/test_list.h @@ -1737,6 +1737,34 @@ static rt_test_group_t _test_groups[] = { .label_set = "labels0" } } + }, + + { + .label = "SAV long string value label key too long", + .write_error = READSTAT_ERROR_STRING_VALUE_IS_TOO_LONG, + .test_formats = RT_FORMAT_SAV, + .label_sets_count = 1, + .label_sets = { + { + .name = "labels0", + .type = READSTAT_TYPE_STRING, + .value_labels_count = 1, + .value_labels = { + { + .value = { .type = READSTAT_TYPE_STRING, .v = { .string_value = "0123456789ABCDEFG" } }, + .label = "Too long key" + } + } + } + }, + .columns = { + { + .name = "VAR1", + .type = READSTAT_TYPE_STRING, + .storage_width = 9, + .label_set = "labels0" + } + } } } }, diff --git a/src/test/test_types.h b/src/test/test_types.h index c0d762d4..7458c2fe 100644 --- a/src/test/test_types.h +++ b/src/test/test_types.h @@ -28,6 +28,7 @@ typedef struct rt_column_s { char label[RT_MAX_STRING]; char format[RT_MAX_STRING]; int display_width; + int storage_width; readstat_alignment_t alignment; readstat_measure_t measure; readstat_type_t type; diff --git a/src/test/test_write.c b/src/test/test_write.c index 8d68997a..f3dac342 100644 --- a/src/test/test_write.c +++ b/src/test/test_write.c @@ -89,19 +89,23 @@ readstat_error_t write_file_to_buffer(rt_test_file_t *file, rt_buffer_t *buffer, size_t max_len = 0; if (column->type == READSTAT_TYPE_STRING) { - max_len = 8; - for (i=0; irows; i++) { - const char *value = readstat_string_value(column->values[i]); - if (value) { - size_t len = strlen(value); - if (len > max_len) - max_len = len; + if (column->storage_width > 0) { + max_len = column->storage_width; + } else { + max_len = 8; + for (i=0; irows; i++) { + const char *value = readstat_string_value(column->values[i]); + if (value) { + size_t len = strlen(value); + if (len > max_len) + max_len = len; + } } - } - if (label_set) { - for (i=0; ivalue_labels_count; i++) { - if (label_set->value_labels[i].string_key_len > max_len) - max_len = label_set->value_labels[i].string_key_len; + if (label_set) { + for (i=0; ivalue_labels_count; i++) { + if (label_set->value_labels[i].string_key_len > max_len) + max_len = label_set->value_labels[i].string_key_len; + } } } } From 3a027dd9f6391ebf515119ef9b75ec33ac4a9bb4 Mon Sep 17 00:00:00 2001 From: Danny Smith Date: Wed, 7 Jan 2026 19:19:07 +1100 Subject: [PATCH 2/2] Use user_width instead of storage_width when writing long string value labels --- src/spss/readstat_sav_write.c | 18 ++++++++++-------- src/test/test_list.h | 2 +- src/test/test_types.h | 2 +- src/test/test_write.c | 4 ++-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/spss/readstat_sav_write.c b/src/spss/readstat_sav_write.c index bb2a6b00..0e16a924 100644 --- a/src/spss/readstat_sav_write.c +++ b/src/spss/readstat_sav_write.c @@ -913,6 +913,7 @@ static readstat_error_t sav_emit_long_string_value_labels_record(readstat_writer for (k=0; kname); + int32_t user_width = r_variable->user_width; int32_t storage_width = readstat_variable_get_storage_width(r_variable); if (storage_width <= 8) continue; @@ -928,13 +929,13 @@ static readstat_error_t sav_emit_long_string_value_labels_record(readstat_writer if (label_len > MAX_VALUE_LABEL_SIZE) label_len = MAX_VALUE_LABEL_SIZE; - if (r_value_label->string_key_len > storage_width) { + if (r_value_label->string_key_len > user_width) { retval = READSTAT_ERROR_STRING_VALUE_IS_TOO_LONG; goto cleanup; } info_header.count += sizeof(int32_t); // value length - info_header.count += storage_width; + info_header.count += user_width; info_header.count += sizeof(int32_t); // label length info_header.count += label_len; } @@ -959,12 +960,13 @@ static readstat_error_t sav_emit_long_string_value_labels_record(readstat_writer for (k=0; kname); + int32_t user_width = r_variable->user_width; int32_t storage_width = readstat_variable_get_storage_width(r_variable); if (storage_width <= 8) continue; - space_buffer = realloc(space_buffer, storage_width); - memset(space_buffer, ' ', storage_width); + space_buffer = realloc(space_buffer, user_width); + memset(space_buffer, ' ', user_width); retval = readstat_write_bytes(writer, &name_len, sizeof(int32_t)); if (retval != READSTAT_OK) @@ -974,7 +976,7 @@ static readstat_error_t sav_emit_long_string_value_labels_record(readstat_writer if (retval != READSTAT_OK) goto cleanup; - retval = readstat_write_bytes(writer, &storage_width, sizeof(int32_t)); + retval = readstat_write_bytes(writer, &user_width, sizeof(int32_t)); if (retval != READSTAT_OK) goto cleanup; @@ -989,7 +991,7 @@ static readstat_error_t sav_emit_long_string_value_labels_record(readstat_writer if (label_len > MAX_VALUE_LABEL_SIZE) label_len = MAX_VALUE_LABEL_SIZE; - retval = readstat_write_bytes(writer, &storage_width, sizeof(int32_t)); + retval = readstat_write_bytes(writer, &user_width, sizeof(int32_t)); if (retval != READSTAT_OK) goto cleanup; @@ -997,8 +999,8 @@ static readstat_error_t sav_emit_long_string_value_labels_record(readstat_writer if (retval != READSTAT_OK) goto cleanup; - if (value_len < storage_width) { - retval = readstat_write_bytes(writer, space_buffer, storage_width - value_len); + if (value_len < user_width) { + retval = readstat_write_bytes(writer, space_buffer, user_width - value_len); if (retval != READSTAT_OK) goto cleanup; } diff --git a/src/test/test_list.h b/src/test/test_list.h index e831f7ff..61dca89d 100644 --- a/src/test/test_list.h +++ b/src/test/test_list.h @@ -1761,7 +1761,7 @@ static rt_test_group_t _test_groups[] = { { .name = "VAR1", .type = READSTAT_TYPE_STRING, - .storage_width = 9, + .user_width = 9, .label_set = "labels0" } } diff --git a/src/test/test_types.h b/src/test/test_types.h index 7458c2fe..f0a55e01 100644 --- a/src/test/test_types.h +++ b/src/test/test_types.h @@ -28,7 +28,7 @@ typedef struct rt_column_s { char label[RT_MAX_STRING]; char format[RT_MAX_STRING]; int display_width; - int storage_width; + int user_width; readstat_alignment_t alignment; readstat_measure_t measure; readstat_type_t type; diff --git a/src/test/test_write.c b/src/test/test_write.c index f3dac342..89f337bd 100644 --- a/src/test/test_write.c +++ b/src/test/test_write.c @@ -89,8 +89,8 @@ readstat_error_t write_file_to_buffer(rt_test_file_t *file, rt_buffer_t *buffer, size_t max_len = 0; if (column->type == READSTAT_TYPE_STRING) { - if (column->storage_width > 0) { - max_len = column->storage_width; + if (column->user_width > 0) { + max_len = column->user_width; } else { max_len = 8; for (i=0; irows; i++) {