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

Server crashes in Field::set_warning_truncated_wrong_value

Details

    Description

      Reproducible on 10.2-10.4, with MyIsam/Innodb

      set names utf8;
      set use_stat_tables=complementary;
       
      create table t1 ( a varchar(255) character set utf8);
      insert into t1 values ('ӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥӥ');
       
      analyze table t1;
      select 1 from t1;
      
      

      10.2 bf71d263621c90cbddc7bde

          #0 0x5577ef4348a6 in Field::set_warning_truncated_wrong_value(char const*, char const*) /home/alice/git/10.2/sql/field.cc:10949
          #1 0x5577ef413a76 in Field_longstr::check_string_copy_error(String_copier const*, char const*, charset_info_st const*) /home/alice/git/10.2/sql/field.cc:7031
          #2 0x5577ef43746a in Field_longstr::check_conversion_status(String_copier const*, char const*, charset_info_st const*, bool) /home/alice/git/10.2/sql/field.h:1770
          #3 0x5577ef4192d4 in Field_varstring::store(char const*, unsigned int, charset_info_st const*) /home/alice/git/10.2/sql/field.cc:7635
          #4 0x5577ef05a995 in Column_stat::get_stat_values() (/home/alice/git/10.2/sql/mysqld+0xf7b995)
          #5 0x5577ef04fd7d in read_statistics_for_table /home/alice/git/10.2/sql/sql_statistics.cc:2998
          #6 0x5577ef051b09 in read_statistics_for_tables_if_needed(THD*, TABLE_LIST*) /home/alice/git/10.2/sql/sql_statistics.cc:3289
          #7 0x5577eed987dc in open_and_lock_tables(THD*, DDL_options_st const&, TABLE_LIST*, bool, unsigned int, Prelocking_strategy*) /home/alice/git/10.2/sql/sql_base.cc:4779
          #8 0x5577eed7aa77 in open_and_lock_tables(THD*, TABLE_LIST*, bool, unsigned int) /home/alice/git/10.2/sql/sql_base.h:506
          #9 0x5577eeeab591 in execute_sqlcom_select /home/alice/git/10.2/sql/sql_parse.cc:6446
          #10 0x5577eee98fac in mysql_execute_command(THD*) /home/alice/git/10.2/sql/sql_parse.cc:3537
          #11 0x5577eeeb466c in mysql_parse(THD*, char*, unsigned int, Parser_state*, bool, bool) /home/alice/git/10.2/sql/sql_parse.cc:8059
          #12 0x5577eee8f8d4 in dispatch_command(enum_server_command, THD*, char*, unsigned int, bool, bool) /home/alice/git/10.2/sql/sql_parse.cc:1829
          #13 0x5577eee8ca53 in do_command(THD*) /home/alice/git/10.2/sql/sql_parse.cc:1379
          #14 0x5577ef1b52f2 in do_handle_one_connection(CONNECT*) /home/alice/git/10.2/sql/sql_connect.cc:1336
          #15 0x5577ef1b4cfa in handle_one_connection /home/alice/git/10.2/sql/sql_connect.cc:1242
          #16 0x5577f0365327 in pfs_spawn_thread /home/alice/git/10.2/storage/perfschema/pfs.cc:1862
          #17 0x7fb000b9d6b9 in start_thread (/lib/x86_64-linux-gnu/libpthread.so.0+0x76b9)
          #18 0x7fb00003241c in clone (/lib/x86_64-linux-gnu/libc.so.6+0x10741c)
      
      

      Reproducible on non-debug build

      10.3.13

      190312 11:40:40 [ERROR] mysqld got signal 11 ;
       
      /lib/x86_64-linux-gnu/libpthread.so.0(+0x11390)[0x7fc2c4b2a390]
      sql/field.cc:11253(Field::set_warning_truncated_wrong_value(char const*, char const*))[0x557c68f3767b]
      sql/field.cc:6963(Field_longstr::check_string_copy_error(String_copier const*, char const*, charset_info_st const*))[0x557c68f37777]
      sql/field.h:1790(Field_longstr::check_conversion_status(String_copier const*, char const*, charset_info_st const*, bool))[0x557c68f3e6f1]
      sql/field.h:3357(Field_varstring::store_length(unsigned int))[0x557c68f38056]
      sql/sql_statistics.cc:1157(Column_stat::get_stat_values())[0x557c68dfaeef]
      sql/sql_statistics.cc:4088(read_statistics_for_table)[0x557c68df96a4]
      sql/sql_base.cc:5019(open_and_lock_tables(THD*, DDL_options_st const&, TABLE_LIST*, bool, unsigned int, Prelocking_strategy*))[0x557c68d33d72]
      sql/sql_base.h:503(open_and_lock_tables(THD*, TABLE_LIST*, bool, unsigned int))[0x557c68d06977]
      sql/sql_parse.cc:6472(execute_sqlcom_select(THD*, TABLE_LIST*))[0x557c68ca10cd]
      sql/sql_parse.cc:3772(mysql_execute_command(THD*))[0x557c68d8a78c]
      sql/sql_class.h:1810(mysql_parse(THD*, char*, unsigned int, Parser_state*, bool, bool))[0x557c68d8cdf9]
      sql/sql_parse.cc:1854(dispatch_command(enum_server_command, THD*, char*, unsigned int, bool, bool))[0x557c68d8ef95]
      sql/sql_parse.cc:1398(do_command(THD*))[0x557c68d8f73e]
      sql/sql_connect.cc:1403(do_handle_one_connection(CONNECT*))[0x557c68e573ff]
      sql/sql_connect.cc:1311(handle_one_connection)[0x557c68e57524]
      /lib/x86_64-linux-gnu/libpthread.so.0(+0x76ba)[0x7fc2c4b206ba]
      x86_64/clone.S:111(clone)[0x7fc2c398541d]
       
      Query (0x7fc2500115d0): select 1 from t1
      

      Attachments

        Issue Links

          Activity

            bar Alexander Barkov added a comment - - edited

            The crash happens in Field_varstring::store() which is called from here:

                      case COLUMN_STAT_MIN_VALUE:
                        table_field->read_stats->min_value->set_notnull();
                        stat_field->val_str(&val);
                        table_field->read_stats->min_value->store(val.ptr(), val.length(),
                                                                  &my_charset_bin);
                        break;
            

            Field_varstring::store() tries to generate a warning, because the value in the column column_statistics.min_value is not well-formed: it has a hard limit of 255 bytes, so in this bug report on the position 255 it contains the first byte (of a two-byte character) without the second byte.

            The warning is generated here:

            void Field::set_warning_truncated_wrong_value(const char *type_arg,
                                                          const char *value)
            {
              THD *thd= get_thd();
              const char *db_name= table->s->db.str;
              const char *table_name= table->s->table_name.str;
              ...
            }
            

            Notice, it tries to get the db and the table from from "table", however "table" is a NULL pointer here.

            table_field->read_stats->min_value is created here:

            static
            void create_min_max_statistical_fields_for_table_share(THD *thd,
                                                                   TABLE_SHARE *table_share)
            {
              ...
                  for (Field **field_ptr= table_share->field; *field_ptr; field_ptr++)
                  {
                    Field *fld;
                    Field *table_field= *field_ptr;
                    my_ptrdiff_t diff= record - table_share->default_values;
                    if (!(fld= table_field->clone(&stats_cb->mem_root, diff)))
                      continue;
                    if (i == 0)
                      table_field->read_stats->min_value= fld;
                    else
                      table_field->read_stats->max_value= fld;
                  }
             ...
            }
            

            The above code clones a Field instance but does not set the "table" pointer for the cloned Field.
            Note, the original instance, table_field, does have a non-NULL pointer.

            So a solution to fix the crash would probably to copy table_field->table to min_value and max_value.

            However, it will fix only a part of the problem.

            Well-formedness problem

            It is generally wrong that any warnings are generated while copying from column_statistics.min_value.
            1. either column_statistics.min_value should store well-formed values (e.g. don't break characters in the middle)
            2. or the copying code should suppress warnings

            Note, column_statistics.min_value can be modified manually. So probably both #1 and #2 are needed.

            Contraction problem

            Also, the underlying code should be checked for contraction compatibility. The code copying to column_statistics.min_value should make sure not to break contractions in the middle, otherwise max_value can be very far from the actual maximum value.

            For example, consider this data in combination with Czech collation:

            CONCAT(REPEAT('x',254), 'ch'))
            

            'ch' is a separate letter which is sorted between 'h' and 'i':
            http://collation-charts.org/mysql60/mysql604.utf8_czech_ci.html

            'ch' should not be broken into parts when copying to column_statistics.min_value:
            'c' cannot be the last 255-th byte in column_statistics.min_value, because it was followed by 'h' in the original full-length data. The copying code should store only the REPEAT('x',254) part.

            For column_statistics.max_value, the copying code will be even harder: it should replace 'ch' to the character which immediately follows 'ch' in the collation, which is 'i'.

            bar Alexander Barkov added a comment - - edited The crash happens in Field_varstring::store() which is called from here: case COLUMN_STAT_MIN_VALUE: table_field->read_stats->min_value->set_notnull(); stat_field->val_str(&val); table_field->read_stats->min_value->store(val.ptr(), val.length(), &my_charset_bin); break ; Field_varstring::store() tries to generate a warning, because the value in the column column_statistics.min_value is not well-formed: it has a hard limit of 255 bytes, so in this bug report on the position 255 it contains the first byte (of a two-byte character) without the second byte. The warning is generated here: void Field::set_warning_truncated_wrong_value( const char *type_arg, const char *value) { THD *thd= get_thd(); const char *db_name= table->s->db.str; const char *table_name= table->s->table_name.str; ... } Notice, it tries to get the db and the table from from "table", however "table" is a NULL pointer here. table_field->read_stats->min_value is created here: static void create_min_max_statistical_fields_for_table_share(THD *thd, TABLE_SHARE *table_share) { ... for (Field **field_ptr= table_share->field; *field_ptr; field_ptr++) { Field *fld; Field *table_field= *field_ptr; my_ptrdiff_t diff= record - table_share->default_values; if (!(fld= table_field->clone(&stats_cb->mem_root, diff))) continue ; if (i == 0) table_field->read_stats->min_value= fld; else table_field->read_stats->max_value= fld; } ... } The above code clones a Field instance but does not set the "table" pointer for the cloned Field. Note, the original instance, table_field, does have a non-NULL pointer. So a solution to fix the crash would probably to copy table_field->table to min_value and max_value. However, it will fix only a part of the problem. Well-formedness problem It is generally wrong that any warnings are generated while copying from column_statistics.min_value . 1. either column_statistics.min_value should store well-formed values (e.g. don't break characters in the middle) 2. or the copying code should suppress warnings Note, column_statistics.min_value can be modified manually. So probably both #1 and #2 are needed. Contraction problem Also, the underlying code should be checked for contraction compatibility. The code copying to column_statistics.min_value should make sure not to break contractions in the middle, otherwise max_value can be very far from the actual maximum value. For example, consider this data in combination with Czech collation: CONCAT(REPEAT( 'x' ,254), 'ch' )) 'ch' is a separate letter which is sorted between 'h' and 'i': http://collation-charts.org/mysql60/mysql604.utf8_czech_ci.html 'ch' should not be broken into parts when copying to column_statistics.min_value : 'c' cannot be the last 255-th byte in column_statistics.min_value , because it was followed by 'h' in the original full-length data. The copying code should store only the REPEAT('x',254) part. For column_statistics.max_value , the copying code will be even harder: it should replace 'ch' to the character which immediately follows 'ch' in the collation, which is 'i'.
            bar Alexander Barkov added a comment - - edited

            igor, please take over this bug. The problem resides in the statistical code. See the analysis above.

            bar Alexander Barkov added a comment - - edited igor , please take over this bug. The problem resides in the statistical code. See the analysis above.
            varun Varun Gupta (Inactive) added a comment - - edited

            Some imput from bar

            To fix the crash
            1. Fix create_min_max_statistical_fields_for_table_share() to make sure to set table_field->table to a non-NULL pointer
            2. Fix writing code to write only full multi-byte sequences, so from the desciption we have letter 'ӥ' (0xD3A5 in utf8), if we want to write a multi-bytes sequence then we must be sure that there is space to write the entire multi-byte sequence, else we should reject the multi-byte character

            Another task would be to make sure that min and max values are set correctly, the suggestion is to fix this in 10.4

            varun Varun Gupta (Inactive) added a comment - - edited Some imput from bar To fix the crash 1. Fix create_min_max_statistical_fields_for_table_share() to make sure to set table_field->table to a non-NULL pointer 2. Fix writing code to write only full multi-byte sequences, so from the desciption we have letter 'ӥ' (0xD3A5 in utf8), if we want to write a multi-bytes sequence then we must be sure that there is space to write the entire multi-byte sequence, else we should reject the multi-byte character Another task would be to make sure that min and max values are set correctly, the suggestion is to fix this in 10.4
            varun Varun Gupta (Inactive) added a comment - Patch http://lists.askmonty.org/pipermail/commits/2019-March/013580.html
            varun Varun Gupta (Inactive) added a comment - - edited

            Filed MDEV-19028 as a separate issue to address the contraction problem mentioned above

            varun Varun Gupta (Inactive) added a comment - - edited Filed MDEV-19028 as a separate issue to address the contraction problem mentioned above

            People

              varun Varun Gupta (Inactive)
              alice Alice Sherepa
              Votes:
              1 Vote for this issue
              Watchers:
              7 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Git Integration

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