Skip to content

Commit 4874b62

Browse files
committed
MDEV-38010: Fix trailing garbage in master.info causing data corruption
Modified init_intvar_from_file and init_floatvar_from_file to detect and reject trailing garbage in master.info numeric fields. Added a test case to verify the fix and ensure replication is correctly restored.
1 parent ed78f52 commit 4874b62

3 files changed

Lines changed: 140 additions & 26 deletions

File tree

mysql-test/main/mdev_38010.result

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
include/master-slave.inc
2+
[connection master]
3+
connection slave;
4+
include/stop_slave.inc
5+
call mtr.add_suppression("Error reading master configuration");
6+
call mtr.add_suppression("Failed to initialize the master info structure");
7+
call mtr.add_suppression("error connecting to master");
8+
include/rpl_restart_server.inc [server_number=2]
9+
connection slave;
10+
# Verifying that trailing garbage causes parsing to fail.
11+
# Rejection successful.
12+
# Restoring the slave to original replication state
13+
include/rpl_restart_server.inc [server_number=2]
14+
CHANGE MASTER TO MASTER_HOST='MASTER_HOST', MASTER_PORT=MASTER_PORT, MASTER_USER='MASTER_USER';
15+
include/start_slave.inc
16+
connection master;
17+
include/rpl_end.inc

mysql-test/main/mdev_38010.test

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#
2+
# MDEV-38010: Trailing garbage in master.info numeric fields leads to corruption.
3+
# This test verifis that the server strictly validates numeric lines in master.info.
4+
# If trailing non-whitespace characters are found, the line is rejected as corrupted.
5+
#
6+
7+
--source include/master-slave.inc
8+
--source include/have_binlog_format_mixed.inc
9+
10+
--connection slave
11+
12+
# Step1: Capture the current working replication state.
13+
# We need these to restore a clean environment after we've intentionally
14+
# "broken" the master.info file for the test.
15+
--let $master_host= query_get_value(SHOW SLAVE STATUS, Master_Host, 1)
16+
--let $master_port= query_get_value(SHOW SLAVE STATUS, Master_Port, 1)
17+
--let $master_user= query_get_value(SHOW SLAVE STATUS, Master_User, 1)
18+
19+
--source include/stop_slave.inc
20+
21+
# Suppress the err we expect to see in the log during this test.
22+
# The server will log these when it hits our new strict validation logic.
23+
call mtr.add_suppression("Error reading master configuration");
24+
call mtr.add_suppression("Failed to initialize the master info structure");
25+
call mtr.add_suppression("error connecting to master");
26+
27+
--let $MYSQLD_DATADIR= `select @@datadir`
28+
29+
# Step2: Simulate a corrupted master.info file.
30+
# We manually rewrite the file with "garbage" appended to numeric fields
31+
# (Port and Connect_Retry) to trigger the new validation check.
32+
--remove_file $MYSQLD_DATADIR/master.info
33+
--write_file $MYSQLD_DATADIR/master.info
34+
17
35+
master-bin.000001
36+
4
37+
127.0.0.1
38+
root
39+
40+
3306abcdefghijklmnopqrstuvwxyz&10
41+
60
42+
0
43+
0
44+
45+
46+
47+
0
48+
49+
60.000abcdefghijklm
50+
1 1
51+
EOF
52+
53+
# Step3: Restart the slave to force it to parse the corrupted file.
54+
--let $rpl_server_number= 2
55+
--source include/rpl_restart_server.inc
56+
57+
--connection slave
58+
59+
--echo # Verifying that trailing garbage causes parsing to fail.
60+
# If the fix works, the server should reject the bad port "3306abcdef..."
61+
# This may lead to Master_Port being unset or the slave failing to show up.
62+
--let $port= query_get_value(SHOW SLAVE STATUS, Master_Port, 1)
63+
64+
if ($port != 3306) {
65+
if ($port != No such row) {
66+
if ($port != "") {
67+
--echo Error: The garbage in master.info was accepted! Port evaluated to $port.
68+
--die "Test failed: Fix is not preventing garbage parsing."
69+
}
70+
}
71+
}
72+
--echo # Rejection successful.
73+
74+
# Step4: Cleanup and Restoration.
75+
# We remove the bad master.info and use CHANGE MASTER TO with our saved
76+
# parameters to restore the slave to its original state.
77+
--remove_file $MYSQLD_DATADIR/master.info
78+
79+
--echo # Restoring the slave to original replication state
80+
--let $rpl_server_number= 2
81+
--source include/rpl_restart_server.inc
82+
83+
--replace_result $master_port MASTER_PORT $master_host MASTER_HOST $master_user MASTER_USER
84+
--eval CHANGE MASTER TO MASTER_HOST='$master_host', MASTER_PORT=$master_port, MASTER_USER='$master_user'
85+
--source include/start_slave.inc
86+
87+
--connection master
88+
--source include/rpl_end.inc
89+

