Skip to content

Commit caa35f8

Browse files
committed
MDEV-16372 ER_BASE64_DECODE_ERROR upon replaying binary log via mysqlbinlog --verbose
(This commit is for 10.3 and upper branches) In case of a pattern of non-STMT_END-marked Rows-log-event (A) followed by a STMT_END marked one (B) mysqlbinlog mixes up the base64 encoded rows events with their pseudo sql representation produced by the verbose option: BINLOG ' base64 encoded data for A ### verbose section for A base64 encoded data for B ### verbose section for B '/*!*/; In effect the produced BINLOG '...' query is not valid and is rejected with the error. Examples of this way malformed BINLOG could have been found in binlog_row_annotate.result that gets corrected with the patch. The issue is fixed with introduction an auxiliary IO_CACHE to hold on the verbose comments until the terminal STMT_END event is found. The new cache is emptied out after two pre-existing ones are done at that time. The correctly produced output now for the above case is as the following: BINLOG ' base64 encoded data for A base64 encoded data for B '/*!*/; ### verbose section for A ### verbose section for B Thanks to Alexey Midenkov for the problem recognition and attempt to tackle, and to Venkatesh Duggirala who produced a patch for the upstream whose idea is exploited here, as well as to MDEV-23077 reporter LukeXwang who also contributed a piece of a patch aiming at this issue.
1 parent 6a04228 commit caa35f8

10 files changed

+88
-51
lines changed

client/mysqlbinlog.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -969,8 +969,12 @@ static bool print_row_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev,
969969
my_b_printf(body_cache, "'%s\n", print_event_info->delimiter);
970970

971971
// flush cache
972-
if ((copy_event_cache_to_file_and_reinit(&print_event_info->head_cache, result_file) ||
973-
copy_event_cache_to_file_and_reinit(&print_event_info->body_cache, result_file)))
972+
if ((copy_event_cache_to_file_and_reinit(&print_event_info->head_cache,
973+
result_file) ||
974+
copy_event_cache_to_file_and_reinit(&print_event_info->body_cache,
975+
result_file) ||
976+
copy_event_cache_to_file_and_reinit(&print_event_info->tail_cache,
977+
result_file)))
974978
return 1;
975979
}
976980
}

