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; struct ast_json *jsonval = json;
/* Prevent a huge JSON string from blowing the stack. */ /* Prevent a huge JSON string from blowing the stack. */
(*depth)++;
if (*depth > MAX_JSON_STACK) { if (*depth > MAX_JSON_STACK) {
ast_log(LOG_WARNING, "Max JSON stack (%d) exceeded\n", MAX_JSON_STACK); ast_log(LOG_WARNING, "Max JSON stack (%d) exceeded\n", MAX_JSON_STACK);
return -1; return -1;
@@ -115,6 +116,7 @@ static int parse_node(char **key, char *currentkey, char *nestchar, int count, s
switch(ast_json_typeof(jsonval)) { switch(ast_json_typeof(jsonval)) {
unsigned long int size; unsigned long int size;
int r; int r;
double d;
case AST_JSON_STRING: case AST_JSON_STRING:
result = ast_json_string_get(jsonval); 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); ast_debug(1, "Got JSON integer: %d\n", r);
snprintf(buf, len, "%d", r); /* the snprintf below is mutually exclusive with this one */ snprintf(buf, len, "%d", r); /* the snprintf below is mutually exclusive with this one */
break; 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: case AST_JSON_ARRAY:
ast_debug(1, "Got JSON array\n"); ast_debug(1, "Got JSON array\n");
previouskey = currentkey; previouskey = currentkey;
@@ -148,41 +155,39 @@ static int parse_node(char **key, char *currentkey, char *nestchar, int count, s
} else if (r >= size) { } else if (r >= size) {
ast_debug(1, "Requested index '%d' does not exist in parsed array\n", r); ast_debug(1, "Requested index '%d' does not exist in parsed array\n", r);
} else { } else {
struct ast_json *json2 = ast_json_array_get(jsonval, r); ast_debug(1, "Recursing on index %d in array\n", r);
if (!json2) { if (parse_node(key, currentkey, nestchar, count, ast_json_array_get(jsonval, r), buf, len, depth)) { /* recurse on this node */
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 */
return -1; return -1;
} }
} }
break; 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: case AST_JSON_OBJECT:
default:
ast_debug(1, "Got generic JSON object for key %s\n", currentkey); 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 */ if (!currentkey) { /* this is the end, so just dump the object */
char *result2 = ast_json_dump_string(jsonval); char *result2 = ast_json_dump_string(jsonval);
ast_copy_string(buf, result2, len); ast_copy_string(buf, result2, len);
ast_json_free(result2); ast_json_free(result2);
} else { } 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); 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 */ if (parse_node(key, currentkey, nestchar, count, ast_json_object_get(jsonval, currentkey), buf, len, depth)) { /* recurse on this node */
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 */
return -1; return -1;
} }
} }
break; break;
default:
ast_log(LOG_WARNING, "Got unsuported type %d\n", ast_json_typeof(jsonval));
return -1;
} }
return 0; return 0;
} }
@@ -191,9 +196,9 @@ static int json_decode_read(struct ast_channel *chan, const char *cmd, char *dat
{ {
int count = 0; int count = 0;
struct ast_flags flags = {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 . */ char *nestchar = "."; /* default delimeter for nesting key indexing is . */
int res, depth = 0; int index, res, depth = 0;
AST_DECLARE_APP_ARGS(args, AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(varname); 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); ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
return -1; return -1;
} }
if (ast_strlen_zero(args.key)) { if (ast_strlen_zero(args.key)) {
ast_log(LOG_WARNING, "%s requires a key\n", cmd); ast_log(LOG_WARNING, "%s requires a key\n", cmd);
return -1; return -1;
} }
key = ast_strdupa(args.key); key = ast_strdupa(args.key);
if (!ast_strlen_zero(args.nestchar)) { if (!ast_strlen_zero(args.nestchar)) {
int seplen = strlen(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); ast_debug(1, "JSON node '%s', contains no data, nothing to search!\n", currentkey);
return -1; /* empty json string */ return -1; /* empty json string */
} }
json = ast_json_load_str(str, NULL); json = ast_json_load_str(str, NULL);
if (!json) { if (!json) {
ast_log(LOG_WARNING, "Failed to parse as JSON: %s\n", ast_str_buffer(str)); 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 */ /* parse the JSON object, potentially recursively */
nextkey = strsep(&key, nestchar); 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); ast_json_unref(json);
return res; return res;
} }
@@ -290,6 +308,17 @@ AST_TEST_DEFINE(test_JSON_DECODE)
struct ast_str *str; /* fancy string for holding comparing value */ struct ast_str *str; /* fancy string for holding comparing value */
const char *test_strings[][6] = { 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\"}", "", "city", "Anytown"},
{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "state", "USA"}, {"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "state", "USA"},
{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "blah", ""}, {"{\"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); 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. * \brief Check if \a value is JSON true.
* \since 12.0.0 * \since 12.0.0

View File

@@ -250,6 +250,16 @@ struct ast_json *ast_json_null(void)
return (struct ast_json *)json_null(); 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) int ast_json_is_true(const struct ast_json *json)
{ {
return json_is_true((const json_t *)json); return json_is_true((const json_t *)json);