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

Copy_field leaves from_field and to_field uninitialised, and the two kinds of Copy_field share one array

    XMLWordPrintable

Details

    • Bug
    • Status: Open (View Workflow)
    • Minor
    • Resolution: Unresolved
    • 10.6, 10.11, 11.4, 11.8, 12.3, 13.0
    • 10.11, 11.4, 11.8, 12.3, 13.0
    • Server
    • None

    Description

      Copy_field carries two members naming the fields it copies between:

        Field *from_field,*to_field;
        String tmp;					// For items
       
        Copy_field() = default;
        ~Copy_field() = default;
        void set(Field *to,Field *from,bool save);	// Field to field
        void set(uchar *to,Field *from);		// Field to string
      

      The default constructor is trivial and every array of these is made by an array
      new carrying no initialiser, which default-initialises rather than
      value-initialises, so both members start out indeterminate:

        if (!(param->copy_field= new (thd->mem_root) Copy_field[field_count]))
      

      TMP_TABLE_PARAM::copy_field is allocated that way in
      Create_tmp_table::start() and again in setup_copy_fields(), and
      multi_update::initialize_tables() does the same for its own array.

      Only one of the two set() overloads then fills them in.

      The two overloads disagree

      Copy_field::set(Field *to, Field *from, bool save) – the field-to-field
      form – assigns from_field and to_field and installs do_copy
      functions that use them.

      Copy_field::set(uchar *to, Field *from) – the field-to-string-buffer form,
      whose comment says "The 'to' buffer should have a size of
      field->pack_length()+1" – assigns neither:

      void Copy_field::set(uchar *to,Field *from)
      {
        from_ptr=from->ptr;
        to_ptr=to;
        from_length=from->pack_length_in_rec();
        if (from->maybe_null())
        {
          from_null_ptr=from->null_ptr;
          from_bit=	  from->null_bit;
          to_ptr[0]=	  1;				// Null as default value
          to_null_ptr=  (uchar*) to_ptr++;
          to_bit=	  1;
          if (from->table->maybe_null)
          {
            null_row=   &from->table->null_row;
            do_copy=	  do_outer_field_to_null_str;
          }
          else
            do_copy=	  do_field_to_null_str;
        }
        else
        {
          to_null_ptr=  0;				// For easy debugging
          do_copy= Field::do_field_eq;
        }
      }
      

      Note to_null_ptr= 0 carrying the comment "For easy debugging", so the
      function does set a member it does not need purely so that a debugger shows
      something sensible. The two Field * members are left alone.

      Both kinds go into the same array, walked by one loop

      Create_tmp_table::finalize() appends field-to-field entries and
      setup_copy_fields() appends field-to-string-buffer entries, both to
      param->copy_field, and copy_fields() walks the one array:

      void
      copy_fields(TMP_TABLE_PARAM *param)
      {
        Copy_field *ptr=param->copy_field;
        Copy_field *end=param->copy_field_end;
       
        DBUG_ASSERT((ptr != NULL && end >= ptr) || (ptr == NULL && end == NULL));
       
        for (; ptr != end; ptr++)
          (*ptr->do_copy)(ptr);
      

      Nothing distinguishes the two kinds. An entry's do_copy pointer is the only
      record of which set() made it, and that is not readable as a discriminator.
      So a walk of this array cannot ask an entry which fields it connects: reading
      ptr->to_field is undefined behaviour for every entry the string form made,
      and reading a wild pointer as a Field * follows it into arbitrary memory.

      Nothing trips it today

      The three do_copy functions the string form installs –
      do_field_to_null_str, do_outer_field_to_null_str and
      Field::do_field_eq – use only to_ptr, from_ptr, from_length,
      the null pointers and null_row. None of them reads from_field or
      to_field. Every function that does read those members
      (do_copy_null, do_outer_field_null, do_copy_not_null,
      do_save_blob, do_conv_blob, do_field_string and the rest) is
      installed only by the field-to-field form, which fills them in. So the members
      are read only where they were written, and the defect is latent rather than
      live.

      It is reachable the moment anything else walks param->copy_field. Observed
      while doing exactly that: a guard of the form

          if (ptr->to_field && ...)
      

      added to the copy_fields() loop segfaults on an ordinary
      GROUP BY query, the indeterminate pointer being non-null and unmapped:

      sql/field.h:1259(Field::is_valid_json_static() const)
      sql/sql_select.cc(copy_fields(TMP_TABLE_PARAM*))
      sql/sql_select.cc(end_send_group(JOIN*, st_join_table*, bool))
      sql/sql_select.cc(evaluate_join_record(JOIN*, st_join_table*, int))
      

      A null check is the obvious way to write such a guard and it does not work
      here, which is what makes this worth recording rather than leaving to whoever
      next reaches for these members.

      Provenance

      The class predates the current history: Copy_field arrives with the
      initial import, and the members have never been initialised. 08c852026dd
      ("Apply clang-tidy to remove empty constructors / destructors", 2023-02-07)
      changed the spelling from

        Copy_field() {}
        ~Copy_field() {}
      

      to = default, which leaves the behaviour identical – an empty
      user-provided constructor default-initialises the members exactly as a trivial
      one does. That commit neither introduced nor worsened this; it is named only so
      that a reader who finds it in git blame does not stop there.

      The asymmetry between the two set() overloads is present unchanged at the
      tips of 10.5, 10.6, 10.11, 11.4, 11.8, 12.0, 12.1, 12.2, 12.3 and main.

      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.