From af23558aa3b609b420e1fbb959e971d0b25140c0 Mon Sep 17 00:00:00 2001 From: Logikoma Date: Wed, 5 Aug 2026 18:37:55 -0400 Subject: [PATCH] Reject overlong host type and field names Co-authored-by: Andrew Kesterson --- include/akbasic/host.h | 3 ++ src/host.c | 16 +++------- tests/hoststruct.c | 67 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/include/akbasic/host.h b/include/akbasic/host.h index c2d8273..b956242 100644 --- a/include/akbasic/host.h +++ b/include/akbasic/host.h @@ -105,6 +105,8 @@ typedef struct * Registering before the script is loaded is the normal case. A host type and a * `TYPE` the script declares share one namespace, so a script cannot declare a * type the host already registered -- and would be refused if it tried. + * Host type and field names are limited to 31 characters, matching + * script-declared types and fields. * * @param obj Object to initialize, inspect, or modify. * @param type The host's description of its own struct. @@ -112,6 +114,7 @@ typedef struct * @throws AKERR_NULLPOINTER When either argument is NULL. * @throws AKBASIC_ERR_VALUE When a field name carries no type suffix, a nested * type is not registered, or the name is already taken. + * @throws AKERR_OUTOFBOUNDS When a type or field name exceeds 31 characters. * @throws AKBASIC_ERR_BOUNDS When the type table or a field list is full. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_register_type(struct akbasic_Runtime *obj, const akbasic_HostType *type); diff --git a/src/host.c b/src/host.c index 2e62ed0..88f1917 100644 --- a/src/host.c +++ b/src/host.c @@ -235,16 +235,9 @@ akerr_ErrorContext *akbasic_host_register_type(akbasic_Runtime *obj, const akbas dest = &obj->structtypes.types[obj->structtypes.count]; PASS(errctx, aksl_memset(dest, 0, sizeof(*dest))); - /* - * Raw snprintf, and a latent defect rather than a settled decision: a host - * type name over 31 characters truncates silently here, and two that share a - * 31-character prefix then collide in akbasic_structtype_find. scan_names() - * in structtype.c already refuses the same case for a script-declared type - * with an explicit limit message, so the two paths disagree. Converting this - * to aksl_strcpy is the fix and it is a behaviour change on a public - * registration call, so it wants its own issue rather than this port. - */ - snprintf(dest->name, sizeof(dest->name), "%s", type->name); + /* Host and script registration share the 32-byte-including-NUL limit for + both type names and field names. */ + PASS(errctx, aksl_strcpy(dest->name, sizeof(dest->name), type->name)); dest->used = true; dest->ishost = true; dest->hostsize = type->size; @@ -270,8 +263,7 @@ akerr_ErrorContext *akbasic_host_register_type(akbasic_Runtime *obj, const akbas "%s.%s must end in '%c' for the C type it describes", type->name, src->name, suffix_for(src->kind)); - /* Same silent truncation as the type name above, and the same fix. */ - snprintf(field->name, sizeof(field->name), "%s", src->name); + PASS(errctx, aksl_strcpy(field->name, sizeof(field->name), src->name)); field->hostkind = src->kind; field->hostoffset = src->offset; field->hostwidth = src->width; diff --git a/tests/hoststruct.c b/tests/hoststruct.c index 1ff0d8b..2b1fe79 100644 --- a/tests/hoststruct.c +++ b/tests/hoststruct.c @@ -208,6 +208,72 @@ static void test_suffix_must_match_the_c_type(void) harness_stop(); } +/** @brief Host names use the same bounded storage as script-declared names. */ +static void test_registration_name_limits(void) +{ + static const akbasic_HostField SHORT_FIELD[] = { + AKBASIC_HOST_FIELD( test_Enemy, hp, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234#", + AKBASIC_HOSTFIELD_INT32 ) + }; + static const akbasic_HostField LONG_FIELD_A[] = { + AKBASIC_HOST_FIELD( test_Enemy, hp, "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456A#", + AKBASIC_HOSTFIELD_INT32 ) + }; + static const akbasic_HostField LONG_FIELD_B[] = { + AKBASIC_HOST_FIELD( test_Enemy, hp, "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456B#", + AKBASIC_HOSTFIELD_INT32 ) + }; + static const akbasic_HostType SHORT_TYPE = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345", sizeof(test_Enemy), SHORT_FIELD, 1 + }; + static const akbasic_HostType LONG_TYPE_A = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456A", sizeof(test_Enemy), SHORT_FIELD, 1 + }; + static const akbasic_HostType LONG_TYPE_B = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456B", sizeof(test_Enemy), SHORT_FIELD, 1 + }; + static const akbasic_HostType LONG_FIELD_TYPE_A = { + "LONGFIELDA", sizeof(test_Enemy), LONG_FIELD_A, 1 + }; + static const akbasic_HostType LONG_FIELD_TYPE_B = { + "LONGFIELDB", sizeof(test_Enemy), LONG_FIELD_B, 1 + }; + akerr_ErrorContext *raised = NULL; + + TEST_REQUIRE_OK(harness_start(NULL)); + TEST_REQUIRE_OK(akbasic_host_register_type(&HARNESS_RUNTIME, &SHORT_TYPE)); + harness_stop(); + + TEST_REQUIRE_OK(harness_start(NULL)); + raised = akbasic_host_register_type(&HARNESS_RUNTIME, &LONG_TYPE_A); + TEST_REQUIRE(raised != NULL, "an overlong type name must be refused"); + TEST_REQUIRE_INT(raised->status, AKERR_OUTOFBOUNDS); + test_discard_error(raised); + raised = akbasic_host_register_type(&HARNESS_RUNTIME, &LONG_TYPE_B); + TEST_REQUIRE(raised != NULL, "a second long type name must fail cleanly"); + TEST_REQUIRE_INT(raised->status, AKERR_OUTOFBOUNDS); + test_discard_error(raised); + harness_stop(); + + TEST_REQUIRE_OK(harness_start(NULL)); + TEST_REQUIRE_OK(akbasic_host_register_type(&HARNESS_RUNTIME, + &(akbasic_HostType){ + "SHORTFIELDS", sizeof(test_Enemy), SHORT_FIELD, 1 + })); + harness_stop(); + + TEST_REQUIRE_OK(harness_start(NULL)); + raised = akbasic_host_register_type(&HARNESS_RUNTIME, &LONG_FIELD_TYPE_A); + TEST_REQUIRE(raised != NULL, "an overlong field name must be refused"); + TEST_REQUIRE_INT(raised->status, AKERR_OUTOFBOUNDS); + test_discard_error(raised); + raised = akbasic_host_register_type(&HARNESS_RUNTIME, &LONG_FIELD_TYPE_B); + TEST_REQUIRE(raised != NULL, "a second long field name must fail cleanly"); + TEST_REQUIRE_INT(raised->status, AKERR_OUTOFBOUNDS); + test_discard_error(raised); + harness_stop(); +} + /** * @brief After unbinding, the name is refused rather than read. * @@ -258,6 +324,7 @@ int main(void) test_conversion_refuses_rather_than_truncates(); test_copy_versus_point(); test_suffix_must_match_the_c_type(); + test_registration_name_limits(); test_unbind_refuses_later_reads(); test_registration_survives_a_rerun();