]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
func_json: Fix crashes for some types
authorBastian Triller <bastian.triller@gmail.com>
Thu, 21 Sep 2023 06:24:37 +0000 (08:24 +0200)
committerAsterisk Development Team <asteriskteam@digium.com>
Fri, 12 Jan 2024 18:32:12 +0000 (18:32 +0000)
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 1cbbf36929b47bb6b9c125f630d6bcd03618fcde)

funcs/func_json.c
include/asterisk/json.h
main/json.c

index 8bfcd4cfb170093585fde0aaf47f856ee6e43531..bf299dffd98cdca18ec2f44f27debbb0c2127119 100644 (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", ""},
index 5b2d61422d40d4de9698d358816d1e1bce830062..a7e458babc15b57f0222ba470901573f884b67bc 100644 (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
index afb653a229a3217d618a2a0eb9971b8c18ae0dd2..20e2975dce267b5f61a7d79acddd7508e353ac90 100644 (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);