Details
-
Bug
-
Status: Closed (View Workflow)
-
Blocker
-
Resolution: Fixed
-
None
-
Can result in hang or crash
-
Q3/2026 Replic. Development
Description
A crafted DECIMAL user variable event makes bin2decimal() allocate and
copy a size taken entirely from two attacker-supplied bytes.
bin2decimal() has no parameter telling it how many bytes are available
(strings/decimal.c:1424):
int bin_size= decimal_bin_size(precision, scale); |
sanity(to);
|
d_copy= (uchar*) my_alloca(bin_size);
|
memcpy(d_copy, from, bin_size); |
decimal_bin_size() guards its inputs with DBUG_ASSERT(precision > 0)
and DBUG_ASSERT(scale <= precision) only, so release builds proceed with
anything. my_alloca is real alloca in this build.
Precision and scale arrive as val[0] and val[1]. Three call sites
pass them through:
- the applier, User_var_log_event::do_apply_event
(sql/log_event_server.cc:4563), which checks only val_len < 3 - pack_info (sql/log_event_server.cc:4356), which checks nothing, not
even val_len >= 3 - mysqlbinlog (sql/log_event_client.cc:2489), which checks nothing
The constructor does bound val_len against the event
(sql/log_event.cc:3064), so val_len is honest, but nothing ties
decimal_bin_size(val[0], val[1]) to it. val_len of 3 with a precision
of 65 already asks for 30 bytes where 1 is available.
Two things enlarge that. val is const char *
(sql/log_event.h:3317), so a 0xFF precision byte is -1, and the
my_decimal(const uchar*, decimal_digits_t, decimal_digits_t) constructor
narrows it to uint16, giving 65535 and a 29127 byte read. Worse, when
scale exceeds precision, intg0x in decimal_bin_size goes negative and
dig2bytes[intg0x] reads out of the adjacent powers10 array, which
returns 1000000000. Precision 0xFC with scale 0xFF therefore yields a
bin_size of 1000029127: a 954 MB alloca and a 954 MB memcpy.
Release builds carry -fstack-protector but not
-fstack-clash-protection, and the SQL thread stack is 292 KB.
Note that the negative dig2bytes index does not depend on the signedness
of char, so it is reachable on every platform. Note also that
bin_size is never negative for any of the 65536 reachable byte pairs; the
out of bounds static array read returns a large positive instead.
--- a/sql/log_event_server.cc
|
+++ b/sql/log_event_server.cc
|
@@ User_var_log_event::write(), case DECIMAL_RESULT |
val_len= decimal_bin_size(buf2[0], buf2[1]) + 2;
|
+ /* Leave val_len honest and lie about the metadata. */ |
+ DBUG_EXECUTE_IF("corrupt_user_var_decimal_precision", |
+ buf2[0]= (char) 0xFC; buf2[1]= (char) 0xFF;); |
break; |
--source include/have_debug.inc
|
--source include/have_log_bin.inc
|
|
|
set @@session.debug_dbug= "+d,corrupt_user_var_decimal_precision";
|
set @a= 1.5;
|
create table t1 (a decimal(10,5));
|
insert into t1 values (@a);
|
set @@session.debug_dbug= "";
|
|
|
# pack_info() reaches bin2decimal() with precision 65532 and scale 65535, so
|
# decimal_bin_size() returns 1000029127 and the alloca moves the stack
|
# pointer roughly 954 MB.
|
--source include/show_binlog_events.inc
|