Prevent stack overflow in json-related functions.
authorNoah Misch <noah@leadboat.com>
Mon, 5 Oct 2015 14:06:29 +0000 (10:06 -0400)
committerNoah Misch <noah@leadboat.com>
Mon, 5 Oct 2015 14:06:29 +0000 (10:06 -0400)
Sufficiently-deep recursion heretofore elicited a SIGSEGV.  If an
application constructs PostgreSQL json or jsonb values from arbitrary
user input, application users could have exploited this to terminate all
active database connections.  That applies to 9.3, where the json parser
adopted recursive descent, and later versions.  Only row_to_json() and
array_to_json() were at risk in 9.2, both in a non-security capacity.
Back-patch to 9.2, where the json type was introduced.

Oskari Saarenmaa, reviewed by Michael Paquier.

Security: CVE-2015-5289

src/backend/utils/adt/json.c
src/backend/utils/adt/jsonb.c
src/backend/utils/adt/jsonfuncs.c
src/test/regress/expected/json.out
src/test/regress/expected/json_1.out
src/test/regress/expected/jsonb.out
src/test/regress/expected/jsonb_1.out
src/test/regress/sql/json.sql
src/test/regress/sql/jsonb.sql

index eefe93bc8ab926fe6d69c1170e80f2c8f08d0190..f394942bc359b73068c2bf3a2a514758ecf5c957 100644 (file)
@@ -490,6 +490,8 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem)
    json_struct_action oend = sem->object_end;
    JsonTokenType tok;
 
+   check_stack_depth();
+
    if (ostart != NULL)
        (*ostart) (sem->semstate);
 
@@ -568,6 +570,8 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem)
    json_struct_action astart = sem->array_start;
    json_struct_action aend = sem->array_end;
 
+   check_stack_depth();
+
    if (astart != NULL)
        (*astart) (sem->semstate);
 
@@ -1433,6 +1437,8 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
    char       *outputstr;
    text       *jsontext;
 
+   check_stack_depth();
+
    /* callers are expected to ensure that null keys are not passed in */
    Assert(!(key_scalar && is_null));
 
index f0f1651e9da557ebd99e30618ce68daedd1be575..8fef35ea38a53c94082b8e7ca4382707ccc758a9 100644 (file)
@@ -712,6 +712,8 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
    JsonbValue  jb;
    bool        scalar_jsonb = false;
 
