From b565e9d8b06e8703f1c15a4263a987837dd0c5f3 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Sun, 20 Sep 2026 17:50:53 +0200 Subject: [PATCH] fix(mcp): an integer argument outside int range takes the default Distilled from #1245 by Andrew Hundt (d030c8f4). cbm_mcp_get_int_arg read integer arguments with yyjson_is_int and yyjson_get_int, and the int cast inside get_int truncates. A client sending {"limit": 4294967297} -- 2^32 + 1 -- got back 1: the low word of the value, silently, as a bound. The same read serves limit and offset on the query tools, so an oversized page request became a page of one. The argument is now read as a 64-bit signed or unsigned integer and only accepted if it fits in int; anything outside the range takes the caller's default, exactly as a non-integer does. In-range values and non-integer JSON behave as before. One difference from the upstream hunk: the unsigned comparison casts INT_MAX explicitly rather than relying on the implicit conversion. RED before the fix: mcp_get_int_arg FAIL tests/test_mcp.c:1777: val == 1, expected 17 == 17 The test now covers 2^32 + 1, its negative, INT_MAX + 1 and INT64_MIN each returning a distinct default, and INT_MAX, INT_MIN and -7 passing through unchanged. GREEN after: mcp 318 passed, 4 skipped (the Windows-only skips); mcp cli daemon_application 692 passed, 0 failed. Co-authored-by: Andrew Hundt Signed-off-by: Martin Vogel --- src/mcp/mcp.c | 15 +++++++++++++-- tests/test_mcp.c | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/mcp/mcp.c b/src/mcp/mcp.c index 39aaf0dcc..c0bab6a55 100644 --- a/src/mcp/mcp.c +++ b/src/mcp/mcp.c @@ -1583,8 +1583,19 @@ int cbm_mcp_get_int_arg(const char *args_json, const char *key, int default_val) yyjson_val *root = yyjson_doc_get_root(doc); yyjson_val *val = yyjson_obj_get(root, key); int result = default_val; - if (val && yyjson_is_int(val)) { - result = yyjson_get_int(val); + /* yyjson_get_int truncates 64-bit integers to int (2^32 + 1 became 1); + * read the full width and treat anything outside int range like a + * non-integer, i.e. fall back to the caller's default. */ + if (val && yyjson_is_sint(val)) { + int64_t parsed = yyjson_get_sint(val); + if (parsed >= INT_MIN && parsed <= INT_MAX) { + result = (int)parsed; + } + } else if (val && yyjson_is_uint(val)) { + uint64_t parsed = yyjson_get_uint(val); + if (parsed <= (uint64_t)INT_MAX) { + result = (int)parsed; + } } yyjson_doc_free(doc); return result; diff --git a/tests/test_mcp.c b/tests/test_mcp.c index 7d51c9c6d..9d6aa66ec 100644 --- a/tests/test_mcp.c +++ b/tests/test_mcp.c @@ -1771,6 +1771,23 @@ TEST(mcp_get_int_arg) { ASSERT_EQ(val, 5); val = cbm_mcp_get_int_arg(args, "missing", 42); ASSERT_EQ(val, 42); + /* Out-of-int-range integers return the default instead of truncating: + * 2^32 + 1 used to read back as 1 through yyjson_get_int's int cast. */ + val = cbm_mcp_get_int_arg("{\"limit\":4294967297}", "limit", 17); + ASSERT_EQ(val, 17); + val = cbm_mcp_get_int_arg("{\"limit\":-4294967297}", "limit", 19); + ASSERT_EQ(val, 19); + val = cbm_mcp_get_int_arg("{\"limit\":2147483648}", "limit", 23); + ASSERT_EQ(val, 23); + val = cbm_mcp_get_int_arg("{\"limit\":-9223372036854775808}", "limit", 29); + ASSERT_EQ(val, 29); + /* Boundary values and a negative in-range value still pass through. */ + val = cbm_mcp_get_int_arg("{\"limit\":2147483647}", "limit", 0); + ASSERT_EQ(val, 2147483647); + val = cbm_mcp_get_int_arg("{\"limit\":-2147483648}", "limit", 0); + ASSERT_EQ(val, -2147483648); + val = cbm_mcp_get_int_arg("{\"limit\":-7}", "limit", 0); + ASSERT_EQ(val, -7); PASS(); }