Bug #121113 `JSON_QUOTE()` metadata overflow truncates valid output
Submitted: 18 Aug 9:22 Modified: 19 Aug 14:23
Reporter: xingwang Verdant Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S3 (Non-critical)
Version:8.0.46, 8.4.11, 9.7.2 OS:Any
Assigned to: CPU Architecture:Any

[18 Aug 9:22] xingwang Verdant
Description:
## Summary

`JSON_QUOTE()` reserves up to six output characters per input character plus
two quotes. The reservation is calculated in 32-bit arithmetic. At the first
overflow boundary, a legal input descriptor is advertised as only four
characters even though direct evaluation produces 98 bytes.

`GROUP BY` returns a 16-byte prefix and non-strict CTAS persists only four
bytes. Strict CTAS rejects the value with error 1406. The server stays alive;
this report concerns silent result and persistent-data corruption.

## Affected version

Reproduced twice in the official optimized MySQL 8.4.10 image and twice in an
exact-source Debug+ASan build. The checked MySQL 8.4 and trunk revisions retain
the same formula.

## Reproduction

```sh
mysql --force -uroot -p < reproduce.sql
```

Only a one-byte row is stored. The selected runtime branch contains sixteen
control characters and is identical for control and trigger.

Expected observations:

- both direct results have length 98;
- the control remains complete through `GROUP BY` and CTAS;
- trigger `GROUP BY` returns only 16 bytes;
- trigger non-strict CTAS stores four bytes and emits a truncation warning;
- the final strict CTAS returns error 1406, while the server remains live.

## Real-world impact

JSON text can be silently cut into an invalid or semantically different value
when a query plan materializes the expression. CTAS can persist the damaged
value. The discrepancy can also affect `GROUP BY`, `DISTINCT`, derived tables,
views, temporary tables, and protocol metadata consumed by client libraries.

The trigger requires normal schema privileges only. It does not require a
large JSON document, large packet, malformed table, or administrative access.

How to repeat:
DROP DATABASE IF EXISTS h64;
CREATE DATABASE h64;
USE h64;
SET SESSION sql_mode='';

CREATE TABLE source_data(a LONGTEXT CHARACTER SET ascii);
INSERT INTO source_data VALUES ('A');

CREATE VIEW control_view AS
SELECT JSON_QUOTE(IF(a IS NOT NULL,
                     REPEAT(CONVERT(CHAR(1) USING ascii),16),
                     RIGHT(a,715827882))) AS j
FROM source_data;

CREATE VIEW trigger_view AS
SELECT JSON_QUOTE(IF(a IS NOT NULL,
                     REPEAT(CONVERT(CHAR(1) USING ascii),16),
                     RIGHT(a,715827883))) AS j
FROM source_data;

SELECT 'control_direct', HEX(j), LENGTH(j) FROM control_view;
SELECT 'trigger_direct', HEX(j), LENGTH(j) FROM trigger_view;
SELECT 'control_grouped', HEX(j), LENGTH(j) FROM control_view GROUP BY j;
SELECT 'trigger_grouped', HEX(j), LENGTH(j) FROM trigger_view GROUP BY j;

SELECT TABLE_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH,
       CHARACTER_OCTET_LENGTH
FROM information_schema.columns
WHERE TABLE_SCHEMA='h64'
  AND TABLE_NAME IN ('control_view','trigger_view')
ORDER BY TABLE_NAME;

CREATE TABLE control_materialized AS SELECT * FROM control_view;
CREATE TABLE trigger_materialized AS SELECT * FROM trigger_view;
SELECT 'control_stored', HEX(j), LENGTH(j) FROM control_materialized;
SELECT 'trigger_stored', HEX(j), LENGTH(j) FROM trigger_materialized;
SHOW WARNINGS;

SET SESSION sql_mode='STRICT_ALL_TABLES';
CREATE TABLE trigger_strict AS SELECT * FROM trigger_view;
[19 Aug 14:23] Chaithra Marsur Gopala Reddy
Hi xingwang Verdant,

Thank you for the test case. Verified as described.