+   check_stack_depth();
+
    if (is_null)
    {
        Assert(!key_scalar);
index 01b6bb0a483a7762d940aba1b67ff8c4b89a516b..f33864e619dca5f605aedbdb3e24d17bbb23b718 100644 (file)
@@ -3724,6 +3724,8 @@ setPath(JsonbIterator **it, Datum *path_elems,
    JsonbValue *res = NULL;
    int         r;
 
+   check_stack_depth();
+
    if (path_nulls[level])
        elog(ERROR, "path element at the position %d is NULL", level + 1);
 
index eb6b26b241e8f9511e2e5ae0cbf803d8c979c732..cb9a0a1eb3082f3b6114e6b77d27d7ef8121e314 100644 (file)
@@ -231,6 +231,15 @@ LINE 1: SELECT '{"abc":1,3}'::json;
                ^
 DETAIL:  Expected string, but found "3".
 CONTEXT:  JSON data, line 1: {"abc":1,3...
+-- Recursion.
+SET max_stack_depth = '100kB';
+SELECT repeat('[', 1000)::json;
+ERROR:  stack depth limit exceeded
+HINT:  Increase the configuration parameter "max_stack_depth" (currently 100kB), after ensuring the platform's stack depth limit is adequate.
+SELECT repeat('{"a":', 1000)::json;
+ERROR:  stack depth limit exceeded
+HINT:  Increase the configuration parameter "max_stack_depth" (currently 100kB), after ensuring the platform's stack depth limit is adequate.
+RESET max_stack_depth;
 -- Miscellaneous stuff.
 SELECT 'true'::json;           -- OK
  json 
index 48543e8c356231f0c7f7a0b3adb58d549fc0b636..fe63a72b39c81ff6895bbcc1ed67110f9336a46f 100644 (file)
@@ -231,6 +231,15 @@ LINE 1: SELECT '{"abc":1,3}'::json;
                ^
 DETAIL:  Expected string, but found "3".
 CONTEXT:  JSON data, line 1: {"abc":1,3...
+-- Recursion.
+SET max_stack_depth = '100kB';
+SELECT repeat('[', 1000)::json;
+ERROR:  stack depth limit exceeded
+HINT:  Increase the configuration parameter "max_stack_depth" (currently 100kB), after ensuring the platform's stack depth limit is adequate.
+SELECT repeat('{"a":', 1000)::json;
+ERROR:  stack depth limit exceeded
+HINT:  Increase the configuration parameter "max_stack_depth" (currently 100kB), after ensuring the platform's stack depth limit is adequate.
+RESET max_stack_depth;
 -- Miscellaneous stuff.
 SELECT 'true'::json;           -- OK
  json 
index 6da5a151d726fd6231d6e460ac9b43a6fe546c60..148364d74bf2ba20e10733c01cf549fe75f38cbd 100644 (file)
@@ -239,6 +239,15 @@ LINE 1: SELECT '{"abc":1,3}'::jsonb;
                ^
 DETAIL:  Expected string, but found "3".
 CONTEXT:  JSON data, line 1: {"abc":1,3...
+-- Recursion.
+SET max_stack_depth = '100kB';
+SELECT repeat('[', 1000)::jsonb;
+ERROR:  stack depth limit exceeded
+HINT:  Increase the configuration parameter "max_stack_depth" (currently 100kB), after ensuring the platform's stack depth limit is adequate.
+SELECT repeat('{"a":', 1000)::jsonb;
+ERROR:  stack depth limit exceeded
+HINT:  Increase the configuration parameter "max_stack_depth" (currently 100kB), after ensuring the platform's stack depth limit is adequate.
+RESET max_stack_depth;
 -- Miscellaneous stuff.
 SELECT 'true'::jsonb;          -- OK
  jsonb 
index 39e7b1ff5707b933279915f64f35e905335d46c8..903dbd4c402e4b20afd860c510d46d3ddfba6b98 100644 (file)
@@ -239,6 +239,15 @@ LINE 1: SELECT '{"abc":1,3}'::jsonb;
                ^
 DETAIL:  Expected string, but found "3".
 CONTEXT:  JSON data, line 1: {"abc":1,3...
+-- Recursion.
+SET max_stack_depth = '100kB';
+SELECT repeat('[', 1000)::jsonb;
+ERROR:  stack depth limit exceeded
+HINT:  Increase the configuration parameter "max_stack_depth" (currently 100kB), after ensuring the platform's stack depth limit is adequate.
+SELECT repeat('{"a":', 1000)::jsonb;
+ERROR:  stack depth limit exceeded
+HINT:  Increase the configuration parameter "max_stack_depth" (currently 100kB), after ensuring the platform's stack depth limit is adequate.
+RESET max_stack_depth;
 -- Miscellaneous stuff.
 SELECT 'true'::jsonb;          -- OK
  jsonb 
index f631480f9676a3e72e1a588446d5f8f60e1de47a..a173f06d958e1e9556e5c0997f589ff7a589343c 100644 (file)
@@ -45,6 +45,12 @@ SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::json; -- OK
 SELECT '{"abc":1:2}'::json;        -- ERROR, colon in wrong spot
 SELECT '{"abc":1,3}'::json;        -- ERROR, no value
 
+-- Recursion.
+SET max_stack_depth = '100kB';
+SELECT repeat('[', 1000)::json;
+SELECT repeat('{"a":', 1000)::json;
+RESET max_stack_depth;
+
 -- Miscellaneous stuff.
 SELECT 'true'::json;           -- OK
 SELECT 'false'::json;          -- OK
index b1a0764cfaaca893c6ecf0b69398a72669a88b2b..f86b7fbf9bf1d6e4da42e9a0efbbe59670a4e442 100644 (file)
@@ -48,6 +48,12 @@ SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::jsonb; -- OK
 SELECT '{"abc":1:2}'::jsonb;       -- ERROR, colon in wrong spot
 SELECT '{"abc":1,3}'::jsonb;       -- ERROR, no value
 
+-- Recursion.
+SET max_stack_depth = '100kB';
+SELECT repeat('[', 1000)::jsonb;
+SELECT repeat('{"a":', 1000)::jsonb;
+RESET max_stack_depth;
+
 -- Miscellaneous stuff.
 SELECT 'true'::jsonb;          -- OK
 SELECT 'false'::jsonb;         -- OK