Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-40668

JSON functions read freed memory when another argument reassigns the document argument's SP variable

    XMLWordPrintable

Details

    • Bug
    • Status: Open (View Workflow)
    • Critical
    • Resolution: Unresolved
    • 10.6, 10.11, 11.4, 11.8, 12.3, 13.0
    • None
    • JSON, Stored routines
    • None

    Description

      The JSON functions that take a document take a pointer into their first
      argument's buffer, then evaluate their remaining arguments, then read the
      document off that pointer. Evaluating an argument can run arbitrary user
      code, and if that code reassigns the same stored-routine variable the
      document came from, the variable's buffer is reallocated and the earlier
      pointer is left dangling. The document is then read out of freed heap.

      This is not particular to the functions that edit a document, nor to
      value arguments. Every argument is worked out just as late as every
      other, so a path reaches it (JSON_REMOVE, JSON_EXTRACT, JSON_VALUE and
      others have no value argument at all), so does a string to search for
      (JSON_SEARCH), and so does the number of spaces to indent by
      (JSON_DETAILED). Where the function scans the whole document rather
      than splicing round one place - JSON_CONTAINS, JSON_OVERLAPS,
      JSON_EQUALS - the entire read comes out of the freed buffer rather
      than only the tail.

      The read is masked today rather than harmless. The functions that splice a
      document back together re-parse what they built, and the bytes that come
      back out of freed heap usually fail that re-parse, so the statement returns
      NULL with a "Broken JSON string" warning instead of crashing. The ones that
      only read - JSON_LENGTH, JSON_EXISTS, JSON_CONTAINS_PATH and the like - have
      no such re-parse and simply answer from whatever the freed buffer now
      holds. Either way nothing tells the user that freed memory was read.

      How to repeat

      An Oracle-mode package body variable is the vehicle, being visible to both the
      statement and the function it calls.

      SET sql_mode='ORACLE';
      DELIMITER $$
      CREATE PACKAGE pkg AS
        PROCEDURE p;
      END;
      $$
      CREATE PACKAGE BODY pkg AS
        v TEXT := '{"a":1}';
       
        FUNCTION grow RETURN INT AS
        BEGIN
          v := '{"a":1,"pad":"' || LPAD('x', 20000, 'x') || '"}';
          RETURN 7;
        END;
       
        PROCEDURE p AS
        BEGIN
          SELECT JSON_INSERT(v, '$.b', grow()) AS r;
        END;
      END;
      $$
      DELIMITER ;
      CALL pkg.p();
      

      On a normal build:

      r
      NULL
      Warning	4035	Broken JSON string in argument 1 to function 'json_insert' at position 13
      

      The variable is 7 bytes before the call and 20016 after it, so the store
      cannot have been satisfied in place.

      ASAN

      Built from 10.11 at 1dab253482d with -DWITH_ASAN=ON, clang 21.

      ==3238202==ERROR: AddressSanitizer: heap-use-after-free on address 0x7bd1aea4c2ce
      READ of size 1 at 0x7bd1aea4c2ce thread T6
          #0 __asan_memcpy
          #1 Binary_string::q_append(char const*, unsigned long) sql/sql_string.h:435
          #2 append_simple(String*, char const*, unsigned long) sql/item_jsonfunc.cc:82
          #3 Item_func_json_insert::val_str(String*) sql/item_jsonfunc.cc:3603
          #4 Type_handler::Item_send_str(Item*, Protocol*, st_value*) const sql/sql_type.cc:7713
          #5 Protocol::send_result_set_row(List<Item>*) sql/protocol.cc:1333
          #14 sp_instr_stmt::exec_core(THD*, unsigned int*) sql/sp_head.cc:3950
          #18 sp_head::execute_procedure(THD*, List<Item>*) sql/sp_head.cc:2483
       
      0x7bd1aea4c2ce is located 30 bytes inside of 32-byte region
      [0x7bd1aea4c2b0,0x7bd1aea4c2d0)
       
      freed by thread T6 here:
          #0 free
          #1 my_free mysys/my_malloc.c:217
          #2 Binary_string::free_buffer() sql/sql_string.h:308
          #3 Binary_string::real_alloc(unsigned long) sql/sql_string.cc:44
          #4 Binary_string::alloc(unsigned long) sql/sql_string.h:774
          #5 Field_blob::store(char const*, unsigned long, charset_info_st const*) sql/field.cc:8878
          #6 Item::save_str_in_field(Field*, bool) sql/item.cc:7192
          #8 Field::sp_prepare_and_store_item(THD*, Item**) sql/field.cc:1517
          #9 THD::sp_eval_expr(Field*, Item**) sql/sp_head.cc:453
          #10 sp_rcontext::set_variable(THD*, unsigned int, Item**) sql/sp_rcontext.cc:640
          #11 sp_instr_set::exec_core(THD*, unsigned int*) sql/sp_head.cc:3983
          #15 sp_head::execute_function(...) sql/sp_head.cc:2228
          #16 Item_sp::execute_impl(THD*, Item**, unsigned int) sql/item.cc:3003
          #19 Item_func_sp::val_str(String*) sql/item_func.h:4154
          #20 append_json_value(String*, Item*, String*) sql/item_jsonfunc.cc:1967
          #21 Item_func_json_insert::val_str(String*) sql/item_jsonfunc.cc:3602
       
      previously allocated by thread T6 here:
          #0 malloc
          #1 my_malloc mysys/my_malloc.c:92
          #2 Binary_string::real_alloc(unsigned long) sql/sql_string.cc:45
          #4 Field_blob::store(char const*, unsigned long, charset_info_st const*) sql/field.cc:8878
          #5 Item::save_str_value_in_field(Field*, String*) sql/item.cc:413
          #6 Item_string::save_in_field(Field*, bool) sql/item.cc:7281
          #7 Field::sp_prepare_and_store_item(THD*, Item**) sql/field.cc:1517
          #9 sp_rcontext::set_variable(THD*, unsigned int, Item**) sql/sp_rcontext.cc:640
       
      SUMMARY: AddressSanitizer: heap-use-after-free in __asan_memcpy
      

      The free and the read are in the same Item_func_json_insert::val_str
      frame, one line apart: the buffer is freed underneath :3602 and read at
      :3603.

      Where the pointer is taken

      Item_func_json_insert::val_str opens with

      String *js= args[0]->val_json(&tmp_js);
      

      and Item_sp_variable::val_str ends with

      str_value.set(res->ptr(), res->length(), res->charset());
      

      which aliases rather than copies. That is deliberate - the comment above it
      explains that it marks the value const so that functions such as CONCAT cannot
      scribble on the variable through it. What is not accounted for is a value
      argument writing to the same variable while the alias is still being held.

      Affected functions

      Every row below was reproduced separately under ASAN, one server per
      case, all on 10.11 at 1dab253482d. Grouped by which argument is the
      one whose evaluation frees the document.

      Splicing a value into the document:

      function implementation read freed under
      JSON_INSERT Item_func_json_insert item_jsonfunc.cc:3603 :3602
      JSON_SET Item_func_json_insert item_jsonfunc.cc:3627 :3626
      JSON_REPLACE Item_func_json_insert item_jsonfunc.cc:3627 :3626
      JSON_ARRAY_APPEND Item_func_json_array_append item_jsonfunc.cc:2233 :2228
      JSON_ARRAY_INSERT Item_func_json_array_insert item_jsonfunc.cc:2428 :2412
      JSON_CONTAINS Item_func_json_contains item_jsonfunc.cc:1632 :1596

      Working out a path, with no value argument in the statement at all:

      function implementation read freed under
      JSON_EXISTS Item_func_json_exists item_jsonfunc.cc:734 :714
      JSON_VALUE, JSON_QUERY Json_path_extractor::extract item_jsonfunc.cc:818 :781
      JSON_EXTRACT, -> Item_func_json_extract::read_json item_jsonfunc.cc:1212 :1176
      JSON_CONTAINS_PATH Item_func_json_contains_path item_jsonfunc.cc:1873 :1845
      JSON_LENGTH Item_func_json_length item_jsonfunc.cc:3204 :3187
      JSON_REMOVE Item_func_json_remove item_jsonfunc.cc:3742 :3701
      JSON_KEYS Item_func_json_keys item_jsonfunc.cc:3955 :3936

      Working out a second document:

      function implementation read freed under
      JSON_EQUALS Item_func_json_equals item_jsonfunc.cc:665 :642
      JSON_MERGE, JSON_MERGE_PRESERVE Item_func_json_merge item_jsonfunc.cc:2779 :2767
      JSON_MERGE_PATCH Item_func_json_merge_patch item_jsonfunc.cc:3108 :3077
      JSON_OVERLAPS Item_func_json_overlaps item_jsonfunc.cc:5108 :5090

      Working out something that is neither:

      function implementation read freed under the argument
      JSON_SEARCH Item_func_json_search item_jsonfunc.cc:4187 :4144 string to search for
      JSON_DETAILED Item_func_json_format item_jsonfunc.cc:4326 :4309 number of spaces to indent by

      *Twenty-one functions across seventeen implementations.* The one
      implementation NOT affected among those that take a document is
      Item_func_json_contains_path::val_int, and only because it sits
      inside #ifdef DUMMY and is never compiled; the live val_bool
      entry point is in the table above.

      Functions with a single argument - JSON_VALID, JSON_TYPE, JSON_DEPTH,
      JSON_QUOTE, JSON_UNQUOTE, JSON_COMPACT, JSON_LOOSE, JSON_NORMALIZE -
      have nothing to work out after the document and are not affected.
      JSON_DETAILED is affected only in its two-argument form.

      What does NOT reproduce

      A session variable with a plain stored function, in the default sql_mode, is
      clean under ASAN and answers correctly:

      DELIMITER $$
      CREATE FUNCTION grow2() RETURNS INT
      BEGIN
        SET @v = CONCAT('{"a":1,"pad":"', REPEAT('x', 20000), '"}');
        RETURN 7;
      END
      $$
      DELIMITER ;
      SET @v = '{"a":1}';
      SELECT JSON_INSERT(@v, '$.b', grow2()) AS r;
      

      r
      {"a": 1, "b": 7}
      

      Affected versions

      Reproduced on 10.11 at 1dab253482d, which is the tip of that branch. The
      same alias-then-evaluate shape is present in sql/item_jsonfunc.cc on 10.5
      through main - nine occurrences of the pattern on each branch - but only 10.11
      was built and run.

      Attachments

        Issue Links

          Activity

            People

              danblack Daniel Black
              arcivanov Arcadiy Ivanov
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

                Created:
                Updated:

                Git Integration

                  Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.