sql/slave.cc

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,27 +1699,33 @@ int init_strvar_from_file(char *var, int max_size, IO_CACHE *f,
16991699
DBUG_RETURN(1);
17001700
}
17011701

1702-
/*
1703-
when moving these functions to mysys, don't forget to
1704-
remove slave.cc from libmysqld/CMakeLists.txt
1705-
*/
1702+
1703+
17061704
int init_intvar_from_file(int* var, IO_CACHE* f, int default_val)
17071705
{
17081706
char buf[32];
17091707
DBUG_ENTER("init_intvar_from_file");
17101708

1709+
*var= default_val;
17111710

17121711
if (my_b_gets(f, buf, sizeof(buf)))
17131712
{
1714-
*var = atoi(buf);
1715-
DBUG_RETURN(0);
1716-
}
1717-
else if (default_val)
1718-
{
1719-
*var = default_val;
1720-
DBUG_RETURN(0);
1713+
char *endptr= buf + strlen(buf);
1714+
int error= 0;
1715+
longlong val= my_strtoll10(buf, &endptr, &error);
1716+
1717+
if (error && error != MY_ERRNO_EDOM)
1718+
DBUG_RETURN(1);
1719+
1720+
while (my_isspace(&my_charset_latin1, *endptr))
1721+
endptr++;
1722+
if (*endptr != '\0')
1723+
DBUG_RETURN(1);
1724+
1725+
*var= (int) val;
17211726
}
1722-
DBUG_RETURN(1);
1727+
1728+
DBUG_RETURN(0);
17231729
}
17241730

17251731
int init_ulonglongvar_from_file(ulonglong* var, IO_CACHE* f,
@@ -1729,17 +1735,14 @@ int init_ulonglongvar_from_file(ulonglong* var, IO_CACHE* f,
17291735
int error;
17301736
DBUG_ENTER("init_ulonglongvar_from_file");
17311737

1738+
*var = default_val;
17321739

17331740
if (my_b_gets(f, buf, sizeof(buf)))
17341741
{
17351742
*var = (ulonglong) my_strtoll10(buf, (char**) 0, &error);
17361743
DBUG_RETURN(0);
17371744
}
1738-
else if (default_val)
1739-
{
1740-
*var = default_val;
1741-
DBUG_RETURN(0);
1742-
}
1745+
17431746
DBUG_RETURN(1);
17441747
}
17451748

@@ -1748,20 +1751,25 @@ int init_floatvar_from_file(float* var, IO_CACHE* f, float default_val)
17481751
char buf[16];
17491752
DBUG_ENTER("init_floatvar_from_file");
17501753

1754+
*var= default_val;
17511755

17521756
if (my_b_gets(f, buf, sizeof(buf)))
17531757
{
1754-
if (sscanf(buf, "%f", var) != 1)
1758+
char *endptr= buf + strlen(buf);
1759+
int error= 0;
1760+
double val= my_strtod(buf, &endptr, &error);
1761+
if (error && error != MY_ERRNO_EDOM)
17551762
DBUG_RETURN(1);
1756-
else
1757-
DBUG_RETURN(0);
1758-
}
1759-
else if (default_val != 0.0)
1760-
{
1761-
*var = default_val;
1762-
DBUG_RETURN(0);
1763+
1764+
while (my_isspace(&my_charset_latin1, *endptr))
1765+
endptr++;
1766+
if (*endptr != '\0')
1767+
DBUG_RETURN(1);
1768+
1769+
*var= (float) val;
17631770
}
1764-
DBUG_RETURN(1);
1771+
1772+
DBUG_RETURN(0);
17651773
}
17661774

17671775

0 commit comments

Comments
 (0)