From 8536ce8c38cd680d1a2e57d605ff6a39f7eededb Mon Sep 17 00:00:00 2001 From: Bastian Triller Date: Thu, 21 Sep 2023 08:24:37 +0200 Subject: [PATCH] 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. --- funcs/func_json.c | 73 ++++++++++++++++++++++++++++------------- include/asterisk/json.h | 16 +++++++++ main/json.c | 10 ++++++ 3 files changed, 77 insertions(+), 22 deletions(-) diff --git a/funcs/func_json.c b/funcs/func_json.c index 8bfcd4cfb1..bf299dffd9 100644 --- a/funcs/func_json.c +++ b/funcs/func_json.c @@ -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", ""}, diff --git a/include/asterisk/json.h b/include/asterisk/json.h index 5b2d61422d..a7e458babc 100644 --- a/include/asterisk/json.h +++ b/include/asterisk/json.h @@ -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 diff --git a/main/json.c b/main/json.c index afb653a229..20e2975dce 100644 --- a/main/json.c +++ b/main/json.c @@ -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);