Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions mysql-test/suite/galera/include/galera_sst_param_injection.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Drive one shell-injection rejection check for a single SST directory parameter
# that is validated by the SST script (safe_path).
#
# Caller sets before sourcing (and the cluster must be healthy, rsync SST):
# $inj_param - mysqld option name, clean text (e.g. innodb-log-group-home-dir)
# $inj_reject - log pattern in mysqld.2.err that proves the value was rejected
#
# Points $inj_param at a directory whose name contains a shell metacharacter and
# forces a full SST on node 2, so the restarted joiner runs the value through the
# SST script; asserts node 2 refuses it (logs $inj_reject). For
# innodb-data-home-dir the system tablespace (ibdata1) is moved into that dir so
# mysqld can still start. Restores the config and rejoins the cluster.

--connection node_2
--source include/shutdown_mysqld.inc

# pass the (clean) option name to perl via a sidecar file
--exec echo "$inj_param" > $MYSQL_TMP_DIR/inj_param

perl;
use strict;
use File::Path;
my $tmp = $ENV{MYSQL_TMP_DIR};
my $vardir = $ENV{MYSQLTEST_VARDIR};
# read the option name from the sidecar (perl can't see mysqltest vars)
open(my $pf, '<', "$tmp/inj_param") or die $!;
my $param = <$pf>; chomp $param; close $pf;
my $ddir = "$tmp/pinj'x"; # name carries a shell metachar (')
my $cnf = "$vardir/my.cnf";
mkpath($ddir); # must exist for mysqld to start
# innodb-data-home-dir holds the system tablespace: move ibdata1 in so the
# joiner can still start and reach the SST
rename("$vardir/mysqld.2/data/ibdata1", "$ddir/ibdata1")
if $param eq 'innodb-data-home-dir';
# remember my.cnf size so cleanup can truncate the appended block away
open(my $sz, '>', "$tmp/inj_cnf_size") or die $!; print $sz -s $cnf; close $sz;
# point the option at the malicious dir (perl writes it, so no shell parses the ')
open(my $fh, '>>', $cnf) or die $!;
print $fh "[mysqld.2]\n$param=\"$ddir\"\n";
close $fh;
unlink "$vardir/mysqld.2/data/grastate.dat"; # force a full SST
EOF

# start the joiner; it must refuse SST and exit
--connection node_2
--error 1,134
--exec $MYSQLD_LAST_CMD

# joiner must have rejected the value
--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.2.err
--let SEARCH_PATTERN = $inj_reject
--source include/search_pattern_in_file.inc

# cleanup: restore my.cnf, ibdata1 and the dir, rejoin. No stray helpers to kill
# - safe_path rejects in create_dirs, before the rsync daemon is ever started.
perl;
use strict;
use File::Path qw(rmtree);
my $tmp = $ENV{MYSQL_TMP_DIR};
my $vardir = $ENV{MYSQLTEST_VARDIR};
my $cnf = "$vardir/my.cnf";
my $ddir = "$tmp/pinj'x";
open(my $pf, '<', "$tmp/inj_param") or die $!;
my $param = <$pf>; chomp $param; close $pf;
open(my $sz, '<', "$tmp/inj_cnf_size") or die $!;
my $orig = <$sz>; close $sz;
truncate($cnf, $orig) or die "truncate $cnf: $!";
rename("$ddir/ibdata1", "$vardir/mysqld.2/data/ibdata1")
if $param eq 'innodb-data-home-dir';
rmtree($ddir);
unlink "$tmp/inj_param", "$tmp/inj_cnf_size";
EOF

--connection node_2
--source include/start_mysqld.inc

