From 1ab067b680cdf25095c025dc3fe66e21afd84ac3 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Tue, 23 Dec 2025 17:27:11 +0100 Subject: [PATCH] rainerscript: ensure parse_json consumes entire input string This fix ensures that parse_json() only succeeds if the entire input string is a valid JSON value. This prevents false positives when a non-JSON string happens to start with a valid JSON value, like a number. Documentation is updated to reflect this stricter validation. Impact: Corrects false-success in parse_json() for malformed input. Modified doFunc_parse_json in grammar/rainerscript.c to check if the json-c tokener consumed the entire provided string. After parsing, the remainder of the string is scanned for any non-whitespace characters. If trailing garbage is found, the function now returns RS_SCRIPT_EINVAL instead of RS_SCRIPT_EOK. Updated rs-parse_json.rst to document the requirement for a complete JSON object/value. Added a regression test and updated the testbench Makefile.am to include the new validation scenario. Fixes: https://github.com/rsyslog/rsyslog/issues/4970 AI-Agent: Antigravity --- .../rainerscript/functions/rs-parse_json.rst | 5 +++- grammar/rainerscript.c | 17 ++++++++++--- plugins/mmanon/mmanon.c | 3 ++- tests/Makefile.am | 2 ++ tests/rscript_parse_json_issue.sh | 25 +++++++++++++++++++ 5 files changed, 47 insertions(+), 5 deletions(-) create mode 100755 tests/rscript_parse_json_issue.sh diff --git a/doc/source/rainerscript/functions/rs-parse_json.rst b/doc/source/rainerscript/functions/rs-parse_json.rst index 269b587dd..ef1c3fafc 100644 --- a/doc/source/rainerscript/functions/rs-parse_json.rst +++ b/doc/source/rainerscript/functions/rs-parse_json.rst @@ -9,8 +9,11 @@ parse_json(str, container) Parses the json string ``str`` and places the resulting json object into ``container`` where container can be any valid rsyslog variable. +Note that the **entire** ``str`` must be valid JSON for the function +to succeed. If there is trailing data after a valid JSON object/value, +it will be considered as an error. Returns 0 on success and something otherwise if ``str`` does **not** -contain valid json. +contain a valid, complete json string. Example diff --git a/grammar/rainerscript.c b/grammar/rainerscript.c index 6c0896f3a..519826404 100644 --- a/grammar/rainerscript.c +++ b/grammar/rainerscript.c @@ -1823,9 +1823,20 @@ static void ATTR_NONNULL() doFunc_parse_json(struct cnffunc *__restrict__ const if (json == NULL) { retVal = RS_SCRIPT_EINVAL; } else { - size_t off = (*container == '$') ? 1 : 0; - msgAddJSON(pMsg, (uchar *)container + off, json, 0, 0); - retVal = RS_SCRIPT_EOK; + /* Check for trailing garbage */ + int i = tokener->char_offset; + while (jsontext[i] != '\0' && isspace((uchar)jsontext[i])) { + i++; + } + if (jsontext[i] != '\0') { + json_object_put(json); + json = NULL; + retVal = RS_SCRIPT_EINVAL; + } else { + size_t off = (*container == '$') ? 1 : 0; + msgAddJSON(pMsg, (uchar *)container + off, json, 0, 0); + retVal = RS_SCRIPT_EOK; + } } wtiSetScriptErrno(pWti, retVal); json_tokener_free(tokener); diff --git a/plugins/mmanon/mmanon.c b/plugins/mmanon/mmanon.c index 0d32763be..0d6d1e8b4 100644 --- a/plugins/mmanon/mmanon.c +++ b/plugins/mmanon/mmanon.c @@ -1321,7 +1321,8 @@ static rsRetVal findIPv6(struct ipv6_int *num, char *address, wrkrInstanceData_t struct hashtable *randConsisUniqueGeneratedIPs = useEmbedded ? pWrkrData->pData->embeddedIPv4.randConsisUniqueGeneratedIPs : pWrkrData->pData->ipv6.randConsisUniqueGeneratedIPs; - const int uniqueMode = useEmbedded ? pWrkrData->pData->embeddedIPv4.randConsisUnique : pWrkrData->pData->ipv6.randConsisUnique; + const int uniqueMode = + useEmbedded ? pWrkrData->pData->embeddedIPv4.randConsisUnique : pWrkrData->pData->ipv6.randConsisUnique; struct ipv6_int original = *num; struct ipv6_int *uniqueKey = NULL; sbool locked = 0; diff --git a/tests/Makefile.am b/tests/Makefile.am index 7a2cf4d88..135a5a56c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -449,6 +449,7 @@ TESTS += \ rscript_is_time.sh \ rscript_script_error.sh \ rscript_parse_json.sh \ + rscript_parse_json_issue.sh \ rscript_previous_action_suspended.sh \ rscript_str2num_negative.sh \ rscript_exists-yes.sh \ @@ -2286,6 +2287,7 @@ EXTRA_DIST= \ rscript_is_time.sh \ rscript_script_error.sh \ rscript_parse_json.sh \ + rscript_parse_json_issue.sh \ rscript_parse_json-vg.sh \ rscript_backticks-vg.sh \ rscript_backticks_empty_envvar.sh \ diff --git a/tests/rscript_parse_json_issue.sh b/tests/rscript_parse_json_issue.sh new file mode 100755 index 000000000..437643273 --- /dev/null +++ b/tests/rscript_parse_json_issue.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Reproduction for parse_json() issue +. ${srcdir:=.}/diag.sh init +generate_conf +add_conf ' +module(load="../plugins/imtcp/.libs/imtcp") +input(type="imtcp" port="0" listenPortFileName="'$RSYSLOG_DYNNAME'.tcpflood_port") +template(name="outfmt" type="string" string="ret: %$.ret%, parsed: %$!parsed%\n") + +local4.* { + set $.ret = parse_json("22 08 23 this is a test message", "\$!parsed"); + action(type="omfile" file=`echo $RSYSLOG_OUT_LOG` template="outfmt") +} +' +startup +tcpflood -m1 +shutdown_when_empty +wait_shutdown + +export EXPECTED='ret: 1, parsed: ' +# Since the bug is that it returns 0 and parses "22", we expect ret: 0 and parsed: 22 if it fails. +# We want it to be 1 and empty. +cmp_exact $RSYSLOG_OUT_LOG + +exit_test