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

Slave Overflow on Malformed Query_compressed_log_event

    XMLWordPrintable

Details

    • Can result in hang or crash

    Description

      A slave can overflow when uncompressing a
      Query_compressed_log_event.

      The function query_event_uncompress() reads in a 32-bit uint,
      converts it to 64-bit, manipulates the value, casts the value back to
      32-bits, and then decides memory allocation strategy based on that
      value:

        uint32 un_len=  binlog_get_uncompress_len(tmp);
       
        // bad event 
        if (comp_len < 0 || un_len == 0)
          return 1;
       
        *newlen= (ulong)(tmp - src) + un_len;
        if (contain_checksum)
          *newlen+= BINLOG_CHECKSUM_LEN;
       
        uint32 alloc_size= (uint32)ALIGN_SIZE(*newlen);
        
        if (alloc_size <= buf_size)
          new_dst= buf;
        else 
        {
          new_dst= (uchar *) my_malloc(PSI_INSTRUMENT_ME, alloc_size, MYF(MY_WME));
          if (!new_dst)
            return 1;
          *is_malloc= true;
        } 
      

      If the 32-bit read-in value is close to the 32-bit max, this can cause
      the value to overflow and pick the wrong allocation strategy.

      In practice, this cannot happen because the max_allowed_packet for a
      server is hard-capped at 1GB, so there is not a valid path for a user to
      generate a query that overflow.

      The follwing MTR test (with .cnf and patch) can be used to show the
      issue.

      MTR file

      --source include/have_debug.inc
      --source include/have_binlog_format_statement.inc
      --source include/master-slave.inc
       
      --echo #
      --echo # Setup: a query large enough that its decompressed body well exceeds
      --echo # the 4 KB stack buffer in query_event_uncompress.
      --connection master
      CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 LONGTEXT);
           
      --echo #
      --echo # Arm the corruption: the next compressed query event written will
      --echo # have its un_len header overwritten to 0xFFFFFFFC.
      SET @saved_dbug= @@SESSION.debug_dbug;
      SET @@SESSION.debug_dbug= "+d,query_compressed_corrupt_un_len_overflow";
       
      --echo #
      --echo # An ~8 KB INSERT statement -- compresses to a few hundred bytes on
      --echo # the wire, but decompresses back to ~8 KB at the slave, which is
      --echo # what overruns the 4 KB stack buffer.
      --let $payload= `SELECT REPEAT('A', 8192)`
      --eval INSERT INTO t1 VALUES (1, '$payload')
       
      SET @@SESSION.debug_dbug= @saved_dbug;
      --source include/save_master_gtid.inc
       
      --echo #
      --echo # The slave's IO thread crashes while uncompressing the malformed
      --echo # QUERY_COMPRESSED_EVENT. Reaching this sync means the bug did not
      --echo # fire; on an unfixed build the slave aborts here.
      --connection slave
      --source include/sync_with_master_gtid.inc
       
      --echo #
      --echo # Cleanup
      --connection master
      DROP TABLE t1;
      --source include/rpl_end.inc
      

      MTR .cnf

      !include ../my.cnf
       
      [mysqld]
      log_bin_compress=ON
      log_bin_compress_min_len=10
      binlog_checksum=NONE
      

      Patch file

      diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc
      index 8652888d744..f96e7f9dc18 100644
      --- a/sql/log_event_server.cc
      +++ b/sql/log_event_server.cc
      @@ -1298,6 +1298,24 @@ bool Query_compressed_log_event::write(Log_event_writer *writer)
         if (buffer &&
             !binlog_buf_compress((uchar*) query, buffer, q_len, &compressed_size))
         {
      +    DBUG_EXECUTE_IF("query_compressed_corrupt_un_len_overflow",
      +    {
      +      /*
      +        Rewrite the un_len header so it claims a near-UINT32_MAX
      +        uncompressed length (0xFFFFFFFC), forcing a 4-byte length
      +        prefix. binlog_get_compress_len reserves room for a 4-byte
      +        prefix, so we can safely shift the zlib payload right.
      +      */
      +      uchar old_lenlen= buffer[0] & 0x07;
      +      uint32 zlib_data_len= compressed_size - 1 - old_lenlen;
      +      memmove(buffer + 1 + 4, buffer + 1 + old_lenlen, zlib_data_len);
      +      buffer[0]= 0x84;
      +      buffer[1]= 0xFF;
      +      buffer[2]= 0xFF;
      +      buffer[3]= 0xFF;
      +      buffer[4]= 0xFC;
      +      compressed_size= zlib_data_len + 5;
      +    });
           /*
             Write the compressed event. We have to temporarily store the event
             in query and q_len as Query_log_event::write() uses these.
      

      Note that compressed row events and regular Query events have already
      fixed this problem and are not vulnerable. Their pattern should be used
      here to fix this.

      Reported by Winston Crooker

      Attachments

        Issue Links

          Activity

            People

              Unassigned Unassigned
              bnestere Brandon Nesterenko
              Votes:
              0 Vote for this issue
              Watchers:
              3 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.