mysql-test/main/mysqlbinlog_row_minimal.result

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,24 @@ ROLLBACK /* added by mysqlbinlog */;
327327
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
328328
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
329329
DROP TABLE t1,t2;
330+
#
331+
# MDEV-16372 ER_BASE64_DECODE_ERROR upon replaying binary log with system table
332+
#
333+
FLUSH BINARY LOGS;
334+
CREATE TABLE t1 (pk INT PRIMARY KEY);
335+
INSERT INTO t1 VALUES (1);
336+
INSERT INTO t1 VALUES (1), (2) ON DUPLICATE KEY UPDATE pk= pk + 10;
337+
FLUSH BINARY LOGS;
338+
Proof: two subsequent patterns must be found
339+
FOUND 1 /### UPDATE `test`.`t1`/ in mysqlbinlog.sql
340+
FOUND 2 /### INSERT INTO `test`.`t1`/ in mysqlbinlog.sql
341+
DROP TABLE t1;
342+
SELECT * FROM t1;
343+
pk
344+
2
345+
11
346+
# Cleanup
347+
DROP TABLE t1;
330348
CREATE TABLE `t1` (
331349
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
332350
`is_deleted` BIT(1) DEFAULT b'0',
@@ -351,34 +369,34 @@ FLUSH BINARY LOGS;
351369
/*!40019 SET @@session.max_insert_delayed_threads=0*/;
352370
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
353371
DELIMITER /*!*/;
354-
# at 387
355-
#<date> server id 1 end_log_pos 429 CRC32 XXX GTID 0-1-16
372+
# at POS
373+
#<date> server id 1 end_log_pos END_LOG_POS CRC32 XXX GTID D-S-N
356374
/*!100101 SET @@session.skip_parallel_replication=0*//*!*/;
357375
/*!100001 SET @@session.gtid_domain_id=0*//*!*/;
358376
/*!100001 SET @@session.server_id=1*//*!*/;
359-
/*!100001 SET @@session.gtid_seq_no=16*//*!*/;
377+
/*!100001 SET @@session.gtid_seq_no=21*//*!*/;
360378
START TRANSACTION
361379
/*!*/;
362-
# at 429
363-
# at 543
364-
#<date> server id 1 end_log_pos 543 CRC32 XXX Annotate_rows:
380+
# at POS
381+
# at POS
382+
#<date> server id 1 end_log_pos END_LOG_POS CRC32 XXX Annotate_rows:
365383
#Q> UPDATE t1 t1 INNER JOIN t2 t2 ON t1.ref_id = t2.id
366384
#Q> SET t1.is_deleted = TRUE
367385
#Q> WHERE t1.id =
368-
#<date> server id 1 end_log_pos 594 CRC32 XXX Table_map: `test`.`t1` mapped to number 35
369-
# at 594
370-
#<date> server id 1 end_log_pos 643 CRC32 XXX Update_rows: table id 35 flags: STMT_END_F
386+
#<date> server id 1 end_log_pos END_LOG_POS CRC32 XXX Table_map: `test`.`t1` mapped to number TID
387+
# at POS
388+
#<date> server id 1 end_log_pos END_LOG_POS CRC32 XXX Update_rows: table id TID flags: STMT_END_F
371389
### UPDATE `test`.`t1`
372390
### WHERE
373391
### @1=1 /* LONGINT meta=0 nullable=0 is_null=0 */
374392
### SET
375393
### @2=b'1' /* BIT(1) meta=1 nullable=1 is_null=0 */
376394
### @3=X /* TIMESTAMP(0) meta=0 nullable=0 is_null=0 */
377395
# Number of rows: 1
378-
# at 643
379-
#<date> server id 1 end_log_pos 725 CRC32 XXX Query thread_id=5 exec_time=x error_code=0
396+
# at POS
397+
#<date> server id 1 end_log_pos END_LOG_POS CRC32 XXX Query thread_id=TID exec_time=x error_code=0
380398
SET TIMESTAMP=X/*!*/;
381-
SET @@session.pseudo_thread_id=5/*!*/;
399+
SET @@session.pseudo_thread_id=TID/*!*/;
382400
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1/*!*/;
383401
SET @@session.sql_mode=#/*!*/;
384402
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
@@ -388,8 +406,6 @@ SET @@session.lc_time_names=0/*!*/;
388406
SET @@session.collation_database=DEFAULT/*!*/;
389407
COMMIT
390408
/*!*/;
391-
# at 725
392-
#<date> server id 1 end_log_pos 773 CRC32 XXX Rotate to master-bin.000004 pos: 4
393409
DELIMITER ;
394410
# End of log file
395411
ROLLBACK /* added by mysqlbinlog */;

mysql-test/main/mysqlbinlog_row_minimal.test

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,33 @@ FLUSH BINARY LOGS;
3333

3434
DROP TABLE t1,t2;
3535

36+
--echo #
37+
--echo # MDEV-16372 ER_BASE64_DECODE_ERROR upon replaying binary log with system table
38+
--echo #
39+
FLUSH BINARY LOGS;
40+
CREATE TABLE t1 (pk INT PRIMARY KEY);
41+
INSERT INTO t1 VALUES (1);
42+
INSERT INTO t1 VALUES (1), (2) ON DUPLICATE KEY UPDATE pk= pk + 10;
43+
44+
--let $binlog = query_get_value(SHOW MASTER STATUS, File, 1)
45+
46+
FLUSH BINARY LOGS;
47+
--exec $MYSQL_BINLOG --verbose $datadir/$binlog > $MYSQLTEST_VARDIR/tmp/mysqlbinlog.sql
48+
--echo Proof: two subsequent patterns must be found
49+
--let SEARCH_PATTERN= ### UPDATE `test`.`t1`
50+
--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mysqlbinlog.sql
51+
--source include/search_pattern_in_file.inc
52+
--let SEARCH_PATTERN= ### INSERT INTO `test`.`t1`
53+
--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mysqlbinlog.sql
54+
--source include/search_pattern_in_file.inc
55+
56+
DROP TABLE t1;
57+
--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/mysqlbinlog.sql
58+
SELECT * FROM t1;
59+
60+
--echo # Cleanup
61+
DROP TABLE t1;
62+
--remove_file $MYSQLTEST_VARDIR/tmp/mysqlbinlog.sql
3663
#
3764
# MDEV-14605 ON UPDATE CURRENT_TIMESTAMP fields by multi-table UPDATE are not logged with binlog_row_image=MINIMAL
3865
#
@@ -64,11 +91,12 @@ UPDATE t1 t1 INNER JOIN t2 t2 ON t1.ref_id = t2.id
6491
WHERE t1.id = 1;
6592

6693
--let $binlog = query_get_value(SHOW MASTER STATUS, File, 1)
94+
--let $binlog_end= query_get_value(SHOW MASTER STATUS, Position, 1)
6795

6896
FLUSH BINARY LOGS;
6997
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
70-
--replace_regex /\d{6} *\d*:\d\d:\d\d/<date>/ /SET TIMESTAMP=\d*/SET TIMESTAMP=X/ /exec_time=\d*/exec_time=x/ /@3=\d*/@3=X/ /CRC32 0x[0-9a-f]+/CRC32 XXX/ /@@session.sql_mode=\d+/@@session.sql_mode=#/ /collation_server=\d+/collation_server=#/
71-
--exec $MYSQL_BINLOG --verbose --verbose --base64-output=DECODE-ROWS $datadir/$binlog --start-position=$binlog_pos
98+
--replace_regex /table id \d*/table id TID/ /mapped to number \d*/mapped to number TID/ /at \d*/at POS/ /end_log_pos \d*/end_log_pos END_LOG_POS/ /GTID \d*-\d*-\d*/GTID D-S-N/ /\d{6} *\d*:\d\d:\d\d/<date>/ /SET TIMESTAMP=\d*/SET TIMESTAMP=X/ /exec_time=\d*/exec_time=x/ /@3=\d*/@3=X/ /CRC32 0x[0-9a-f]+/CRC32 XXX/ /@@session.sql_mode=\d+/@@session.sql_mode=#/ /collation_server=\d+/collation_server=#/ /thread_id=\d*/thread_id=TID/
99+
--exec $MYSQL_BINLOG --verbose --verbose --base64-output=DECODE-ROWS $datadir/$binlog --start-position=$binlog_pos --stop-position=$binlog_end
72100

73101
DROP TABLE t1,t2;
74102

mysql-test/suite/binlog/r/binlog_mysqlbinlog_row.result

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5356,7 +5356,5 @@ START TRANSACTION
53565356
#010909 4:46:40 server id 1 end_log_pos # CRC32 XXX Table_map: `test`.`t1dec102` mapped to number #
53575357
# at #
53585358
#010909 4:46:40 server id 1 end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
5359-
### INSERT INTO `test`.`t1dec102`
5360-
### SET
5361-
### @1=
5359+
53625360
Error: Found Old DECIMAL (mysql-4.1 or earlier). Not enough metadata to display the value.

mysql-test/suite/binlog/r/binlog_row_annotate.result

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,6 @@ START TRANSACTION
449449
### DELETE FROM `test1`.`t1`
450450
### WHERE
451451
### @1=3 /* INT meta=0 nullable=1 is_null=0 */
452-
'/*!*/;
453452
# at #
454453
#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0
455454
SET TIMESTAMP=1000000000/*!*/;
@@ -1073,7 +1072,6 @@ START TRANSACTION
10731072
### DELETE FROM `test1`.`t1`
10741073
### WHERE
10751074
### @1=3 /* INT meta=0 nullable=1 is_null=0 */
1076-
'/*!*/;
10771075
# at #
10781076
#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0
10791077
SET TIMESTAMP=1000000000/*!*/;

mysql-test/suite/binlog/t/binlog_mysqlbinlog_row_frag.test

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ SELECT a from t into @a;
1111
FLUSH LOGS;
1212
DELETE FROM t;
1313

14-
# Todo: MDEV-10362 to test multi-row Rows_log_event:s in verbose mode
15-
--exec $MYSQL_BINLOG -vv --debug-binlog-row-event-max-encoded-size=256 $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/mysqlbinlog.sql
14+
--exec $MYSQL_BINLOG --verbose --debug-binlog-row-event-max-encoded-size=256 $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/mysqlbinlog.sql
1615

1716
--let SEARCH_PATTERN= BINLOG @binlog_fragment_0, @binlog_fragment_1
1817
--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mysqlbinlog.sql

mysql-test/suite/binlog_encryption/binlog_row_annotate.result

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,6 @@ START TRANSACTION
456456
### DELETE FROM `test1`.`t1`
457457
### WHERE
458458
### @1=3 /* INT meta=0 nullable=1 is_null=0 */
459-
'/*!*/;
460459
# at #
461460
#010909 4:46:40 server id # end_log_pos # Query thread_id=# exec_time=# error_code=0
462461
SET TIMESTAMP=1000000000/*!*/;

sql/log_event.cc

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3875,7 +3875,7 @@ bool Log_event::print_base64(IO_CACHE* file,
38753875
ev->need_flashback_review= need_flashback_review;
38763876
if (print_event_info->verbose)
38773877
{
3878-
if (ev->print_verbose(file, print_event_info))
3878+
if (ev->print_verbose(&print_event_info->tail_cache, print_event_info))
38793879
goto err;
38803880
}
38813881
else
@@ -3899,22 +3899,9 @@ bool Log_event::print_base64(IO_CACHE* file,
38993899
}
39003900
#else
39013901
if (print_event_info->verbose)
3902-
{
3903-
/*
3904-
Verbose event printout can't start before encoded data
3905-
got enquoted. This is done at this point though multi-row
3906-
statement remain vulnerable.
3907-
TODO: fix MDEV-10362 to remove this workaround.
3908-
*/
3909-
if (print_event_info->base64_output_mode !=
3910-
BASE64_OUTPUT_DECODE_ROWS)
3911-
my_b_printf(file, "'%s\n", print_event_info->delimiter);
3912-
error= ev->print_verbose(file, print_event_info);
3913-
}
3902+
error= ev->print_verbose(&print_event_info->tail_cache, print_event_info);
39143903
else
3915-
{
39163904
ev->count_row_events(print_event_info);
3917-
}
39183905
#endif
39193906
delete ev;
39203907
if (unlikely(error))
@@ -11992,7 +11979,7 @@ bool copy_cache_to_file_wrapped(IO_CACHE *body,
1199211979
FILE *file,
1199311980
bool do_wrap,
1199411981
const char *delimiter,
11995-
bool is_verbose)
11982+
bool is_verbose /*TODO: remove */)
1199611983
{
1199711984
const my_off_t cache_size= my_b_tell(body);
1199811985

@@ -12025,8 +12012,7 @@ bool copy_cache_to_file_wrapped(IO_CACHE *body,
1202512012
my_fprintf(file, fmt_frag, 1);
1202612013
if (my_b_copy_to_file(body, file, SIZE_T_MAX))
1202712014
goto err;
12028-
if (!is_verbose)
12029-
my_fprintf(file, fmt_delim, delimiter);
12015+
my_fprintf(file, fmt_delim, delimiter);
1203012016

1203112017
my_fprintf(file, fmt_binlog2, delimiter);
1203212018
}
@@ -12035,8 +12021,7 @@ bool copy_cache_to_file_wrapped(IO_CACHE *body,
1203512021
my_fprintf(file, str_binlog);
1203612022
if (my_b_copy_to_file(body, file, SIZE_T_MAX))
1203712023
goto err;
12038-
if (!is_verbose)
12039-
my_fprintf(file, fmt_delim, delimiter);
12024+
my_fprintf(file, fmt_delim, delimiter);
1204012025
}
1204112026
reinit_io_cache(body, WRITE_CACHE, 0, FALSE, TRUE);
1204212027

@@ -12122,7 +12107,6 @@ bool copy_cache_to_string_wrapped(IO_CACHE *cache,
1212212107
goto err;
1212312108
str += (add_to_len= uint32(cache->end_of_file - (cache_size/2 + 1)));
1212412109
to->length += add_to_len;
12125-
if (!is_verbose)
1212612110
{
1212712111
str += (add_to_len= sprintf(str , fmt_delim, delimiter));
1212812112
to->length += add_to_len;
@@ -12138,7 +12122,6 @@ bool copy_cache_to_string_wrapped(IO_CACHE *cache,
1213812122
goto err;
1213912123
str += cache->end_of_file;
1214012124
to->length += (size_t)cache->end_of_file;
12141-
if (!is_verbose)
1214212125
to->length += sprintf(str , fmt_delim, delimiter);
1214312126
}
1214412127

@@ -12186,6 +12169,7 @@ bool Rows_log_event::print_helper(FILE *file,
1218612169
{
1218712170
IO_CACHE *const head= &print_event_info->head_cache;
1218812171
IO_CACHE *const body= &print_event_info->body_cache;
12172+
IO_CACHE *const tail= &print_event_info->tail_cache;
1218912173
#ifdef WHEN_FLASHBACK_REVIEW_READY
1219012174
IO_CACHE *const sql= &print_event_info->review_sql_cache;
1219112175
#endif
@@ -12216,7 +12200,8 @@ bool Rows_log_event::print_helper(FILE *file,
1221612200
if (copy_event_cache_to_file_and_reinit(head, file) ||
1221712201
copy_cache_to_file_wrapped(body, file, do_print_encoded,
1221812202
print_event_info->delimiter,
12219-
print_event_info->verbose))
12203+
print_event_info->verbose) ||
12204+
copy_event_cache_to_file_and_reinit(tail, file))
1222012205
goto err;
1222112206
}
1222212207
else
@@ -12234,6 +12219,11 @@ bool Rows_log_event::print_helper(FILE *file,
1223412219
return 1;
1223512220
output_buf.append(tmp_str.str, tmp_str.length);
1223612221
my_free(tmp_str.str);
12222+
if (copy_event_cache_to_string_and_reinit(tail, &tmp_str))
12223+
return 1;
12224+
output_buf.append(tmp_str.str, tmp_str.length);
12225+
my_free(tmp_str.str);
12226+
1223712227
#ifdef WHEN_FLASHBACK_REVIEW_READY
1223812228
if (copy_event_cache_to_string_and_reinit(sql, &tmp_str))
1223912229
return 1;
@@ -15056,6 +15046,7 @@ st_print_event_info::st_print_event_info()
1505615046
base64_output_mode=BASE64_OUTPUT_UNSPEC;
1505715047
open_cached_file(&head_cache, NULL, NULL, 0, flags);
1505815048
open_cached_file(&body_cache, NULL, NULL, 0, flags);
15049+
open_cached_file(&tail_cache, NULL, NULL, 0, flags);
1505915050
#ifdef WHEN_FLASHBACK_REVIEW_READY
1506015051
open_cached_file(&review_sql_cache, NULL, NULL, 0, flags);
1506115052
#endif

sql/log_event.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,7 @@ typedef struct st_print_event_info
889889
*/
890890
IO_CACHE head_cache;
891891
IO_CACHE body_cache;
892+
IO_CACHE tail_cache;
892893
#ifdef WHEN_FLASHBACK_REVIEW_READY
893894
/* Storing the SQL for reviewing */
894895
IO_CACHE review_sql_cache;
@@ -899,6 +900,7 @@ typedef struct st_print_event_info
899900
~st_print_event_info() {
900901
close_cached_file(&head_cache);
901902
close_cached_file(&body_cache);
903+
close_cached_file(&tail_cache);
902904
#ifdef WHEN_FLASHBACK_REVIEW_READY
903905
close_cached_file(&review_sql_cache);
904906
#endif

sql/log_event_old.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,6 +1848,7 @@ bool Old_rows_log_event::print_helper(FILE *file,
18481848
{
18491849
IO_CACHE *const head= &print_event_info->head_cache;
18501850
IO_CACHE *const body= &print_event_info->body_cache;
1851+
IO_CACHE *const tail= &print_event_info->tail_cache;
18511852
bool do_print_encoded=
18521853
print_event_info->base64_output_mode != BASE64_OUTPUT_DECODE_ROWS &&
18531854
print_event_info->base64_output_mode != BASE64_OUTPUT_NEVER &&
@@ -1867,8 +1868,9 @@ bool Old_rows_log_event::print_helper(FILE *file,
18671868
{
18681869
if (copy_event_cache_to_file_and_reinit(head, file) ||
18691870
copy_cache_to_file_wrapped(body, file, do_print_encoded,
1870-
print_event_info->delimiter,
1871-
print_event_info->verbose))
1871+
print_event_info->delimiter,
1872+
print_event_info->verbose) ||
1873+
copy_event_cache_to_file_and_reinit(tail, file))
18721874
goto err;
18731875
}
18741876
return 0;

0 commit comments

Comments
 (0)