--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'
--source include/wait_condition.inc
25 changes: 25 additions & 0 deletions mysql-test/suite/galera/r/galera_sst_buffer_pool_injection.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
connection node_2;
connection node_1;
SELECT 1;
1
1
connection node_1;
call mtr.add_suppression('Invalid characters in');
call mtr.add_suppression('Process completed with error');
call mtr.add_suppression('State transfer to .* failed');
call mtr.add_suppression('Will never receive state. Need to abort');
connection node_2;
FOUND 1 /Invalid characters in INNODB_BUFFER_POOL/ in mysqld.2.err
buffer-pool injection prevented
connection node_2;
call mtr.add_suppression('Invalid characters in');
call mtr.add_suppression('WSREP_SST:');
call mtr.add_suppression('Failed to read .* from: wsrep_sst_mariabackup');
call mtr.add_suppression('Failed to prepare for .* SST');
call mtr.add_suppression('SST preparation failed');
call mtr.add_suppression('SST request callback failed');
call mtr.add_suppression('Will never receive state. Need to abort');
call mtr.add_suppression('Parent mysqld process .* terminated unexpectedly');
call mtr.add_suppression('Cleanup after exit with status');
call mtr.add_suppression('State transfer to .* failed');
call mtr.add_suppression('SST .* failed');
28 changes: 28 additions & 0 deletions mysql-test/suite/galera/r/galera_sst_datadir_injection.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
connection node_2;
connection node_1;
SELECT 1;
1
1
FOUND 1 /wsrep_sst_rsync/ in mysqld.1.err
connection node_1;
call mtr.add_suppression('unsafe for shell interpolation');
call mtr.add_suppression('Process completed with error');
call mtr.add_suppression('State transfer to .* failed');
call mtr.add_suppression('Will never receive state. Need to abort');
connection node_2;
connection node_2;
FOUND 1 /unsafe for shell interpolation/ in mysqld.2.err
datadir injection prevented
connection node_2;
call mtr.add_suppression('Illegal character in variable');
call mtr.add_suppression('unsafe for shell interpolation');
call mtr.add_suppression('WSREP_SST:');
call mtr.add_suppression('Failed to read .* from: wsrep_sst_rsync');
call mtr.add_suppression('Failed to prepare for .* SST');
call mtr.add_suppression('SST preparation failed');
call mtr.add_suppression('SST request callback failed');
call mtr.add_suppression('Will never receive state. Need to abort');
call mtr.add_suppression('Parent mysqld process .* terminated unexpectedly');
call mtr.add_suppression('Cleanup after exit with status');
call mtr.add_suppression('State transfer to .* failed');
call mtr.add_suppression('SST .* failed');
38 changes: 38 additions & 0 deletions mysql-test/suite/galera/r/galera_sst_dir_param_injection.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
connection node_2;
connection node_1;
SELECT 1;
1
1
connection node_1;
call mtr.add_suppression('Invalid characters in');
call mtr.add_suppression('Process completed with error');
call mtr.add_suppression('Failed to read .* from: wsrep_sst_rsync');
call mtr.add_suppression('State transfer to .* failed');
call mtr.add_suppression('Will never receive state. Need to abort');
connection node_2;
connection node_2;
FOUND 1 /Invalid characters in/ in mysqld.2.err
connection node_2;
connection node_2;
connection node_2;
FOUND 2 /Invalid characters in/ in mysqld.2.err
connection node_2;
connection node_2;
connection node_2;
FOUND 3 /Invalid characters in/ in mysqld.2.err
connection node_2;
connection node_2;
connection node_2;
FOUND 4 /Invalid characters in/ in mysqld.2.err
connection node_2;
call mtr.add_suppression('Invalid characters in');
call mtr.add_suppression('WSREP_SST:');
call mtr.add_suppression('Failed to read .* from: wsrep_sst_rsync');
call mtr.add_suppression('Failed to prepare for .* SST');
call mtr.add_suppression('SST preparation failed');
call mtr.add_suppression('SST request callback failed');
call mtr.add_suppression('Will never receive state. Need to abort');
call mtr.add_suppression('Parent mysqld process .* terminated unexpectedly');
call mtr.add_suppression('Cleanup after exit with status');
call mtr.add_suppression('State transfer to .* failed');
call mtr.add_suppression('SST .* failed');
10 changes: 10 additions & 0 deletions mysql-test/suite/galera/t/galera_sst_buffer_pool_injection.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
!include ../galera_2nodes.cnf

