Uploaded image for project: 'Couchbase Server'
  1. Couchbase Server
  2. MB-38327

Reading V2 access log causes segfault

    XMLWordPrintable

Details

    • Untriaged
    • Yes
    • KV Spint 2020-March

    Description

      Spock-era MutationLog had a MutationLogEntryV2 with the following layout.

      spock

      (gdb) ptype /o MutationLogEntryV2
      /* offset    |  size */  type = class MutationLogEntryV2 {
                               public:
                                 static const uint8_t MagicMarker;
                               private:
      /*    0      |     2 */    const uint16_t _vbucket;
      /*    2      |     1 */    const uint8_t magic;
      /*    3      |     1 */    const enum MutationLogType _type;
      /*    4      |     1 */    const uint8_t pad;
      /*    5      |     3 */    const class SerialisedDocKey : public DocKeyInterface<SerialisedDocKey> {
                                   protected:
      /*    5      |     1 */        uint8_t length;
      /*    6      |     1 */        enum DocNamespace docNamespace;
      /*    7      |     1 */        uint8_t bytes[1];
       
                                     /* total size (bytes):    3 */
                                 } _key;
       
                                 /* total size (bytes):    8 */
                               }
      

      In 6.5.0, there is also a MutationLogEntryV3, with the idea being that older versions can still be read as V2, and then "upgraded" to V3 - this is to support offline upgrades to 6.5.0.

      However, the 6.5.0 MutationLogEntryV2 struct is slightly different ( altered in http://review.couchbase.org/#/c/98769/ )

      mad-hatter

      (gdb) ptype /o MutationLogEntryV2
      /* offset    |  size */  type = class MutationLogEntryV2 {
                               public:
                                 static const uint8_t MagicMarker;
                               private:
      /*    0      |     2 */    const class Vbid {
                                   protected:
      /*    0      |     2 */        id_type vbid;
       
                                     /* total size (bytes):    2 */
                                 } _vbucket;
      /*    2      |     1 */    const uint8_t magic;
      /*    3      |     1 */    const MutationLogType _type;
      /*    4      |     2 */    const uint8_t pad[2];
      /*    6      |     2 */    const class SerialisedDocKey : public DocKeyInterface<SerialisedDocKey> {
                                   protected:
      /*    6      |     1 */        uint8_t length;
      /*    7      |     1 */        uint8_t bytes[1];
       
                                     /* total size (bytes):    2 */
                                 } _key;
       
                                 /* total size (bytes):    8 */
                               }
      

      A mutation log written by spock will have the key length at offset 5, however the mad-hatter version will read the length from offset 6. This would find the value which was written as the docNamespace. This would always be zero (default collection) so the key length will be read as zero.

      Given the constructor used as part of upgradeEntry to upgrade a V2 entry to V3 (_key is a SerialisedDocKey)

      mutation_log_entry.h

      315
          MutationLogEntryV3(const MutationLogEntryV2& mleV2)
      316
              : _vbucket(mleV2._vbucket),
      317
                magic(MagicMarker),
      318
                _type(mleV2._type),
      319
                _key({mleV2._key.data() + 1, mleV2._key.size() - 1},
      320
                     CollectionID::Default) {
      321
              (void)pad;
      322
          }
      

      storeddockey.cc

      89
      SerialisedDocKey::SerialisedDocKey(cb::const_byte_buffer key,
      90
                                         CollectionID cid) {
      91
          cb::mcbp::unsigned_leb128<CollectionIDType> leb128(cid);
      92
          length = gsl::narrow_cast<uint8_t>(key.size() + leb128.size());
      93
          std::copy(key.begin(),
      94
                    key.end(),
      95
                    std::copy(leb128.begin(), leb128.end(), bytes));
      96
      }
      

      We subtract 1 from the V2 key size, as read from disk, at

      _key({mleV2._key.data() + 1, mleV2._key.size() - 1}, CollectionID::Default)
      

      and underflow. Then, we copy the entire key std::copy(key.begin(), key.end(), <outputitr>);, reading past the end of the key and writing past the end of the memory previously allocated to hold the V3 struct.

      Confirmed by unit test, reading an access log written by spock hits this behaviour.

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            thuan Thuan Nguyen
            james.harrison James Harrison (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            7 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes

                PagerDuty