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;
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;