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

mariadb-binlog --print-table-metadata OOB Read on Corrupt Table Map

    XMLWordPrintable

Details

    • Can result in hang or crash

    Description

      A TABLE_MAP event in a binary log carries an optional block of
      column metadata (names, character sets, signedness, primary keys) that
      mariadb-binlog --print-table-metadata decodes and prints. When that
      block is malformed or corrupt, the tool reads a few bytes past the end
      of the buffer it allocated for the block. Under an AddressSanitizer
      build the tool aborts. Under a normal build the read returns whatever
      bytes follow the allocation, and the tool then decodes further metadata
      from a bogus length and prints garbage or crashes.

      The same decode runs on a replication slave when it applies a
      TABLE_MAP event, so a malformed event in a relay log reaches it
      there as well.

      The optional-metadata block is a sequence of type-length-value entries.
      Table_map_log_event's constructor copies the block into a buffer
      that my_malloc() sizes to exactly the block's length
      (m_optional_metadata, m_optional_metadata_len).
      Optional_metadata_fields::Optional_metadata_fields() in
      sql/log_event.cc then walks the block:

      for (uchar *field= optional_metadata ; field < metadata_end ; field+= len)
      {
        Optional_metadata_field_type type=
          static_cast<Optional_metadata_field_type>(field[0]);
        field++;
        len= net_field_length(&field);        // length prefix decoded here
        ...
      

      The loop condition bounds the read of the type byte field[ 0] , but
      nothing bounds the length prefix that follows. net_field_length()
      reads the first byte of the prefix and, for a 0xFE lead byte, reads
      four more bytes as the encoded length and advances the cursor by nine,
      with no knowledge of metadata_end. A 0xFE placed where the last
      prefix begins makes net_field_length() read those four bytes past
      the end of the my_malloc() allocation. The reporter states eight
      bytes; the actual read is the four bytes of uint4korr(), while the
      cursor advances by nine.

      The over-read is the entry point. Its bogus return value then drives the
      rest of the loop: the cursor is left past the buffer, len is
      arbitrary, and the switch either dispatches a parse helper on
      out-of-range arguments or the loop skips to field += len and exits.
      On branches from 10.6 through 11.8 the per-type parse helpers have no
      bounds checks of their own, so a surviving bogus len produces
      further reads past the buffer inside them.

      MDEV-39689 added the if (unlikely(p > end)) return; guards inside
      the per-type parse helpers on the development branch, but it left the
      length prefix in the dispatch loop above decoded before any check, so
      this over-read remains.

      Reproduction

      A debug injection places a 0xFE marker on the last byte
      the allocation can hold and aims the decoder at it, so the loop's own
      net_field_length() reads past the buffer. Note that an ASAN build is
      required to reproduce the problem.

      The injection:

      diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc
      index 90d832e3626..29f25b3947b 100644
      --- a/sql/log_event_server.cc
      +++ b/sql/log_event_server.cc
      @@ -7070,6 +7070,11 @@ void Table_map_log_event::init_metadata_fields()
               init_primary_key_field())
             m_metadata_buf.length(0);
         }
      +  DBUG_EXECUTE_IF("write_corrupt_optional_metadata_marker",
      +  {
      +    m_metadata_buf.length(0);
      +    m_metadata_buf.append(STRING_WITH_LEN("\x01\x04\x00\x00\x00\x00\x01\xFE"));
      +  });
         DBUG_VOID_RETURN;
       }
      

      The test:

      --source include/have_debug.inc
      --source include/have_binlog_format_row.inc
       
      if (`SELECT COUNT(*)= 0 FROM information_schema.system_variables
           WHERE variable_name= 'have_sanitizer' AND global_value LIKE "ASAN%"`)
      {
        --skip Test requires an ASan build to observe the out-of-bounds read
      }
       
      SET @old_metadata= @@GLOBAL.binlog_row_metadata;
      SET GLOBAL binlog_row_metadata = FULL;
       
      CREATE TABLE t1 (c_signed INT, c_char CHAR(10), c_unsigned INT UNSIGNED);
       
      SET @old_dbug= @@GLOBAL.debug_dbug;
      SET @@GLOBAL.debug_dbug= '+d,write_corrupt_optional_metadata_marker';
      INSERT INTO t1 VALUES (-1, 'x', 1);
      SET @@GLOBAL.debug_dbug= @old_dbug;
       
      --let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1)
      FLUSH BINARY LOGS;
      --let $binlog_path= `SELECT CONCAT(@@datadir, '/$binlog_file')`
      --let $output_file= $MYSQLTEST_VARDIR/tmp/oob.output
       
      --echo # $MYSQL_BINLOG -F --print-table-metadata --base64-output=never $binlog_path > $output_file 2>&1
      --exec $MYSQL_BINLOG -F --print-table-metadata --base64-output=never $binlog_path > $output_file 2>&1
       
      # Match the lines specific to --print-table-metadata and ensure they are displayed
      --let SEARCH_PATTERN= (?m-s:# (?:Columns\(| {8}).*)
      --let SEARCH_FILE= $output_file
      --let SEARCH_OUTPUT= matches
      --source include/search_pattern_in_file.inc
      --remove_file $output_file
       
      DROP TABLE t1;
      SET GLOBAL binlog_row_metadata = @old_metadata;
      

      Reported by: Muntadher H

      Attachments

        Issue Links

          Activity

            People

              Unassigned Unassigned
              bnestere Brandon Nesterenko
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

                Created:
                Updated:

                Time Tracking

                  Estimated:
                  Original Estimate - 2d Original Estimate - 2d
                  2d
                  Remaining:
                  Time Spent - 3.75h Remaining Estimate - 1d 7.25h
                  1d 7.25h
                  Logged:
                  Time Spent - 3.75h Remaining Estimate - 1d 7.25h
                  3.75h

                  Git Integration

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