func_json: Fix crashes for some types

This commit fixes crashes in JSON_DECODE() for types null, true, false
and real numbers.

In addition it ensures that a path is not deeper than 32 levels.

Also allow root object to be an array.

Add unit tests for above cases.

(cherry picked from commit 1cbbf36929)
This commit is contained in:
Bastian Triller 2023-09-21 08:24:37 +02:00 committed by Asterisk Development Team
parent 779fb2052a
commit 903c594cef
3 changed files with 77 additions and 22 deletions

View File

@ -101,6 +101,7 @@ static int parse_node(char **key, char *currentkey, char *nestchar, int count, s
struct ast_json *jsonval = json;
/* Prevent a huge JSON string from blowing the stack. */
(*depth)++;
if (*depth > MAX_JSON_STACK) {
ast_log(LOG_WARNING, "Max JSON stack (%d) exceeded\n", MAX_JSON_STACK);
return -1;
@ -115,6 +116,7 @@ static int parse_node(char **key, char *currentkey, char *nestchar, int count, s
switch(ast_json_typeof(jsonval)) {
unsigned long int size;
int r;
double d;
case AST_JSON_STRING:
result = ast_json_string_get(jsonval);
@ -126,6 +128,11 @@ static int parse_node(char **key, char *currentkey, char *nestchar, int count, s
ast_debug(1, "Got JSON integer: %d\n", r);
snprintf(buf, len, "%d", r); /* the snprintf below is mutually exclusive with this one */
break;
case AST_JSON_REAL:
d = ast_json_real_get(jsonval);
ast_debug(1, "Got JSON real: %.17g\n", d);
snprintf(buf, len, "%.17g", d); /* the snprintf below is mutually exclusive with this one */
break;
case AST_JSON_ARRAY:
ast_debug(1, "Got JSON array\n");
previouskey = currentkey;
@ -148,41 +155,39 @@ static int parse_node(char **key, char *currentkey, char *nestchar, int count, s
} else if (r >= size) {
ast_debug(1, "Requested index '%d' does not exist in parsed array\n", r);
} else {
struct ast_json *json2 = ast_json_array_get(jsonval, r);
if (!json2) {
ast_debug(1, "Array index %d contains empty item\n", r);
return -1;
}
previouskey = currentkey;
currentkey = strsep(key, nestchar); /* get the next subkey */
ast_debug(1, "Recursing on index %d in array (key was '%s' and is now '%s')\n", r, previouskey, currentkey);
/* json2 is a borrowed ref. That's fine, since json won't get freed until recursing is over */
/* If there are keys remaining, then parse the next object we can get. Otherwise, just dump the child */
if (parse_node(key, currentkey, nestchar, count, currentkey ? ast_json_object_get(json2, currentkey) : json2, buf, len, depth)) { /* recurse on this node */
ast_debug(1, "Recursing on index %d in array\n", r);
if (parse_node(key, currentkey, nestchar, count, ast_json_array_get(jsonval, r), buf, len, depth)) { /* recurse on this node */
return -1;
}
}
break;
case AST_JSON_TRUE:
case AST_JSON_FALSE:
r = ast_json_is_true(jsonval);
ast_debug(1, "Got JSON %s for key %s\n", r ? "true" : "false", currentkey);
snprintf(buf, len, "%d", r); /* the snprintf below is mutually exclusive with this one */
break;
case AST_JSON_NULL:
ast_debug(1, "Got JSON null for key %s\n", currentkey);
break;
case AST_JSON_OBJECT:
default:
ast_debug(1, "Got generic JSON object for key %s\n", currentkey);
previouskey = currentkey;
currentkey = strsep(key, nestchar); /* retrieve the desired index */
if (!currentkey) { /* this is the end, so just dump the object */
char *result2 = ast_json_dump_string(jsonval);
ast_copy_string(buf, result2, len);
ast_json_free(result2);
} else {
previouskey = currentkey;
currentkey = strsep(key, nestchar); /* retrieve the desired index */
ast_debug(1, "Recursing on object (key was '%s' and is now '%s')\n", previouskey, currentkey);
if (!currentkey) { /* this is the end, so just dump the object */
char *result2 = ast_json_dump_string(jsonval);
ast_copy_string(buf, result2, len);
ast_json_free(result2);
} else if (parse_node(key, currentkey, nestchar, count, ast_json_object_get(jsonval, currentkey), buf, len, depth)) { /* recurse on this node */
if (parse_node(key, currentkey, nestchar, count, ast_json_object_get(jsonval, currentkey), buf, len, depth)) { /* recurse on this node */
return -1;
}
}
break;
default:
ast_log(LOG_WARNING, "Got unsuported type %d\n", ast_json_typeof(jsonval));
return -1;
}
return 0;
}
@ -191,9 +196,9 @@ static int json_decode_read(struct ast_channel *chan, const char *cmd, char *dat
{
int count = 0;
struct ast_flags flags = {0};
struct ast_json *json = NULL;
struct ast_json *json = NULL, *start = NULL;
char *nestchar = "."; /* default delimeter for nesting key indexing is . */
int res, depth = 0;
int index, res, depth = 0;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(varname);
@ -217,10 +222,12 @@ static int json_decode_read(struct ast_channel *chan, const char *cmd, char *dat
ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
return -1;
}
if (ast_strlen_zero(args.key)) {
ast_log(LOG_WARNING, "%s requires a key\n", cmd);
return -1;
}
key = ast_strdupa(args.key);
if (!ast_strlen_zero(args.nestchar)) {
int seplen = strlen(args.nestchar);
@ -264,6 +271,7 @@ static int json_decode_read(struct ast_channel *chan, const char *cmd, char *dat
ast_debug(1, "JSON node '%s', contains no data, nothing to search!\n", currentkey);
return -1; /* empty json string */
}
json = ast_json_load_str(str, NULL);
if (!json) {
ast_log(LOG_WARNING, "Failed to parse as JSON: %s\n", ast_str_buffer(str));
@ -272,7 +280,17 @@ static int json_decode_read(struct ast_channel *chan, const char *cmd, char *dat
/* parse the JSON object, potentially recursively */
nextkey = strsep(&key, nestchar);
res = parse_node(&key, nextkey, nestchar, count, ast_json_object_get(json, firstkey), buf, len, &depth);
if (ast_json_is_object(json)) {
start = ast_json_object_get(json, firstkey);
} else {
if (ast_str_to_int(currentkey, &index)) {
ast_debug(1, "Requested index '%s' is not numeric or is invalid\n", currentkey);
return -1;
}
start = ast_json_array_get(json, index);
}
res = parse_node(&key, nextkey, nestchar, count, start, buf, len, &depth);
ast_json_unref(json);
return res;
}
@ -290,6 +308,17 @@ AST_TEST_DEFINE(test_JSON_DECODE)
struct ast_str *str; /* fancy string for holding comparing value */
const char *test_strings[][6] = {
{"{\"myboolean\": true, \"state\": \"USA\"}", "", "myboolean", "1"},
{"{\"myboolean\": false, \"state\": \"USA\"}", "", "myboolean", "0"},
{"{\"myreal\": 1E+2, \"state\": \"USA\"}", "", "myreal", "100"},
{"{\"myreal\": 1.23, \"state\": \"USA\"}", "", "myreal", "1.23"},
{"{\"myarray\": [[1]], \"state\": \"USA\"}", "", "myarray.0.0", "1"},
{"{\"myarray\": [null], \"state\": \"USA\"}", "", "myarray.0", ""},
{"{\"myarray\": [0, 1], \"state\": \"USA\"}", "", "myarray", "[0,1]"},
{"[0, 1]", "", "", ""},
{"[0, 1]", "", "0", "0"},
{"[0, 1]", "", "foo", ""},
{"{\"mynull\": null, \"state\": \"USA\"}", "", "mynull", ""},
{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "city", "Anytown"},
{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "state", "USA"},
{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "blah", ""},

View File

@ -268,6 +268,22 @@ struct ast_json *ast_json_boolean(int value);
*/
struct ast_json *ast_json_null(void);
/*!
* \brief Check if \a value is JSON array.
* \since 12.0.0
* \retval True (non-zero) if \a value == \ref ast_json_array().
* \retval False (zero) otherwise..
*/
int ast_json_is_array(const struct ast_json *value);
/*!
* \brief Check if \a value is JSON object.
* \since 12.0.0
* \retval True (non-zero) if \a value == \ref ast_json_object().
* \retval False (zero) otherwise..
*/
int ast_json_is_object(const struct ast_json *value);
/*!
* \brief Check if \a value is JSON true.
* \since 12.0.0

View File

@ -250,6 +250,16 @@ struct ast_json *ast_json_null(void)
return (struct ast_json *)json_null();
}
int ast_json_is_array(const struct ast_json *json)
{
return json_is_array((const json_t *)json);
}
int ast_json_is_object(const struct ast_json *json)
{
return json_is_object((const json_t *)json);
}
int ast_json_is_true(const struct ast_json *json)
{
return json_is_true((const json_t *)json);