Details
-
Bug
-
Status: Open (View Workflow)
-
Major
-
Resolution: Unresolved
-
10.11, 11.4, 11.8, 12.3
-
None
Description
get_mysql_vars() in extra/mariabackup/backup_mysql.cc (the block commented "get some default values is they are missing from my.cnf") assigns datadir and innodb_log_group_home_dir from the server's SHOW VARIABLES answer unconditionally. An explicit -datadir or -innodb-log-group-home-dir on the command line is replaced, so mariabackup reads and copies the directory the server names, not the one it was told to use. No warning is printed (the existing "option 'datadir' has different values" message did not appear).
The test runs mariadb-backup --backup against a fake server (perl) that answers like the real one. Control: the server reports the same directory as the option, and the backup completes. Case 1: the server reports another existing directory as datadir. Case 2: the same for innodb_log_group_home_dir. In both cases the real directory is on the command line.
--source include/not_embedded.inc
|
--source include/have_mariabackup.inc
|
|
|
--echo #
|
--echo # get_mysql_vars() (backup_mysql.cc) copies datadir and innodb_log_group_home_dir
|
--echo # from the server's SHOW VARIABLES answer over the same options given on the
|
--echo # mariabackup command line, without a warning.
|
--echo #
|
|
|
--exec $MYSQL -N -B -e "show variables" > $MYSQL_TMP_DIR/vuln_vars.txt
|
--exec $MYSQL -N -B -e "show status like 'innodb_lsn_flushed'" > $MYSQL_TMP_DIR/vuln_status.txt
|
--let $datadir= `SELECT @@datadir`
|
|
|
--let $mode= 0
|
while ($mode <= 2)
|
{
|
--exec echo $mode > $MYSQL_TMP_DIR/vuln_mode.txt |
|
|
perl;
|
use IO::Socket::INET; |
open(my $mf, '<', "$ENV{MYSQL_TMP_DIR}/vuln_mode.txt") or die; |
my $M= <$mf> + 0;
|
close $mf;
|
sub pkt { my ($q, $b)= @_; substr(pack('V', length $b), 0, 3) . chr($q & 255) . $b } |
sub rd { my $s= shift; read($s, my $h, 4) or return; read($s, my $b, unpack('V', substr($h, 0, 3) . "\0")); (ord(substr $h, 3, 1) + 1, $b) } |
sub lstr { my $v= shift; my $n= length $v; ($n < 251 ? chr($n) : "\xfc" . pack('v', $n)) . $v } |
sub rs {
|
my ($s, $q, $cols, @rows)= @_;
|
my $o= pkt($q++, chr(scalar @$cols));
|
$o.= pkt($q++, "\3def\0\0\0" . lstr($_) . lstr($_) . "\x0c" . pack('vVCvC', 33, 1024, 253, 0, 0) . "\0\0") for @$cols; |
$o.= pkt($q++, "\xfe" . pack('vv', 0, 2)); |
$o.= pkt($q++, join '', map { defined $_ ? lstr($_) : "\xfb" } @$_) for @rows; |
print $s $o, pkt($q, "\xfe" . pack('vv', 0, 2)); |
}
|
sub slurp { open(my $h, '<', $_[0]) or die; my @r= map { chomp; [split /\t/, $_, 2] } <$h>; close $h; @r } |
my @real= slurp("$ENV{MYSQL_TMP_DIR}/vuln_vars.txt");
|
my @stat= slurp("$ENV{MYSQL_TMP_DIR}/vuln_status.txt");
|
my $other= "$ENV{MYSQL_TMP_DIR}/";
|
my %over= $M == 1 ? (datadir => $other) : $M == 2 ? (innodb_log_group_home_dir => $other) : ();
|
my @vars= ((grep { !exists $over{$_->[0]} } @real), map { [$_, $over{$_}] } keys %over);
|
my $l= IO::Socket::INET->new(Listen => 5, LocalAddr => '127.0.0.1', LocalPort => 0) or die; |
open(my $f, '>', "$ENV{MYSQL_TMP_DIR}/vuln_port.txt") or die; |
print $f $l->sockport;
|
close $f;
|
fork and exit;
|
open(STDIN, '<', '/dev/null'); open(STDOUT, '>', '/dev/null'); open(STDERR, '>&', \*STDOUT); |
alarm 120;
|
while (my $s= $l->accept) {
|
next if fork;
|
alarm 120;
|
close $l;
|
my $ok= sub { print $s pkt($_[0], "\0\0\0" . pack('vv', 2, 0)) }; |
print $s pkt(0, "\x0a10.11.6-MariaDB\0" . pack('V', 7) . "AAAAAAAA\0" . pack('v', 0xa20d) . "\x2d" . pack('v', 2) . pack('v', 8) . chr(21) . "\0" x 10 . "A" x 12 . "\0mysql_native_password\0"); |
my ($q)= rd($s);
|
print $s pkt($q, "\0\0\0" . pack('vv', 2, 0)); |
while (my ($q, $b)= rd($s)) {
|
my $sql= lc substr($b, 1);
|
if ($sql =~ /^show variables like/) { rs($s, $q, [qw(k v)]) }
|
elsif ($sql =~ /^show variables/) { rs($s, $q, [qw(k v)], @vars) }
|
elsif ($sql =~ /^show (global )?status like 'innodb_lsn_flushed'/) { rs($s, $q, [qw(k v)], @stat) } |
elsif ($sql =~ /^show grants/) { rs($s, $q, ['Grants'], ["GRANT ALL PRIVILEGES ON *.* TO 'root'\@'%' WITH GRANT OPTION"]) } |
elsif ($sql =~ /innodb_encrypt_log/) { rs($s, $q, ['x'], ['0']) } |
elsif ($sql =~ /innodb_encrypt_tables/) { rs($s, $q, ['x'], ['OFF']) } |
elsif ($sql =~ /^(select|show)/) { rs($s, $q, ['x']) } |
else { $ok->($q) } }
|
exit }
|
EOF
|
|
|
--let $fake_port= `SELECT LOAD_FILE('$MYSQL_TMP_DIR/vuln_port.txt')` |
--let $opts= --datadir=$datadir
|
if ($mode == 2)
|
{
|
--let $opts= --innodb-log-group-home-dir=$datadir
|
}
|
if ($mode == 0)
|
{
|
--echo # control: the server reports the same directory as the option
|
}
|
if ($mode == 1)
|
{
|
--echo # BUG if the server's datadir replaces --datadir |
}
|
if ($mode == 2) |
{
|
--echo # BUG if the server's innodb_log_group_home_dir replaces --innodb-log-group-home-dir |
}
|
|
|
--exec timeout 100 $XTRABACKUP --no-defaults --backup --target-dir=$MYSQL_TMP_DIR/vuln_backup --host=127.0.0.1 --port=$fake_port --user=root --password=x --skip-ssl $opts > $MYSQL_TMP_DIR/vuln_out.txt 2>&1 |
|
|
--remove_file $MYSQL_TMP_DIR/vuln_port.txt |
--remove_file $MYSQL_TMP_DIR/vuln_mode.txt |
--remove_file $MYSQL_TMP_DIR/vuln_out.txt |
--exec rm -rf $MYSQL_TMP_DIR/vuln_backup |
--inc $mode |
}
|
|
|
--remove_file $MYSQL_TMP_DIR/vuln_vars.txt
|
--remove_file $MYSQL_TMP_DIR/vuln_status.txt
|
The control passes; case 1 fails: the backup reads the redo log from the server's directory and stops with
[00] Error: cannot read redo log header
|
Case 2 (checked separately, with a control) fails the same way. Same on 12.3.4 (mariadb-backup). With the same command line and the real directory reported by the server, the backup completes.
10.11.20-MariaDB-asan-debug-log source revision: 4ae8389be807b592fc909b99130350b4b8ced9b3
12.3.4: f74586eebfe2a186087cfdda42be2d0ba398f01c