[mysqld]
wsrep_sst_method=mariabackup
wsrep_sst_auth="root:"
wsrep_debug=1

[sst]
transferfmt=@ENV.MTR_GALERA_TFMT
streamfmt=mbstream
73 changes: 73 additions & 0 deletions mysql-test/suite/galera/t/galera_sst_buffer_pool_injection.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#
# Galera mariabackup SST must reject innodb-buffer-pool-filename when its value
# contains shell metacharacters, instead of running it through the eval'd
# mariabackup command (INNOEXTRA).
#
# Steps:
# 1. Bring up a 2-node mariabackup-SST cluster.
# 2. Shut down node 2; force a full SST; restart it with a malicious
# innodb-buffer-pool-filename on the command line (so it is forwarded to
# the SST script via --mysqld-args). The value carries a "touch <marker>"
# payload.
# 3. Assert node 2 refuses the SST (safe_path) and the injected command never
# runs (no marker file).
# 4. Restart node 2 cleanly and rejoin.
#

--source include/galera_cluster.inc
--source include/have_innodb.inc
--source include/have_mariabackup.inc

SELECT 1;

--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'
--source include/wait_condition.inc

--connection node_1
call mtr.add_suppression('Invalid characters in');
call mtr.add_suppression('Process completed with error');
call mtr.add_suppression('State transfer to .* failed');
call mtr.add_suppression('Will never receive state. Need to abort');

--connection node_2
--source include/shutdown_mysqld.inc

--remove_file $MYSQLTEST_VARDIR/mysqld.2/data/grastate.dat
perl;
unlink "$ENV{MYSQL_TMP_DIR}/bp_inj_marker";
EOF

# restart the joiner with a malicious innodb-buffer-pool-filename on the command
# line; the payload is wrapped in double quotes so mtr's own shell does not run
# it (only the SST script's eval would, on an unfixed build)
--error 1,134
--exec $MYSQLD_LAST_CMD --innodb-buffer-pool-filename="x'&touch $MYSQL_TMP_DIR/bp_inj_marker&'y"

--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.2.err
--let SEARCH_PATTERN = Invalid characters in INNODB_BUFFER_POOL
--source include/search_pattern_in_file.inc

perl;
die "FAIL: marker created - buffer-pool injection was NOT prevented\n"
if -e "$ENV{MYSQL_TMP_DIR}/bp_inj_marker";
print "buffer-pool injection prevented\n";
EOF

# cleanup: restart node 2 cleanly (no malicious arg) and rejoin
--connection node_2
--source include/start_mysqld.inc

--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'
--source include/wait_condition.inc

call mtr.add_suppression('Invalid characters in');
call mtr.add_suppression('WSREP_SST:');
call mtr.add_suppression('Failed to read .* from: wsrep_sst_mariabackup');
call mtr.add_suppression('Failed to prepare for .* SST');
call mtr.add_suppression('SST preparation failed');
call mtr.add_suppression('SST request callback failed');
call mtr.add_suppression('Will never receive state. Need to abort');
call mtr.add_suppression('Parent mysqld process .* terminated unexpectedly');
call mtr.add_suppression('Cleanup after exit with status');
call mtr.add_suppression('State transfer to .* failed');
call mtr.add_suppression('SST .* failed');
5 changes: 5 additions & 0 deletions mysql-test/suite/galera/t/galera_sst_datadir_injection.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
!include ../galera_2nodes.cnf

[mysqld]
wsrep_sst_method=rsync
wsrep_debug=1
98 changes: 98 additions & 0 deletions mysql-test/suite/galera/t/galera_sst_datadir_injection.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#
# Galera SST must reject a datadir containing shell metacharacters in mysqld
# (C++) before the SST command is built, instead of running it through the shell.
#
# Steps:
# 1. Bring up a 2-node rsync-SST cluster.
# 2. Shut down node 2; relocate its datadir to a name carrying a shell-injection
# payload (dd_inj'&touch <marker>&'x); force a full SST.
# 3. Start node 2 as joiner; assert it refuses the datadir and the injected
# command never runs (no marker file).
# 4. Restore the datadir and rejoin.
#

