From 545ca3069918a1f2c9821e647cb509678ecb7621 Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Sun, 20 Sep 2026 00:29:47 +0200 Subject: [PATCH] fix(sacn): validate universe/start_slot via get_int(), reject fractional input --- src/net/nl_api.c | 20 ++++++++++---------- test/test_api.c | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/net/nl_api.c b/src/net/nl_api.c index d85608b..80d4fd6 100644 --- a/src/net/nl_api.c +++ b/src/net/nl_api.c @@ -352,18 +352,18 @@ static void handle_put_settings(const nl_api_ctx_t *ctx, const nl_http_request_t s.auth_all = cJSON_IsTrue(all) ? 1 : 0; } else if (strcmp(type, "sacn") == 0) { const cJSON *en = cJSON_GetObjectItemCaseSensitive(j, "enabled"); - const cJSON *un = cJSON_GetObjectItemCaseSensitive(j, "universe"); - const cJSON *st = cJSON_GetObjectItemCaseSensitive(j, "start_slot"); const char *ot = NULL; - int otp; + long un = 0, st = 0; + int unp, stp, otp; uint8_t slots = nl_board_dmx_slots(ctx->board); if (en && !cJSON_IsBool(en)) { err = "invalid enabled"; - } else if (un && (!cJSON_IsNumber(un) || un->valuedouble < 1 || un->valuedouble > 63999)) { + } else if ((unp = get_int(j, "universe", 1, 63999, &un)) < 0) { err = "invalid universe"; - } else if (st && (!cJSON_IsNumber(st) || st->valuedouble < 1 || - st->valuedouble + slots - 1 > 512)) { + /* Upper bound folds the board's slot count in: the highest start_slot that still + * leaves room for all of them is 512 - slots + 1. */ + } else if ((stp = get_int(j, "start_slot", 1, 512 - slots + 1, &st)) < 0) { err = "invalid start_slot"; } else if ((otp = get_string(j, "on_timeout", 1, 16, &ot)) < 0 || (otp == 1 && strcmp(ot, "release") != 0 && strcmp(ot, "hold") != 0)) { @@ -371,10 +371,10 @@ static void handle_put_settings(const nl_api_ctx_t *ctx, const nl_http_request_t } else { if (en) s.sacn_enabled = cJSON_IsTrue(en) ? 1 : 0; - if (un) - s.sacn_universe = (uint16_t)un->valuedouble; - if (st) - s.sacn_start_slot = (uint16_t)st->valuedouble; + if (unp == 1) + s.sacn_universe = (uint16_t)un; + if (stp == 1) + s.sacn_start_slot = (uint16_t)st; if (otp == 1) s.sacn_hold = strcmp(ot, "hold") == 0 ? 1 : 0; } diff --git a/test/test_api.c b/test/test_api.c index 553c5be..0cb8867 100644 --- a/test/test_api.c +++ b/test/test_api.c @@ -424,6 +424,21 @@ static void test_put_sacn_rejects_bad_values(void) TEST_ASSERT_EQUAL_INT(0, fake_save_calls); /* nichts davon darf gespeichert werden */ } +/* universe/start_slot muessen ueber get_int() laufen wie jedes andere Zahlenfeld in dieser + * Datei - ein Bruchwert darf nicht klammheimlich auf einen Integer abgeschnitten werden. */ +static void test_put_sacn_rejects_fractional_numbers(void) +{ + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"universe\":12.5}"); + TEST_ASSERT_EQUAL_INT(400, resp.status); + TEST_ASSERT_EQUAL_STRING("invalid universe", error_text()); + + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, "{\"type\":\"sacn\",\"start_slot\":2.5}"); + TEST_ASSERT_EQUAL_INT(400, resp.status); + TEST_ASSERT_EQUAL_STRING("invalid start_slot", error_text()); + + TEST_ASSERT_EQUAL_INT(0, fake_save_calls); +} + /* Ein Board mit RGB braucht 6 Slots: ab 508 passt der Satz nicht mehr ins Universe. */ static void test_put_sacn_start_slot_must_leave_room_for_all_slots(void) { @@ -518,6 +533,7 @@ int main(void) RUN_TEST(test_put_sacn_all_fields); RUN_TEST(test_put_sacn_single_field_leaves_rest); RUN_TEST(test_put_sacn_rejects_bad_values); + RUN_TEST(test_put_sacn_rejects_fractional_numbers); RUN_TEST(test_put_sacn_start_slot_must_leave_room_for_all_slots); RUN_TEST(test_put_sacn_start_slot_limit_follows_the_board); RUN_TEST(test_info_exposes_sacn_state);