From c5a0ad056ccfdfa6fb07a6d7c20b0710570d3706 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Mon, 4 May 2026 14:10:35 +1000 Subject: [PATCH] MDEV-28374 UBSAN signed integer overflow PROCEDURE ANALYSE PROCEDURE ANALYSE returns a Std (Standard deviation) which involves the sum of squares, which can exceed the longlong datatypes. Adjust the sum_sqr and sum value to double to acocunt that its only used in a double context and precision isn't required. Reviewed by: Alexander Barkov --- mysql-test/main/func_analyse.result | 102 +++++++++++++++++++ mysql-test/main/func_analyse.test | 83 +++++++++++++++ mysql-test/suite/federated/federatedx.result | 17 +++- mysql-test/suite/federated/federatedx.test | 19 +++- sql/sql_analyse.cc | 39 +++---- sql/sql_analyse.h | 24 ++--- 6 files changed, 251 insertions(+), 33 deletions(-) diff --git a/mysql-test/main/func_analyse.result b/mysql-test/main/func_analyse.result index dc56cf5d7d751..8e46d6453185d 100644 --- a/mysql-test/main/func_analyse.result +++ b/mysql-test/main/func_analyse.result @@ -257,3 +257,105 @@ DROP TABLE t; # # End of 10.5 tests # +# +# MDEV-28374 UBSAN signed integer overflow PROCEDURE ANALYSE +# +SET sql_mode=''; +CREATE TABLE t (a BIGINT); +INSERT INTO t VALUES ('10000000000000'),('10000000000000'); +SELECT * FROM t PROCEDURE ANALYSE(); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t.a 10000000000000 10000000000000 14 14 0 0 10000000000000.0000 0.0000 ENUM('10000000000000') NOT NULL +DROP TABLE t; +CREATE TABLE t (a BIGINT UNSIGNED); +INSERT INTO t VALUES ('18446744073709551615'),('18446744073709551615'); +SELECT * FROM t PROCEDURE ANALYSE(); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t.a 18446744073709551615 18446744073709551615 20 20 0 0 18446744073709552000.0000 0.0000 ENUM('18446744073709551615') NOT NULL +DROP TABLE t; +CREATE TABLE t (a BIGINT,KEY a USING BTREE (a)); +INSERT INTO t VALUES (0),('1'),('1'),('1'),('1'),('00010101'),('99991231'),('00101000000'),('691231000000'),('700101000000'),('991231235959'),('10000101000000'),('99991231235959'),('20030100000000'),('20030000000000'); +SELECT * FROM t PROCEDURE ANALYSE(); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t.a 0 99991231235959 1 14 1 0 10162279764883.6000 24971351159406.2300 ENUM('0','1','10101','99991231','101000000','691231000000','700101000000','991231235959','10000101000000','20030000000000','20030100000000','99991231235959') NOT NULL +DROP TABLE t; +CREATE TABLE t (c BIGINT,c2 DATE,c3 BLOB,KEY(c)) ENGINE=InnoDB; +INSERT INTO t VALUES (-1.e+308,1,1); +Warnings: +Warning 1264 Out of range value for column 'c' at row 1 +Warning 1265 Data truncated for column 'c2' at row 1 +SELECT * FROM t PROCEDURE ANALYSE(); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t.c -9223372036854775808 -9223372036854775808 20 20 0 0 -9223372036854776000.0000 0.0000 ENUM('-9223372036854775808') NOT NULL +test.t.c2 0000-00-00 0000-00-00 10 10 0 0 10.0000 NULL ENUM('0000-00-00') NOT NULL +test.t.c3 1 1 1 1 0 0 1.0000 NULL ENUM('1') NOT NULL +DROP TABLE t; +CREATE TABLE t (c INT) ENGINE=InnoDB UNION=(t,t2) INSERT_METHOD=LAST; +CREATE TABLE t3 LIKE t; +INSERT INTO t VALUES (1),(2),(-685113344),(0); +INSERT INTO t3 SELECT * FROM t; +INSERT INTO t SELECT * FROM t; +INSERT INTO t SELECT * FROM t; +INSERT INTO t3 SELECT * FROM t; +SELECT * FROM t3 PROCEDURE ANALYSE(); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t3.c -685113344 2 1 10 5 0 -171278335.2500 296662780.6209 ENUM('-685113344','0','1','2') NOT NULL +DROP TABLE t, t3; +CREATE TABLE t (c INT) ENGINE=InnoDB UNION=(t,t2) INSERT_METHOD=LAST; +INSERT INTO t VALUES (1); +INSERT INTO t VALUES (0xA9BD); +INSERT INTO t (c) VALUES (ADDTIME(NOW(),1)); +Warnings: +Warning 1264 Out of range value for column 'c' at row 1 +CREATE TABLE c AS SELECT 1 A; +SELECT * FROM t a,c,t b PROCEDURE ANALYSE(); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.a.c 1 2147483647 1 10 0 0 715842367.0000 1012323257.4700 ENUM('1','43453','2147483647') NOT NULL +test.c.A 1 1 1 1 0 0 1.0000 0.0000 ENUM('1') NOT NULL +test.b.c 1 2147483647 1 10 0 0 715842367.0000 1012323257.4700 ENUM('1','43453','2147483647') NOT NULL +DROP TABLE t, c; +CREATE TABLE t (c BIGINT); +INSERT INTO t VALUES (1000000000000000); +SELECT * FROM t PROCEDURE ANALYSE(0,0); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t.c 1000000000000000 1000000000000000 16 16 0 0 1000000000000000.0000 0.0000 BIGINT(16) UNSIGNED NOT NULL +DROP TABLE t; +CREATE SEQUENCE t CACHE=1 MAXVALUE=0 INCREMENT=-1; +SELECT * FROM t PROCEDURE ANALYSE(); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t.next_not_cached_value 0 0 1 1 1 0 0.0000 0.0000 ENUM('0') NOT NULL +test.t.minimum_value -9223372036854775807 -9223372036854775807 20 20 0 0 -9223372036854776000.0000 0.0000 ENUM('-9223372036854775807') NOT NULL +test.t.maximum_value 0 0 1 1 1 0 0.0000 0.0000 ENUM('0') NOT NULL +test.t.start_value 0 0 1 1 1 0 0.0000 0.0000 ENUM('0') NOT NULL +test.t.increment -1 -1 2 2 0 0 -1.0000 0.0000 ENUM('-1') NOT NULL +test.t.cache_size 1 1 1 1 0 0 1.0000 0.0000 ENUM('1') NOT NULL +test.t.cycle_option 0 0 1 1 1 0 0.0000 0.0000 ENUM('0') NOT NULL +test.t.cycle_count 0 0 1 1 1 0 0.0000 0.0000 ENUM('0') NOT NULL +DROP SEQUENCE t; +CREATE SEQUENCE t CACHE+1; +SELECT * FROM t PROCEDURE ANALYSE(); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t.next_not_cached_value 1 1 1 1 0 0 1.0000 0.0000 ENUM('1') NOT NULL +test.t.minimum_value 1 1 1 1 0 0 1.0000 0.0000 ENUM('1') NOT NULL +test.t.maximum_value 9223372036854775806 9223372036854775806 19 19 0 0 9223372036854776000.0000 0.0000 ENUM('9223372036854775806') NOT NULL +test.t.start_value 1 1 1 1 0 0 1.0000 0.0000 ENUM('1') NOT NULL +test.t.increment 1 1 1 1 0 0 1.0000 0.0000 ENUM('1') NOT NULL +test.t.cache_size 1 1 1 1 0 0 1.0000 0.0000 ENUM('1') NOT NULL +test.t.cycle_option 0 0 1 1 1 0 0.0000 0.0000 ENUM('0') NOT NULL +test.t.cycle_count 0 0 1 1 1 0 0.0000 0.0000 ENUM('0') NOT NULL +DROP SEQUENCE t; +CREATE TABLE t (c INT) ENGINE=InnoDB; +INSERT INTO t VALUES (0x31313131),(0x32323232); +INSERT INTO t VALUES (0x31313131),(0x32323232); +SELECT * FROM t LIMIT 1 PROCEDURE ANALYSE(); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t.c 825307441 842150450 9 9 0 0 833728945.5000 8421504.5000 ENUM('825307441','842150450') NOT NULL +DROP TABLE t; +CREATE TABLE t (f BIGINT) ENGINE=InnoDB; +INSERT INTO t VALUES (1),(+1),(1),(1),(+1),(1),(1),(1),(1),(+1),(1),(1),(1),(20030000000000); +SELECT * FROM t PROCEDURE ANALYSE(); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t.f 1 20030000000000 1 14 0 0 1430714285715.2144 5158513717681.4360 ENUM('1','20030000000000') NOT NULL +DROP TABLE t; +SET sql_mode=DEFAULT; +# End of 10.11 tests diff --git a/mysql-test/main/func_analyse.test b/mysql-test/main/func_analyse.test index f5c027a5283d6..c3d5eaa17eaf7 100644 --- a/mysql-test/main/func_analyse.test +++ b/mysql-test/main/func_analyse.test @@ -2,6 +2,7 @@ # Test of procedure analyse # -- source include/have_innodb.inc +--source include/have_sequence.inc --disable_warnings drop table if exists t1,t2; @@ -267,3 +268,85 @@ DROP TABLE t; --echo # --echo # End of 10.5 tests --echo # + +--echo # +--echo # MDEV-28374 UBSAN signed integer overflow PROCEDURE ANALYSE +--echo # + +SET sql_mode=''; + +CREATE TABLE t (a BIGINT); +INSERT INTO t VALUES ('10000000000000'),('10000000000000'); +SELECT * FROM t PROCEDURE ANALYSE(); + +DROP TABLE t; + +CREATE TABLE t (a BIGINT UNSIGNED); +INSERT INTO t VALUES ('18446744073709551615'),('18446744073709551615'); +SELECT * FROM t PROCEDURE ANALYSE(); + +DROP TABLE t; + +CREATE TABLE t (a BIGINT,KEY a USING BTREE (a)); +INSERT INTO t VALUES (0),('1'),('1'),('1'),('1'),('00010101'),('99991231'),('00101000000'),('691231000000'),('700101000000'),('991231235959'),('10000101000000'),('99991231235959'),('20030100000000'),('20030000000000'); +SELECT * FROM t PROCEDURE ANALYSE(); + +DROP TABLE t; + +CREATE TABLE t (c BIGINT,c2 DATE,c3 BLOB,KEY(c)) ENGINE=InnoDB; +INSERT INTO t VALUES (-1.e+308,1,1); +SELECT * FROM t PROCEDURE ANALYSE(); + +DROP TABLE t; + +CREATE TABLE t (c INT) ENGINE=InnoDB UNION=(t,t2) INSERT_METHOD=LAST; +CREATE TABLE t3 LIKE t; +INSERT INTO t VALUES (1),(2),(-685113344),(0); +INSERT INTO t3 SELECT * FROM t; +INSERT INTO t SELECT * FROM t; +INSERT INTO t SELECT * FROM t; +INSERT INTO t3 SELECT * FROM t; +SELECT * FROM t3 PROCEDURE ANALYSE(); + +DROP TABLE t, t3; + +CREATE TABLE t (c INT) ENGINE=InnoDB UNION=(t,t2) INSERT_METHOD=LAST; +INSERT INTO t VALUES (1); +INSERT INTO t VALUES (0xA9BD); +INSERT INTO t (c) VALUES (ADDTIME(NOW(),1)); +CREATE TABLE c AS SELECT 1 A; +SELECT * FROM t a,c,t b PROCEDURE ANALYSE(); + +DROP TABLE t, c; + +CREATE TABLE t (c BIGINT); +INSERT INTO t VALUES (1000000000000000); +SELECT * FROM t PROCEDURE ANALYSE(0,0); + +DROP TABLE t; + +CREATE SEQUENCE t CACHE=1 MAXVALUE=0 INCREMENT=-1; +SELECT * FROM t PROCEDURE ANALYSE(); + +DROP SEQUENCE t; + +CREATE SEQUENCE t CACHE+1; +SELECT * FROM t PROCEDURE ANALYSE(); + +DROP SEQUENCE t; + +CREATE TABLE t (c INT) ENGINE=InnoDB; +INSERT INTO t VALUES (0x31313131),(0x32323232); +INSERT INTO t VALUES (0x31313131),(0x32323232); +SELECT * FROM t LIMIT 1 PROCEDURE ANALYSE(); + +DROP TABLE t; + +CREATE TABLE t (f BIGINT) ENGINE=InnoDB; +INSERT INTO t VALUES (1),(+1),(1),(1),(+1),(1),(1),(1),(1),(+1),(1),(1),(1),(20030000000000); +SELECT * FROM t PROCEDURE ANALYSE(); + +DROP TABLE t; +SET sql_mode=DEFAULT; + +--echo # End of 10.11 tests diff --git a/mysql-test/suite/federated/federatedx.result b/mysql-test/suite/federated/federatedx.result index 14d3db9dfcd25..4dbac2f5bc140 100644 --- a/mysql-test/suite/federated/federatedx.result +++ b/mysql-test/suite/federated/federatedx.result @@ -2356,11 +2356,26 @@ pk 3 DROP TABLE t2_fed, t1, t2; set @@optimizer_switch=@save_optimizer_switch; -DROP SERVER s; # End of 10.5 tests +# +# MDEV-28374 UBSAN signed integer overflow PROCEDURE ANALYSE +# +CREATE TABLE t (a BIGINT); +INSERT INTO t VALUES ('-9223372036854775808'); +INSERT INTO t SELECT * FROM t; +SELECT * FROM t PROCEDURE ANALYSE(2); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t.a -9223372036854775808 -9223372036854775808 20 20 0 0 -9223372036854776000.0000 0.0000 BIGINT(20) NOT NULL +CREATE TABLE t_fed ENGINE=FEDERATED CONNECTION='s/t'; +SELECT * FROM t_fed PROCEDURE ANALYSE(2); +Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype +test.t_fed.a -9223372036854775808 -9223372036854775808 20 20 0 0 -9223372036854776000.0000 0.0000 BIGINT(20) NOT NULL +DROP TABLE t, t_fed; +DROP SERVER s; connection master; DROP TABLE IF EXISTS federated.t1; DROP DATABASE IF EXISTS federated; connection slave; DROP TABLE IF EXISTS federated.t1; DROP DATABASE IF EXISTS federated; +# End of 10.11 tests diff --git a/mysql-test/suite/federated/federatedx.test b/mysql-test/suite/federated/federatedx.test index bc75edc5a408a..08cf140e99550 100644 --- a/mysql-test/suite/federated/federatedx.test +++ b/mysql-test/suite/federated/federatedx.test @@ -2086,8 +2086,23 @@ SELECT * FROM t2_fed WHERE pk IN ( SELECT a FROM t1 ); DROP TABLE t2_fed, t1, t2; set @@optimizer_switch=@save_optimizer_switch; -DROP SERVER s; - --echo # End of 10.5 tests +--echo # +--echo # MDEV-28374 UBSAN signed integer overflow PROCEDURE ANALYSE +--echo # + +CREATE TABLE t (a BIGINT); +INSERT INTO t VALUES ('-9223372036854775808'); +INSERT INTO t SELECT * FROM t; +SELECT * FROM t PROCEDURE ANALYSE(2); + +CREATE TABLE t_fed ENGINE=FEDERATED CONNECTION='s/t'; +SELECT * FROM t_fed PROCEDURE ANALYSE(2); + +DROP TABLE t, t_fed; + +DROP SERVER s; source include/federated_cleanup.inc; + +--echo # End of 10.11 tests diff --git a/sql/sql_analyse.cc b/sql/sql_analyse.cc index 0da8f2ca02a87..fc844206cb7a5 100644 --- a/sql/sql_analyse.cc +++ b/sql/sql_analyse.cc @@ -555,12 +555,12 @@ void field_decimal::add() } } - void field_longlong::add() { char buff[MAX_FIELD_WIDTH]; - longlong num = item->val_int(); - uint length = (uint) (longlong10_to_str(num, buff, -10) - buff); + longlong numlong = item->val_int(); + double num = (double) numlong; + uint length = (uint) (longlong10_to_str(numlong, buff, -10) - buff); TREE_ELEMENT *element; if (item->null_value) @@ -568,12 +568,12 @@ void field_longlong::add() nulls++; return; } - if (num == 0) + if (num == 0.0) empty++; if (room_in_tree) { - if (!(element = tree_insert(&tree, (void*) &num, 0, tree.custom_arg))) + if (!(element = tree_insert(&tree, (void*) &numlong, 0, tree.custom_arg))) { room_in_tree = 0; // Remove tree, out of RAM ? delete_tree(&tree, 0); @@ -592,7 +592,8 @@ void field_longlong::add() if (!found) { found = 1; - min_arg = max_arg = sum = num; + min_arg = max_arg = numlong; + sum = num; sum_sqr = num * num; min_length = max_length = length; } @@ -604,10 +605,10 @@ void field_longlong::add() min_length = length; if (length > max_length) max_length = length; - if (compare_longlong(&num, &min_arg) < 0) - min_arg = num; - if (compare_longlong(&num, &max_arg) > 0) - max_arg = num; + if (compare_longlong(&numlong, &min_arg) < 0) + min_arg = numlong; + if (compare_longlong(&numlong, &max_arg) > 0) + max_arg = numlong; } } // field_longlong::add @@ -615,8 +616,9 @@ void field_longlong::add() void field_ulonglong::add() { char buff[MAX_FIELD_WIDTH]; - longlong num = item->val_int(); - uint length = (uint) (longlong10_to_str(num, buff, 10) - buff); + ulonglong numlong = item->val_int(); + double num= (double) numlong; + uint length = (uint) (longlong10_to_str(numlong, buff, 10) - buff); TREE_ELEMENT *element; if (item->null_value) @@ -629,7 +631,7 @@ void field_ulonglong::add() if (room_in_tree) { - if (!(element = tree_insert(&tree, (void*) &num, 0, tree.custom_arg))) + if (!(element = tree_insert(&tree, (void*) &numlong, 0, tree.custom_arg))) { room_in_tree = 0; // Remove tree, out of RAM ? delete_tree(&tree, 0); @@ -648,7 +650,8 @@ void field_ulonglong::add() if (!found) { found = 1; - min_arg = max_arg = sum = num; + min_arg = max_arg = numlong; + sum = num; sum_sqr = num * num; min_length = max_length = length; } @@ -660,10 +663,10 @@ void field_ulonglong::add() min_length = length; if (length > max_length) max_length = length; - if (compare_ulonglong((ulonglong*) &num, &min_arg) < 0) - min_arg = num; - if (compare_ulonglong((ulonglong*) &num, &max_arg) > 0) - max_arg = num; + if (compare_ulonglong(&numlong, &min_arg) < 0) + min_arg = numlong; + if (compare_ulonglong(&numlong, &max_arg) > 0) + max_arg = numlong; } } // field_ulonglong::add diff --git a/sql/sql_analyse.h b/sql/sql_analyse.h index 00d9500bbd93b..4b111ff42f13f 100644 --- a/sql/sql_analyse.h +++ b/sql/sql_analyse.h @@ -234,11 +234,11 @@ int collect_longlong(void *element, element_count count, void *info); class field_longlong: public field_info { longlong min_arg, max_arg; - longlong sum, sum_sqr; + double sum, sum_sqr; public: field_longlong(Item* a, analyse* b) :field_info(a,b), - min_arg(0), max_arg(0), sum(0), sum_sqr(0) + min_arg(0), max_arg(0), sum(0), sum_sqr(0.0) { init_tree(&tree, 0, 0, sizeof(longlong), compare_longlong2, NULL, NULL, MYF(MY_THREAD_SPECIFIC)); @@ -263,9 +263,9 @@ class field_longlong: public field_info s->set_real((double) 0.0, 1,my_thd_charset); else { - double tmp2 = ((sum_sqr - sum * sum / (tmp - nulls)) / - (tmp - nulls)); - s->set_real(((double) tmp2 <= 0.0 ? 0.0 : sqrt(tmp2)), DEC_IN_AVG,my_thd_charset); + double tmp2 = (sum_sqr - sum * sum / (tmp - nulls)) / + (tmp - nulls); + s->set_real(tmp2 <= 0.0 ? 0.0 : sqrt(tmp2), DEC_IN_AVG,my_thd_charset); } return s; } @@ -280,7 +280,7 @@ int collect_ulonglong(void *element, element_count count, void *info); class field_ulonglong: public field_info { ulonglong min_arg, max_arg; - ulonglong sum, sum_sqr; + double sum, sum_sqr; public: field_ulonglong(Item* a, analyse * b) :field_info(a,b), @@ -298,8 +298,8 @@ class field_ulonglong: public field_info if (!(rows - nulls)) s->set_real((double) 0.0, 1,my_thd_charset); else - s->set_real((ulonglong2double(sum) / ulonglong2double(rows - nulls)), - DEC_IN_AVG,my_thd_charset); + s->set_real(sum / ulonglong2double(rows - nulls), + DEC_IN_AVG, my_thd_charset); return s; } String *std(String *s, ha_rows rows) override @@ -309,10 +309,10 @@ class field_ulonglong: public field_info s->set_real((double) 0.0, 1,my_thd_charset); else { - double tmp2 = ((ulonglong2double(sum_sqr) - - ulonglong2double(sum * sum) / (tmp - nulls)) / - (tmp - nulls)); - s->set_real(((double) tmp2 <= 0.0 ? 0.0 : sqrt(tmp2)), DEC_IN_AVG,my_thd_charset); + double tmp2 = (sum_sqr - + (sum * sum) / (tmp - nulls)) / + (tmp - nulls); + s->set_real(tmp2 <= 0.0 ? 0.0 : sqrt(tmp2), DEC_IN_AVG,my_thd_charset); } return s; }