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

UUID type: valid v8 values with byte 8 = 0x80 rejected, variant-0 values accepted

    XMLWordPrintable

Details

    • Bug
    • Status: Confirmed (View Workflow)
    • Critical
    • Resolution: Unresolved
    • 10.11, 11.4, 11.8, 12.3
    • 10.11, 11.4, 11.8, 12.3
    • Data types
    • None
    • Unexpected results

    Description

      UUID version >= 8 variant validation is wrong for exactly two values of byte 8 (accepts invalid variant 0, rejects valid variant 10)

      Created bug report on behalf of Marc-Etienne Barrut. This report was also sent to the developers mailing list.

      We're trying to make use of UUIDv8 in our application (well I'm in the research phase), and I tried to use an UUID typed field.
      For UUIDs with version >= 8, the validation of the variant field is wrong for exactly two values of byte 8.
      One valid UUID is rejected, and one invalid UUID is accepted.
      Quick reproduction script:

      CREATE TABLE t1 (id UUID);
       
      -- variant 0, should be rejected, but is accepted:
      INSERT INTO t1 VALUES ('00000000-0000-8000-0000-000000000000');
       
      -- variant 10 (valid per RFC 9562), should be accepted, but gives
      -- ERROR 1292 (22007): Incorrect uuid value:
      INSERT INTO t1 VALUES ('00000000-0000-8000-8000-000000000000');
       
      SELECT * FROM t1;
      DROP TABLE t1;
      

      The expected result would be that the first insert fails and the second succeeds. Currently it's the other way round.

      A quick reference table of things I tried just to be sure. Version nibble 8, varying byte 8:

        byte 8   variant bits   expected   actual
        0x00     00             reject     ACCEPTED   <- wrong
        0x01     00             reject     rejected
        0x7f     01             reject     rejected
        0x80     10             accept     REJECTED   <- wrong
        0x81     10             accept     accepted
        0xbf     10             accept     accepted
        0xff     11             accept     accepted
      

      If needed, I'm happy to put together a patch and an MTR test for mysql-test/main/type_uuid.test. Just let me know which branch it should be based on.

      Full disclosure: From this part on, the analysis was done by AI (Claude Opus 5 if it's relevant).

      Source checked: plugin/type_uuid/sql_type_uuid.h on branches 11.4, 11.8 and main, the expression below is byte-identical in all three.

      if (m_buffer[6] & -m_buffer[8] & 0x80)
        goto err; // impossible combination: version >= 8, variant = 0
      

      The first clause is correct: m_buffer[6] & 0x80 is set exactly when the version nibble is >= 8.

      The second clause, -m_buffer[8] & 0x80, is meant to test that the top bit of byte 8 is clear, i.e. variant = 0.
      That holds for byte 8 in 0x01..0x7f: ~x has bit 7 set, and the +1 of two's complement cannot carry into bit 7, because the low seven bits of ~x are never all ones in that range.

      It fails at the two fixed points of negation:
      byte 8 = 0x00 – -0 is 0, bit 7 clear, so the check does not fire. But 0x00 has variant bits 00, which is variant 0 and should be rejected.
      byte 8 = 0x80 – -0x80 is 0xFFFFFF80 after integer promotion, bit 7 set, so the check fires. But 0x80 has variant bits 10, the RFC 9562 variant, and is perfectly valid.

      The two cases are mirror images: the only byte that should be rejected is the one negation can't flag, and the byte that gets wrongly rejected is the one that negates to itself.

      IMPACT

      Only version >= 8 reaches this check, so v1 through v7 are unaffected, which is probably why it hasn't surfaced.
      For a UUIDv8 layout with random bits in the low six bits of byte 8 – the natural layout, and what RFC 9562 leaves to the implementer – byte 8 is uniform over 0x80..0xbf, so roughly one insert in 64 (1.6%) is rejected at random.
      That's frequent enough to hit production and rare enough to survive review and integration tests. It's how I ran into it.

      SUGGESTED FIX

      The parse expression should be:

      -    if (m_buffer[6] & -m_buffer[8] & 0x80)
      +    if ((m_buffer[6] & 0x80) && !(m_buffer[8] & 0x80))
             goto err; // impossible combination: version >= 8, variant = 0
      

      but I don't think this can be applied on its own.

      The same x & -y & 0x80 idiom appears in rec_need_swap() in the same header:

      // s[6] & 0x80 && s[8] > 0: this means a swapped uuid
      static bool rec_need_swap(const char *s)
      { return s[6] & -s[8] & 0x80; }
      

      Lining the two up for version nibble 8:

      byte 8   parse now   parse fixed   mem_need_swap   rec_need_swap
      0x00     accept      reject        0               0
      0x01     reject      reject        0               1
      0x7f     reject      reject        0               1
      0x80     reject      ACCEPT        0               1
      0x81     accept      accept        0               0
      0xbf     accept      accept        0               0
      0xff     accept      accept        0               0
      

      mem_need_swap is 0 for every v8 value, which is correct, v8 is never byte-swapped on write.
      But rec_need_swap returns 1 for byte 8 in 0x01..0x80, so a record written unswapped would be unswapped again on read.

      That asymmetry is currently unreachable: every v8 value where rec_need_swap fires is rejected at parse time, and 0x00, the one invalid value that slips through, happens to be where the idiom returns 0.
      Fixing the parse check alone makes 0x80 insertable, and 0x80 is exactly the value that trips rec_need_swap.
      That would trade a wrongly rejected insert for a silently corrupted read, which seems worse. So the two probably need to move together.

      I haven't derived the correct rec_need_swap, that needs the swapped-record byte layout and I'd be guessing.
      Note also that this part is static reading of the source, not something I could test, since 0x80 can't currently be inserted.

      Attachments

        Issue Links

          Activity

            People

              sanja Oleksandr Byelkin
              shipjain Shipra Jain
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:

                Time Tracking

                  Estimated:
                  Original Estimate - 0d
                  0d
                  Remaining:
                  Remaining Estimate - 1d
                  1d
                  Logged:
                  Time Spent - Not Specified
                  Not Specified

                  Git Integration

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