Bug #121541 Loadable function receives a corrupted string argument when the argument is CONCAT(...) of a CAST(... AS CHAR) (MySQL 8.
Submitted: 18 Oct 18:55
Reporter: Gabriel Shea Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: User-defined functions ( UDF ) Severity:S2 (Serious)
Version:8.4.0 through 8.4.11 OS:Any
Assigned to: CPU Architecture:Any
Tags: cast, concat, string_result, udf

[18 Oct 18:55] Gabriel Shea
Description:
A loadable function that takes its argument as STRING_RESULT is handed bytes
that another row has partly overwritten. In udf_handler::get_and_convert_string
(sql/item_func.cc), the argument buffer handed to the function shares its bytes
with the result of CONCAT, and the next row's CAST writes into the same memory,
so each row's number lands on the first bytes of its own result.

With a function f(str) that returns its argument unchanged:

  SELECT n, f(CONCAT('customer-', CAST(n AS CHAR))) AS got
  FROM (SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3) AS t;

the function receives '2ustomer-2' for the row that holds 'customer-2'.

Affected: 8.4.0 through 8.4.11. Not affected: 8.0.40, 8.0.44, 9.0, and
MariaDB 11.4 (9.0 copies the argument into its own buffer; the others do not
share the buffer).

How to repeat:
1. Build a loadable function f(str) that returns the string argument it
   received, unchanged. In its init function leave the argument type as
   STRING_RESULT (do not change args->arg_type[0]).
2. CREATE FUNCTION f RETURNS STRING SONAME '<the library>';
3. Run:
   SELECT n, f(CONCAT('customer-', CAST(n AS CHAR))) AS got
   FROM (SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3) AS t;

Expected: each row's got equals customer-<n>.
Observed on 8.4.0 through 8.4.11: the function is handed '2ustomer-2' for
the row holding 'customer-2'; the row's number overwrote the first byte of
its own string.

Workaround that shows the cause: declaring the argument as DECIMAL_RESULT in
the init function (args->arg_type[0] = DECIMAL_RESULT) makes the server pass
the value's own bytes and the function sees 'customer-2'. For text the
server reads the argument with the same call either way and never parses it
as a number, so the only difference is the buffer.

Suggested fix:
In udf_handler::get_and_convert_string, copy the String result into a buffer
the handler owns before handing it to the function, as 9.0 does, instead of
pointing the argument at the item's result buffer that CONCAT and the next
row's CAST share.