Skip to content

Commit 83ba012

Browse files
ayush-jha123gkodinov
authored andcommitted
MDEV-38010: Master & relay log info files ignore trailing garbage in numeric lines
This patch fixes an issue where Int_IO_CACHE::from_chars stops parsing at the first invalid character but fails to consume the remainder of the line. This caused trailing garbage on a numeric field (like Master_Port) to be interpreted as the value for the subsequent field. The fix introduces a strict validation helper is_string_blank_or_empty which ensures that only whitespace or control characters follow the parsed numeric value. The init_*_from_file functions now zero-initialize variables, perform error checking immediately after string conversion, and safely reject files with trailing garbage. The test master_info_numeric_validation has been updated to use --move_file for robust backup and restoration of the master.info file.
1 parent e4bdee5 commit 83ba012

4 files changed

Lines changed: 165 additions & 33 deletions

File tree

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+
call mtr.add_suppression("Error reading master configuration");
5+
call mtr.add_suppression("Failed to initialize the master info structure");
6+
call mtr.add_suppression("error connecting to master");
7+
include/rpl_stop_server.inc [server_number=2]
8+
include/rpl_start_server.inc [server_number=2]
9+
connection slave;
10+
# Verifying that trailing garbage causes parsing to fail.
11+
# Rejection successful. The server refused to parse the garbage.
12+
# Restoring the slave to original replication state
13+
include/rpl_restart_server.inc [server_number=2]
14+
include/start_slave.inc
15+
connection master;
16+
include/rpl_end.inc
17+
# End of master_info_numeric_validation.test
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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_port= query_get_value(SHOW SLAVE STATUS, Master_Port, 1)
16+
17+
# Suppress the err we expect to see in the log during this test.
18+
# The server will log these when it hits our new strict validation logic.
19+
call mtr.add_suppression("Error reading master configuration");
20+
call mtr.add_suppression("Failed to initialize the master info structure");
21+
call mtr.add_suppression("error connecting to master");
22+
23+
--let $MYSQLD_DATADIR= `select @@datadir`
24+
25+
--let $rpl_server_number= 2
26+
--source include/rpl_stop_server.inc
27+
28+
# Step2: Simulate a corrupted master.info file.
29+
# The server is now fully stopped, preventing it from flushing its clean
30+
# in-memory state and overwriting our manually hacked file on shutdown.
31+
--move_file $MYSQLD_DATADIR/master.info $MYSQLD_DATADIR/master.info.backup
32+
--write_file $MYSQLD_DATADIR/master.info
33+
17
34+
master-bin.000001
35+
4
36+
127.0.0.1
37+
root
38+
39+
9999 60
40+
60
41+
0
42+
0
43+
44+
45+
46+
0
47+
48+
60.000abcdefghijklm
49+
1 1
50+
EOF
51+
52+
# Step3: Start the server to force it to parse the corrupted file.
53+
--let $rpl_server_number= 2
54+
--source include/rpl_start_server.inc
55+
56+
--connection slave
57+
58+
--echo # Verifying that trailing garbage causes parsing to fail.
59+
--let $port= query_get_value(SHOW SLAVE STATUS, Master_Port, 1)
60+
61+
if ($port == 9999) {
62+
--echo Error: The garbage in master.info was silently accepted! Port evaluated to $port.
63+
--die "Test failed: Fix is not preventing garbage parsing."
64+
}
65+
66+
--echo # Rejection successful. The server refused to parse the garbage.
67+
68+
# Step4: Cleanup and Restoration.
69+
# We remove the bad master.info and restore the original from the temp dir.
70+
--remove_file $MYSQLD_DATADIR/master.info
71+
--move_file $MYSQLD_DATADIR/master.info.backup $MYSQLD_DATADIR/master.info
72+
73+
--echo # Restoring the slave to original replication state
74+
--let $rpl_server_number= 2
75+
--source include/rpl_restart_server.inc
76+
77+
--source include/start_slave.inc
78+
79+
--connection master
80+
--source include/rpl_end.inc
81+
82+
83+
--echo # End of master_info_numeric_validation.test

