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
This commit is contained in:
Rainer Gerhards 2025-12-23 17:27:11 +01:00
parent 8e1fa59ae1
commit 1ab067b680
No known key found for this signature in database
GPG Key ID: 0CB6B2A8BE80B499
5 changed files with 47 additions and 5 deletions

View File

@ -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

View File

@ -1822,11 +1822,22 @@ static void ATTR_NONNULL() doFunc_parse_json(struct cnffunc *__restrict__ const
json = json_tokener_parse_ex(tokener, jsontext, strlen(jsontext));
if (json == NULL) {
retVal = RS_SCRIPT_EINVAL;
} else {
/* 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);

View File

@ -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;

View File

@ -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 \

View File

@ -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