--source include/galera_cluster.inc
--source include/have_innodb.inc

SELECT 1;

--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'
--source include/wait_condition.inc

# sanity: initial SST used rsync
--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.1.err
--let SEARCH_PATTERN = wsrep_sst_rsync
--source include/search_pattern_in_file.inc

--connection node_1
call mtr.add_suppression('unsafe for shell interpolation');
call mtr.add_suppression('Process completed with error');
call mtr.add_suppression('State transfer to .* failed');
call mtr.add_suppression('Will never receive state. Need to abort');

--connection node_2
--source include/shutdown_mysqld.inc

# move the datadir to a name carrying a shell-injection payload
perl;
use strict;
use File::Path;
my $tmp = $ENV{MYSQL_TMP_DIR};
my $vardir = $ENV{MYSQLTEST_VARDIR};
my $marker = "$tmp/datadir_inj_marker";
my $ddir = "$tmp/dd_inj'&touch $marker&'x";
my $cnf = "$vardir/my.cnf";
unlink $marker;
(my $parent = $ddir) =~ s{/[^/]+$}{};
mkpath($parent);
rename("$vardir/mysqld.2/data", $ddir) or die "rename datadir: $!";
unlink "$ddir/grastate.dat";
open(my $sz, '>', "$tmp/inj_cnf_size") or die $!; print $sz -s $cnf; close $sz;
open(my $fh, '>>', $cnf) or die $!; print $fh "[mysqld.2]\ndatadir=\"$ddir\"\n"; close $fh;
EOF

--connection node_2
--error 1,134
--exec $MYSQLD_LAST_CMD

--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.2.err
--let SEARCH_PATTERN = unsafe for shell interpolation
--source include/search_pattern_in_file.inc

perl;
die "FAIL: marker created - datadir injection was NOT prevented\n"
if -e "$ENV{MYSQL_TMP_DIR}/datadir_inj_marker";
print "datadir injection prevented\n";
EOF

# restore datadir and rejoin
perl;
use strict;
my $tmp = $ENV{MYSQL_TMP_DIR};
my $vardir = $ENV{MYSQLTEST_VARDIR};
my $cnf = "$vardir/my.cnf";
open(my $sz, '<', "$tmp/inj_cnf_size") or die $!; my $orig = <$sz>; close $sz;
truncate($cnf, $orig) or die "truncate: $!";
unlink "$tmp/datadir_inj_marker", "$tmp/inj_cnf_size";
rename("$tmp/dd_inj'&touch $tmp/datadir_inj_marker&'x", "$vardir/mysqld.2/data")
or die "restore datadir: $!";
EOF

--connection node_2
--source include/start_mysqld.inc
--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'
--source include/wait_condition.inc

# suppress the rejected-SST noise on the final node_2 instance
call mtr.add_suppression('Illegal character in variable');
call mtr.add_suppression('unsafe for shell interpolation');
call mtr.add_suppression('WSREP_SST:');
call mtr.add_suppression('Failed to read .* from: wsrep_sst_rsync');
call mtr.add_suppression('Failed to prepare for .* SST');
call mtr.add_suppression('SST preparation failed');
call mtr.add_suppression('SST request callback failed');
call mtr.add_suppression('Will never receive state. Need to abort');
call mtr.add_suppression('Parent mysqld process .* terminated unexpectedly');
call mtr.add_suppression('Cleanup after exit with status');
call mtr.add_suppression('State transfer to .* failed');
call mtr.add_suppression('SST .* failed');
5 changes: 5 additions & 0 deletions mysql-test/suite/galera/t/galera_sst_dir_param_injection.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
!include ../galera_2nodes.cnf

[mysqld]
wsrep_sst_method=rsync
wsrep_debug=1
Loading