sql/rpl_mi.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ Master_info::Master_info(LEX_CSTRING *connection_name_arg,
4949
{
5050
char *tmp;
5151
host[0] = 0; user[0] = 0; password[0] = 0;
52+
master_log_pos = 0;
53+
master_log_name[0] = 0;
5254
ssl_ca[0]= 0; ssl_capath[0]= 0; ssl_cert[0]= 0;
5355
ssl_cipher[0]= 0; ssl_key[0]= 0;
5456
ssl_crl[0]= 0; ssl_crlpath[0]= 0;

sql/slave.cc

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,69 +1699,99 @@ 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+
/* Check if numeric string parsing has trailing garbage */
1703+
static bool is_string_blank_or_empty(const char *endptr)
1704+
{
1705+
while (*endptr != '\0' &&
1706+
(my_isspace(system_charset_info, *endptr) ||
1707+
my_iscntrl(system_charset_info, *endptr)))
1708+
{
1709+
endptr++;
1710+
}
1711+
1712+
return *endptr != '\0';
1713+
}
1714+
17061715
int init_intvar_from_file(int* var, IO_CACHE* f, int default_val)
17071716
{
1708-
char buf[32];
1717+
char buf[32]= {0};
17091718
DBUG_ENTER("init_intvar_from_file");
17101719

1720+
*var= 0;
17111721

1712-
if (my_b_gets(f, buf, sizeof(buf)))
1713-
{
1714-
*var = atoi(buf);
1715-
DBUG_RETURN(0);
1716-
}
1717-
else if (default_val)
1722+
if (!my_b_gets(f, buf, sizeof(buf)))
17181723
{
1719-
*var = default_val;
1724+
if (!default_val)
1725+
DBUG_RETURN(1);
1726+
*var= default_val;
17201727
DBUG_RETURN(0);
17211728
}
1722-
DBUG_RETURN(1);
1729+
1730+
char *endptr= buf + strlen(buf);
1731+
int error= 0;
1732+
longlong val= my_strtoll10(buf, &endptr, &error);
1733+
1734+
if (error || is_string_blank_or_empty(endptr))
1735+
DBUG_RETURN(1);
1736+
1737+
if (val < 0 || val > UINT_MAX)
1738+
DBUG_RETURN(1);
1739+
1740+
*var= (int) val;
1741+
DBUG_RETURN(0);
17231742
}
17241743

17251744
int init_ulonglongvar_from_file(ulonglong* var, IO_CACHE* f,
17261745
ulonglong default_val)
17271746
{
1728-
char buf[MY_INT64_NUM_DECIMAL_DIGITS];
1729-
int error;
1747+
char buf[MY_INT64_NUM_DECIMAL_DIGITS]= {0};
1748+
int error= 0;
17301749
DBUG_ENTER("init_ulonglongvar_from_file");
17311750

1751+
*var= 0;
17321752

1733-
if (my_b_gets(f, buf, sizeof(buf)))
1734-
{
1735-
*var = (ulonglong) my_strtoll10(buf, (char**) 0, &error);
1736-
DBUG_RETURN(0);
1737-
}
1738-
else if (default_val)
1753+
if (!my_b_gets(f, buf, sizeof(buf)))
17391754
{
1740-
*var = default_val;
1755+
if (!default_val)
1756+
DBUG_RETURN(1);
1757+
*var= default_val;
17411758
DBUG_RETURN(0);
17421759
}
1743-
DBUG_RETURN(1);
1760+
1761+
char *endptr= buf + strlen(buf);
1762+
ulonglong val= (ulonglong) my_strtoll10(buf, &endptr, &error);
1763+
1764+
if (error || is_string_blank_or_empty(endptr))
1765+
DBUG_RETURN(1);
1766+
1767+
*var= val;
1768+
DBUG_RETURN(0);
17441769
}
17451770

17461771
int init_floatvar_from_file(float* var, IO_CACHE* f, float default_val)
17471772
{
1748-
char buf[16];
1773+
char buf[16]= {0};
17491774
DBUG_ENTER("init_floatvar_from_file");
17501775

1776+
*var= 0.0;
17511777

1752-
if (my_b_gets(f, buf, sizeof(buf)))
1778+
if (!my_b_gets(f, buf, sizeof(buf)))
17531779
{
1754-
if (sscanf(buf, "%f", var) != 1)
1780+
if (default_val == 0.0)
17551781
DBUG_RETURN(1);
1756-
else
1757-
DBUG_RETURN(0);
1758-
}
1759-
else if (default_val != 0.0)
1760-
{
1761-
*var = default_val;
1782+
*var= default_val;
17621783
DBUG_RETURN(0);
17631784
}
1764-
DBUG_RETURN(1);
1785+
1786+
char *endptr= buf + strlen(buf);
1787+
int error= 0;
1788+
double val= my_strtod(buf, &endptr, &error);
1789+
1790+
if (error || is_string_blank_or_empty(endptr))
1791+
DBUG_RETURN(1);
1792+
1793+
*var= (float) val;
1794+
DBUG_RETURN(0);
17651795
}
17661796

17671797

0 commit comments

Comments
 (0)