From 06449e6f6e0b5afc0c3523d6d1fdd2230cbd6f82 Mon Sep 17 00:00:00 2001 From: belowzeroff Date: Fri, 18 Sep 2026 12:10:50 -0400 Subject: [PATCH] fix(system): validate launcher and timeit integer arguments --- src/app/main.c | 9 ++++- src/lang/syscmd.c | 19 +++++++++-- src/ops/system.c | 53 +++++++++++++++++++++++++---- test/rfl/system/syscmd_coverage.rfl | 10 +++--- test/test_runtime.c | 17 ++++++++- 5 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/app/main.c b/src/app/main.c index f6ff95f3a..332773143 100644 --- a/src/app/main.c +++ b/src/app/main.c @@ -214,7 +214,14 @@ int main(int argc, char** argv) { } /* Expose the full command line to Rayfall via (.sys.args). */ - ray_runtime_set_sys_args(ray_build_sys_args(argc, argv)); + ray_t* sys_args = ray_build_sys_args(argc, argv); + if (!sys_args || RAY_IS_ERR(sys_args)) { + fprintf(stderr, "error: invalid command-line arguments\n"); + if (sys_args && RAY_IS_ERR(sys_args)) ray_error_free(sys_args); + ray_runtime_destroy(rt); + return 2; + } + ray_runtime_set_sys_args(sys_args); /* Initialise the worker pool before anything else that might use it * (file load, REPL eval, builtins). If -c wasn't given, leave the diff --git a/src/lang/syscmd.c b/src/lang/syscmd.c index a02c07c29..9fd7b44c6 100644 --- a/src/lang/syscmd.c +++ b/src/lang/syscmd.c @@ -38,6 +38,8 @@ void* ray_runtime_get_poll(void); #include +#include +#include #include #include #include @@ -79,9 +81,20 @@ static int64_t arg_as_i64(ray_t* arg, int* err) { int sign = 1; if (i < len && (p[i] == '+' || p[i] == '-')) { if (p[i] == '-') sign = -1; i++; } if (i >= len || p[i] < '0' || p[i] > '9') { *err = 1; return 0; } - int64_t v = 0; - while (i < len && p[i] >= '0' && p[i] <= '9') { v = v * 10 + (p[i] - '0'); i++; } - return sign * v; + uint64_t v = 0; + uint64_t limit = sign < 0 ? (uint64_t)INT64_MAX + 1u : (uint64_t)INT64_MAX; + while (i < len && p[i] >= '0' && p[i] <= '9') { + uint64_t digit = (uint64_t)(p[i] - '0'); + if (v > (limit - digit) / 10u) { *err = 1; return 0; } + v = v * 10u + digit; + i++; + } + if (i != len) { *err = 1; return 0; } + if (sign < 0) { + if (v == (uint64_t)INT64_MAX + 1u) return INT64_MIN; + return -(int64_t)v; + } + return (int64_t)v; } *err = 1; return 0; diff --git a/src/ops/system.c b/src/ops/system.c index dec663f7a..176415645 100644 --- a/src/ops/system.c +++ b/src/ops/system.c @@ -53,10 +53,12 @@ void* ray_runtime_get_poll(void); void ray_runtime_set_sys_args(void* dict); void* ray_runtime_get_sys_args(void); #include +#include #include #include #include #include + #if !defined(RAY_OS_WINDOWS) #include #include /* WIFEXITED/WEXITSTATUS — .sys.exec exit codes */ @@ -67,6 +69,25 @@ void* ray_runtime_get_sys_args(void); #define RAY_PCLOSE(f) _pclose(f) #endif +static int parse_sys_int_arg(const char* s, bool nonnegative, bool core_count, + int64_t* out) { + if (!s || !*s) return 0; + const unsigned char* p = (const unsigned char*)s; + if (!nonnegative && (*p == '+' || *p == '-')) p++; + if (!isdigit(*p)) return 0; + for (const unsigned char* q = p; *q; q++) + if (!isdigit(*q)) return 0; + + char* end = NULL; + errno = 0; + long long v = strtoll(s, &end, 10); + if (errno == ERANGE || end == s || *end != '\0' || + (nonnegative && v < 0) || (core_count && v > INT_MAX)) + return 0; + *out = (int64_t)v; + return 1; +} + /* ══════════════════════════════════════════ * Serialization / storage * ══════════════════════════════════════════ */ @@ -1553,13 +1574,33 @@ ray_t* ray_build_sys_args(int argc, char** argv) { interactive = true; else if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) && i + 1 < argc) port = (int64_t)atoll(argv[++i]); - else if ((strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--cores") == 0) && i + 1 < argc) { - long long v = atoll(argv[++i]); if (v < 0) v = 0; cores = (int64_t)v; + else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--cores") == 0) { + if (i + 1 >= argc) + return ray_error("rank", ".sys.args: %s requires an argument", argv[i]); + int64_t v; + if (!parse_sys_int_arg(argv[++i], true, true, &v)) + return ray_error("domain", ".sys.args: invalid %s value \"%s\"", + argv[i - 1], argv[i]); + cores = v; + } + else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--timeit") == 0) { + if (i + 1 >= argc) + return ray_error("rank", ".sys.args: %s requires an argument", argv[i]); + int64_t v; + if (!parse_sys_int_arg(argv[++i], false, false, &v)) + return ray_error("domain", ".sys.args: invalid %s value \"%s\"", + argv[i - 1], argv[i]); + timeit = (v != 0); + } + else if (strcmp(argv[i], "-Q") == 0 || strcmp(argv[i], "--querylog") == 0) { + if (i + 1 >= argc) + return ray_error("rank", ".sys.args: %s requires an argument", argv[i]); + int64_t v; + if (!parse_sys_int_arg(argv[++i], false, false, &v)) + return ray_error("domain", ".sys.args: invalid %s value \"%s\"", + argv[i - 1], argv[i]); + querylog = (v != 0); } - else if ((strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--timeit") == 0) && i + 1 < argc) - timeit = (atoll(argv[++i]) != 0); - else if ((strcmp(argv[i], "-Q") == 0 || strcmp(argv[i], "--querylog") == 0) && i + 1 < argc) - querylog = (atoll(argv[++i]) != 0); else if ((strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--file") == 0) && i + 1 < argc) file = argv[++i]; else if (strcmp(argv[i], "-u") == 0 && i + 1 < argc) i++; /* skip secret */ diff --git a/test/rfl/system/syscmd_coverage.rfl b/test/rfl/system/syscmd_coverage.rfl index 8a0a18fe8..a69bf0af6 100644 --- a/test/rfl/system/syscmd_coverage.rfl +++ b/test/rfl/system/syscmd_coverage.rfl @@ -55,14 +55,14 @@ (.sys.cmd "timeit 100") -- 1 (.sys.cmd "timeit 0") -- 0 -;; Empty-after-sign / non-digit start → type error (line 80). +;; Empty-after-sign / non-digit start / trailing garbage → type error. (.sys.cmd "timeit +") !- type (.sys.cmd "timeit -") !- type (.sys.cmd "timeit abc") !- type -(.sys.cmd "timeit 1.5") -- 1 -;; ^ "1.5" parses up to the dot → 1, profile turns on; the trailing -;; ".5" is not an error (the parser stops at first non-digit and -;; returns what it has). Reset state for any later toggle tests. +(.sys.cmd "timeit 1.5") !- type +(.sys.timeit "1abc") !- type +(.sys.timeit "9223372036854775808") !- type +;; Reset state for any later toggle tests. (.sys.timeit 0) -- 0 ;; ────────────── arg_as_i64: type-fallthrough (lines 85-86) ────────────── diff --git a/test/test_runtime.c b/test/test_runtime.c index 8e6094fad..35160723b 100644 --- a/test/test_runtime.c +++ b/test/test_runtime.c @@ -635,6 +635,21 @@ static test_result_t test_build_sys_args_edges(void) { PASS(); } +static test_result_t test_build_sys_args_rejects_malformed_numbers(void) { + char* cases[][3] = { + { "rayforce", "-c", "2abc" }, + { "rayforce", "-c", "999999999999999999999999" }, + { "rayforce", "-t", "1abc" }, + { "rayforce", "-Q", "0abc" }, + }; + for (size_t i = 0; i < sizeof cases / sizeof cases[0]; i++) { + ray_t* d = ray_build_sys_args(3, cases[i]); + TEST_ASSERT_TRUE(RAY_IS_ERR(d)); + ray_error_free(d); + } + PASS(); +} + /* .sys.args builtin: only `source` when unset; reflects stored dict when set */ static test_result_t test_sys_args_builtin(void) { /* unset → a dict holding just `source` (empty here: ray_eval_str is @@ -1288,6 +1303,7 @@ const test_entry_t runtime_entries[] = { { "runtime/build_sys_args_defaults", test_build_sys_args_defaults, sys_setup, sys_teardown }, { "runtime/build_sys_args_flags_user", test_build_sys_args_flags_and_user, sys_setup, sys_teardown }, { "runtime/build_sys_args_edges", test_build_sys_args_edges, sys_setup, sys_teardown }, + { "runtime/build_sys_args_rejects_malformed_numbers", test_build_sys_args_rejects_malformed_numbers, sys_setup, sys_teardown }, { "runtime/sys_args_builtin", test_sys_args_builtin, sys_setup, sys_teardown }, { "runtime/syscov_rc", test_syscov_rc, sys_setup, sys_teardown }, { "runtime/syscov_time_now", test_syscov_time_now, sys_setup, sys_teardown }, @@ -1315,4 +1331,3 @@ const test_entry_t runtime_entries[] = { { NULL, NULL, NULL, NULL }, }; -