Skip to content

Commit eb5cd72

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 b0ee381 commit eb5cd72

3 files changed

Lines changed: 143 additions & 19 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: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,48 +1585,66 @@ int init_strvar_from_file(char *var, int max_size, IO_CACHE *f,
15851585
DBUG_RETURN(1);
15861586
}
15871587

1588-
/*
1589-
when moving these functions to mysys, don't forget to
1590-
remove slave.cc from libmysqld/CMakeLists.txt
1591-
*/
1588+
1589+
15921590
int init_intvar_from_file(int* var, IO_CACHE* f, int default_val)
15931591
{
15941592
char buf[32];
15951593
DBUG_ENTER("init_intvar_from_file");
15961594

1595+
*var= default_val;
15971596

15981597
if (my_b_gets(f, buf, sizeof(buf)))
15991598
{
1600-
*var = atoi(buf);
1601-
DBUG_RETURN(0);
1602-
}
1603-
else if (default_val)
1604-
{
1605-
*var = default_val;
1599+
char *endptr= buf + strlen(buf);
1600+
int error= 0;
1601+
longlong val= my_strtoll10(buf, &endptr, &error);
1602+
1603+
if (error && error != MY_ERRNO_EDOM)
1604+
DBUG_RETURN(1);
1605+
1606+
while (my_isspace(&my_charset_latin1, *endptr))
1607+
endptr++;
1608+
if (*endptr != '\0')
1609+
DBUG_RETURN(1);
1610+
1611+
if (error != MY_ERRNO_EDOM)
1612+
*var= (int) val;
1613+
1614+
16061615
DBUG_RETURN(0);
16071616
}
1608-
DBUG_RETURN(1);
1617+
DBUG_RETURN(0);
16091618
}
16101619

1620+
16111621
int init_floatvar_from_file(float* var, IO_CACHE* f, float default_val)
16121622
{
16131623
char buf[16];
16141624
DBUG_ENTER("init_floatvar_from_file");
16151625

1626+
*var= default_val;
16161627

16171628
if (my_b_gets(f, buf, sizeof(buf)))
16181629
{
1619-
if (sscanf(buf, "%f", var) != 1)
1630+
char *endptr= buf + strlen(buf);
1631+
int error= 0;
1632+
double val= my_strtod(buf, &endptr, &error);
1633+
if (error && error != MY_ERRNO_EDOM)
16201634
DBUG_RETURN(1);
1621-
else
1622-
DBUG_RETURN(0);
1623-
}
1624-
else if (default_val != 0.0)
1625-
{
1626-
*var = default_val;
1635+
1636+
while (my_isspace(&my_charset_latin1, *endptr))
1637+
endptr++;
1638+
if (*endptr != '\0')
1639+
DBUG_RETURN(1);
1640+
1641+
if (error != MY_ERRNO_EDOM)
1642+
*var= (float) val;
1643+
1644+
16271645
DBUG_RETURN(0);
16281646
}
1629-
DBUG_RETURN(1);
1647+
DBUG_RETURN(0);
16301648
}
16311649

16321650

0 commit comments

Comments
 (0)