List:Commits« Previous MessageNext Message »
From:Jorgen Loland Date:August 21 2008 8:38am
Subject:bzr commit into mysql-6.0-backup branch (jorgen.loland:2679) Bug#34171
View as plain text  
#At file:///localhome/jl208045/mysql/mysql-6.0-backup-34171/

 2679 Jorgen Loland	2008-08-21
      Bug#34171 - "Backup: ignoring --secure-file-priv"
      
      Before patch: Backup code ignored secure-file-priv option, 
      allowing backup to write to any location
      
      After patch: Backup and restore are not allowed to write or read 
      outside path in secure-file-priv option if option has been 
      specified.
added:
  mysql-test/r/backup_securefilepriv.result
  mysql-test/t/backup_backupdir-master.opt
  mysql-test/t/backup_securefilepriv-master.opt
  mysql-test/t/backup_securefilepriv.test
modified:
  mysql-test/lib/mtr_report.pl
  mysql-test/r/backup_backupdir.result
  mysql-test/r/backup_errors.result
  sql/backup/stream.cc
  sql/backup/stream.h
  sql/share/errmsg.txt

per-file comments:
  mysql-test/lib/mtr_report.pl
    Added expected error for backup_securefilepriv
  mysql-test/r/backup_backupdir.result
    Result modified to handle slightly changed error message.
  mysql-test/r/backup_errors.result
    Result modified to handle slightly changed error message.
  mysql-test/r/backup_securefilepriv.result
    New result file for secure-file-priv backup tests
  mysql-test/t/backup_backupdir-master.opt
    Sets secure-file-priv to / for backup_backupdir test
  mysql-test/t/backup_securefilepriv-master.opt
    Sets secure-file-priv option for backup_securefilepriv test
  mysql-test/t/backup_securefilepriv.test
    New test file for secure-file-priv backup tests
  sql/backup/stream.cc
    Adds check for secure-file-priv option when opening backup/restore streams. Also some doc-bug fixes for Stream::prepare_path
  sql/backup/stream.h
    Added private method test_secure_file_priv_access
  sql/share/errmsg.txt
    Changed ER_BACKUP_WRITE_LOC to include secure-file-priv in err message
=== modified file 'mysql-test/lib/mtr_report.pl'
--- a/mysql-test/lib/mtr_report.pl	2008-08-20 13:23:10 +0000
+++ b/mysql-test/lib/mtr_report.pl	2008-08-21 08:38:12 +0000
@@ -357,6 +357,12 @@ sub mtr_report_stats ($) {
 		(
 		  /Restore: Tablespace .* needed by tables being restored has changed on the server/
 		) or
+                
+		# The securefilepriv test triggers error below on purpose
+		($testname eq 'main.backup_securefilepriv') and
+		(
+		  /Backup: Can't write to backup location/
+		) or
 		
 		# The views test triggers errors below on purpose
 		($testname eq 'main.backup_views') and

=== modified file 'mysql-test/r/backup_backupdir.result'
--- a/mysql-test/r/backup_backupdir.result	2008-08-08 17:21:31 +0000
+++ b/mysql-test/r/backup_backupdir.result	2008-08-21 08:38:12 +0000
@@ -42,10 +42,10 @@ backup_id
 Ensure backup image file went to the correct location
 Try a backup to an invalid relative path.
 BACKUP DATABASE bup_backupdir TO '../../../../../../../../../../../../../../../../../../bup_backupdir5.bak';
-ERROR HY000: Can't write to backup location '../../../../../../../../../../../../../../../../../../bup_backup' (file already exists?)
+ERROR HY000: Can't write to backup location '../../../../../../../../../../../../../../../../../../bup_backup' (file already exists or running with --secure-file-priv option?)
 Try a backup to an invalid hard path.
 BACKUP DATABASE bup_backupdir TO '/dev/not/there/either/bup_backupdir6.bak';
-ERROR HY000: Can't write to backup location '/dev/not/there/either/bup_backupdir6.bak' (file already exists?)
+ERROR HY000: Can't write to backup location '/dev/not/there/either/bup_backupdir6.bak' (file already exists or running with --secure-file-priv option?)
 SET @@global.backupdir = 'This_is_really_stupid/not/there/at/all';
 Warnings:
 Warning	1725	The path specified for the system variable backupdir cannot be accessed or is invalid. ref: This_is_really_stupid/not/there/at/all

=== modified file 'mysql-test/r/backup_errors.result'
--- a/mysql-test/r/backup_errors.result	2008-03-05 17:48:12 +0000
+++ b/mysql-test/r/backup_errors.result	2008-08-21 08:38:12 +0000
@@ -8,12 +8,12 @@ CREATE TABLE bdb.t1(a int) ENGINE=MEMORY
 BACKUP DATABASE adb TO '';
 ERROR HY000: Malformed file path ''
 BACKUP DATABASE adb TO "bdb/t1.frm";
-ERROR HY000: Can't write to backup location 'bdb/t1.frm' (file already exists?)
+ERROR HY000: Can't write to backup location 'bdb/t1.frm' (file already exists or running with --secure-file-priv option?)
 BACKUP DATABASE adb TO "test.bak";
 backup_id
 #
 BACKUP DATABASE adb TO "test.bak";
-ERROR HY000: Can't write to backup location 'test.bak' (file already exists?)
+ERROR HY000: Can't write to backup location 'test.bak' (file already exists or running with --secure-file-priv option?)
 DROP DATABASE IF EXISTS foo;
 DROP DATABASE IF EXISTS bar;
 BACKUP DATABASE foo TO 'test.bak';

=== added file 'mysql-test/r/backup_securefilepriv.result'
--- a/mysql-test/r/backup_securefilepriv.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/r/backup_securefilepriv.result	2008-08-21 08:38:12 +0000
@@ -0,0 +1,63 @@
+Initializing tests
+Create directories for backup images
+Creating database and populating tables
+CREATE DATABASE sfp_db;
+CREATE TABLE sfp_db.t1 (i int);
+INSERT INTO sfp_db.t1 VALUES (1);
+INSERT INTO sfp_db.t1 VALUES (2);
+INSERT INTO sfp_db.t1 VALUES (3);
+INSERT INTO sfp_db.t1 VALUES (4);
+INSERT INTO sfp_db.t1 VALUES (5);
+INSERT INTO sfp_db.t1 VALUES (10);
+INSERT INTO sfp_db.t1 VALUES (15);
+INSERT INTO sfp_db.t1 VALUES (100);
+
+Starting tests
+
+Backup to secure_file_priv dir 
+(MYSQLTEST_VARDIR/master-data/sfp_path)
+BACKUP DATABASE sfp_db TO 'sfp_path/bup_sfp1.bak';
+backup_id
+#
+Ensure backup image file went to the correct location
+
+Backup to subpath of secure_file_priv 
+(MYSQLTEST_VARDIR/master-data/sfp_path/subpath)
+BACKUP DATABASE sfp_db TO 'sfp_path/subpath/bup_sfp2.bak';
+backup_id
+#
+Ensure backup image file went to the correct location
+
+Change backupdir to sfp_path/subpath 
+(MYSQLTEST_VARDIR/master-data/sfp_path/subpath)
+SET @@global.backupdir = 'sfp_path/subpath';
+
+Backup to secure_file_priv subpath, no dir in backup file name
+(MYSQLTEST_VARDIR/master-data/sfp_path/subpath)
+BACKUP DATABASE sfp_db TO 'bup_sfp3.bak';
+backup_id
+#
+Ensure backup image file went to the correct location
+
+Backup to secure_file_priv, relative path in backup file name
+(MYSQLTEST_VARDIR/master-data/sfp_path)
+BACKUP DATABASE sfp_db TO '../bup_sfp4.bak';
+backup_id
+#
+Ensure backup image file went to the correct location
+
+Backup to relative path below secure_file_priv path should fail
+BACKUP DATABASE sfp_db TO '../../bup_sfp_fail1.bak';
+ERROR HY000: The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
+
+Reset backupdir to MYSQLTEST_VARDIR/master-data/
+SET @@global.backupdir = @@global.datadir;
+
+Backup to other path than secure_file_priv should fail
+BACKUP DATABASE sfp_db TO 'bup_sfp_fail2.bak';
+ERROR HY000: The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
+
+Cleanup
+
+DROP TABLE sfp_db.t1;
+DROP DATABASE sfp_db;

=== added file 'mysql-test/t/backup_backupdir-master.opt'
--- a/mysql-test/t/backup_backupdir-master.opt	1970-01-01 00:00:00 +0000
+++ b/mysql-test/t/backup_backupdir-master.opt	2008-08-21 08:38:12 +0000
@@ -0,0 +1 @@
+--secure-file-priv=/

=== added file 'mysql-test/t/backup_securefilepriv-master.opt'
--- a/mysql-test/t/backup_securefilepriv-master.opt	1970-01-01 00:00:00 +0000
+++ b/mysql-test/t/backup_securefilepriv-master.opt	2008-08-21 08:38:12 +0000
@@ -0,0 +1 @@
+--secure-file-priv=$MYSQLTEST_VARDIR/master-data/sfp_path

=== added file 'mysql-test/t/backup_securefilepriv.test'
--- a/mysql-test/t/backup_securefilepriv.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/t/backup_securefilepriv.test	2008-08-21 08:38:12 +0000
@@ -0,0 +1,118 @@
+#
+# Purpose: Backup images should only be allowed to be written to the
+# path specified by secure_file_priv or a sub-path of it.
+#
+# See backup_securefilepriv-master.opt for secure_file_priv command line option
+#
+# @@backupdir        is MYSQLTEST_VARDIR/master-data/
+# @@secure_file_priv is MYSQLTEST_VARDIR/master-data/sfp_path/
+
+--echo Initializing tests
+
+--error 0,1,2
+rmdir $MYSQLTEST_VARDIR/master-data/sfp_path/subpath;
+--error 0,1,2
+rmdir $MYSQLTEST_VARDIR/master-data/sfp_path;
+
+--echo Create directories for backup images
+mkdir $MYSQLTEST_VARDIR/master-data/sfp_path;
+mkdir $MYSQLTEST_VARDIR/master-data/sfp_path/subpath;
+
+--echo Creating database and populating tables
+
+CREATE DATABASE sfp_db;
+
+CREATE TABLE sfp_db.t1 (i int);
+
+INSERT INTO sfp_db.t1 VALUES (1);
+INSERT INTO sfp_db.t1 VALUES (2);
+INSERT INTO sfp_db.t1 VALUES (3);
+INSERT INTO sfp_db.t1 VALUES (4);
+INSERT INTO sfp_db.t1 VALUES (5);
+INSERT INTO sfp_db.t1 VALUES (10);
+INSERT INTO sfp_db.t1 VALUES (15);
+INSERT INTO sfp_db.t1 VALUES (100);
+
+--echo 
+--echo Starting tests
+
+--echo 
+--echo Backup to secure_file_priv dir 
+--echo (MYSQLTEST_VARDIR/master-data/sfp_path)
+--replace_column 1 #
+BACKUP DATABASE sfp_db TO 'sfp_path/bup_sfp1.bak';
+
+--echo Ensure backup image file went to the correct location
+--file_exists $MYSQLTEST_VARDIR/master-data/sfp_path/bup_sfp1.bak
+
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/sfp_path/bup_sfp1.bak
+
+--echo  
+--echo Backup to subpath of secure_file_priv 
+--echo (MYSQLTEST_VARDIR/master-data/sfp_path/subpath)
+--replace_column 1 #
+BACKUP DATABASE sfp_db TO 'sfp_path/subpath/bup_sfp2.bak';
+
+--echo Ensure backup image file went to the correct location
+--file_exists $MYSQLTEST_VARDIR/master-data/sfp_path/subpath/bup_sfp2.bak
+
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/sfp_path/subpath/bup_sfp2.bak
+
+--echo  
+--echo Change backupdir to sfp_path/subpath 
+--echo (MYSQLTEST_VARDIR/master-data/sfp_path/subpath)
+SET @@global.backupdir = 'sfp_path/subpath';
+
+--echo  
+--echo Backup to secure_file_priv subpath, no dir in backup file name
+--echo (MYSQLTEST_VARDIR/master-data/sfp_path/subpath)
+--replace_column 1 #
+BACKUP DATABASE sfp_db TO 'bup_sfp3.bak';
+
+--echo Ensure backup image file went to the correct location
+--file_exists $MYSQLTEST_VARDIR/master-data/sfp_path/subpath/bup_sfp3.bak
+
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/sfp_path/subpath/bup_sfp3.bak
+
+--echo  
+--echo Backup to secure_file_priv, relative path in backup file name
+--echo (MYSQLTEST_VARDIR/master-data/sfp_path)
+--replace_column 1 #
+BACKUP DATABASE sfp_db TO '../bup_sfp4.bak';
+
+--echo Ensure backup image file went to the correct location
+--file_exists $MYSQLTEST_VARDIR/master-data/sfp_path/bup_sfp4.bak
+
+--error 0,1
+--remove_file $MYSQLTEST_VARDIR/master-data/sfp_path/bup_sfp4.bak
+
+# Tests that fail
+
+--echo  
+--echo Backup to relative path below secure_file_priv path should fail
+--error ER_OPTION_PREVENTS_STATEMENT
+BACKUP DATABASE sfp_db TO '../../bup_sfp_fail1.bak';
+
+--echo  
+--echo Reset backupdir to MYSQLTEST_VARDIR/master-data/
+SET @@global.backupdir = @@global.datadir;
+
+--echo  
+--echo Backup to other path than secure_file_priv should fail
+--error ER_OPTION_PREVENTS_STATEMENT
+BACKUP DATABASE sfp_db TO 'bup_sfp_fail2.bak';
+
+--echo 
+--echo Cleanup
+--echo 
+
+DROP TABLE sfp_db.t1;
+DROP DATABASE sfp_db;
+
+--error 0,1,2
+rmdir $MYSQLTEST_VARDIR/master-data/sfp_path/subpath;
+--error 0,1,2
+rmdir $MYSQLTEST_VARDIR/master-data/sfp_path;

=== modified file 'sql/backup/stream.cc'
--- a/sql/backup/stream.cc	2008-08-08 17:21:31 +0000
+++ b/sql/backup/stream.cc	2008-08-21 08:38:12 +0000
@@ -218,6 +218,9 @@ Stream::Stream(Logger &log, ::String *ba
   For example, if backupdir = '/dev/tmp' and orig_log = '../backup.bak', the 
   combined path is = '/dev/backup.bak'.
 
+  Note: fn_format may likely be used instead of this function - will
+  not be changed unless bugs are found.
+
   @returns 
     0 if success
     1 if cannot be combined. Note: m_path is set to '' when this occurs to
@@ -288,6 +291,23 @@ int Stream::prepare_path(::String *backu
 {
   int path_len= 0;
 
+
+  // If backupdir is not a hard path, replace it with the full path
+  // name. Full path is needed to check secure_file_priv.
+  if (!test_if_hard_path((*backupdir).c_ptr())) {
+    char full_path[FN_LEN];
+    /* 
+       MY_UNPACK_FILENAME -> "~/" will be changed to full path
+       MY_RELATIVE_PATH -> path is constructed from the path in both
+       arguments, including that every "../" in backupdir removes
+       deepest dir from mysql_real_data_home
+    */
+    fn_format(full_path, (*backupdir).c_ptr(), mysql_real_data_home, "",
+              MY_UNPACK_FILENAME|MY_RELATIVE_PATH);
+
+    (*backupdir)= (String)full_path;
+  }
+  
   /*
     Prepare the path using the backupdir iff no relative path
     or no hard path included.
@@ -301,9 +321,9 @@ int Stream::prepare_path(::String *backu
               Make relative to backupdir.
 
       Example BACKUP DATATBASE ... TO '../monthly/dec.bak'
-              If backupdir = '/dev/daily/backup' then the
+              If backupdir = '/dev/daily' then the
               calculated path becomes
-              '/dev/monthly/backup/dec.bak'
+              '/dev/monthly/dec.bak'
     */
     char new_path[FN_LEN];
     if (make_relative_path(new_path, orig_loc.str, backupdir))
@@ -316,27 +336,31 @@ int Stream::prepare_path(::String *backu
   else if (!test_if_hard_path(orig_loc.str))
   {
     /* 
-      Case 2: Backup image file name has hard path. 
+      Case 2: Backup image file name has no path or has a subpath. 
 
-      Example BACKUP DATATBASE ... TO '/dev/dec.bak'
-              If backupdir = '/dev/daily/backup' then the
+      Example BACKUP DATABASE ... TO 'week2.bak'
+              If backupdir = '/dev/weekly/' then the
               calculated path becomes
-              '/dev/dec.bak'
+              '/dev/weekly/week2.bak'
+      Example BACKUP DATABASE ... TO 'jan/day1.bak'
+              If backupdir = '/dev/monthly/' then the
+              calculated path becomes
+              '/dev/monthly/jan/day1.bak'
     */
     path_len=backupdir->length() + orig_loc.length + 1;
     m_path.alloc(path_len);
     fn_format(m_path.c_ptr(), orig_loc.str, backupdir->c_ptr(), "",
-              MY_UNPACK_FILENAME);
+              MY_UNPACK_FILENAME|MY_RELATIVE_PATH);
   }
   else 
   {
     /* 
-      Case 3: Backup image file name has no path. 
+      Case 3: Backup image file name has hard path. 
 
-      Example BACKUP DATATBASE ... TO 'week2.bak'
-              If backupdir = '/dev/weekly/backup' then the
+      Example BACKUP DATATBASE ... TO '/dev/dec.bak'
+              If backupdir = '/dev/daily/backup' then the
               calculated path becomes
-              '/dev/weekly/week2.bak'
+              '/dev/dec.bak'
     */
     path_len= orig_loc.length + 1;
     m_path.alloc(path_len);
@@ -347,9 +371,35 @@ int Stream::prepare_path(::String *backu
   return 0;
 }
 
+/**
+  Check if secure_file_priv variable has been set and if so, whether
+  or not backup tries to write to the path (or a sub-path) specified
+  by secure_file_priv.
+
+  Reports error ER_OPTION_PREVENTS_STATEMENT if backup tries to write
+  to a different path than specified by secure_file_priv.
+  
+  @retval TRUE  backup is allowed to write to this path
+  @retval FALSE backup is not allowed to write to this path. Side
+                effect: error is reported
+*/
+bool Stream::test_secure_file_priv_access(char *path) {
+  bool has_access = !opt_secure_file_priv ||                 // option not specified, or
+                    !strncmp(opt_secure_file_priv, path,     // path is (subpath of)
+                             strlen(opt_secure_file_priv));  // opt_secure_file_priv
+  if (!has_access) {
+    /* Write only allowed to dir or subdir specified by secure_file_priv */
+    my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--secure-file-priv");
+  }
+  return has_access;
+}
+
 bool Stream::open()
 {
   close();
+  if (!test_secure_file_priv_access(m_path.c_ptr())) {
+    return FALSE;
+  }
   m_fd= my_open(m_path.c_ptr(), m_flags, MYF(0));
   return m_fd >= 0;
 }

=== modified file 'sql/backup/stream.h'
--- a/sql/backup/stream.h	2008-08-08 17:21:31 +0000
+++ b/sql/backup/stream.h	2008-08-21 08:38:12 +0000
@@ -109,7 +109,7 @@ private:
                          ::String *backupdir);
   int prepare_path(::String *backupdir, 
                    LEX_STRING orig_loc);
-
+  bool test_secure_file_priv_access(char *path);
 };
 
 /// Used to write to backup stream.

=== modified file 'sql/share/errmsg.txt'
--- a/sql/share/errmsg.txt	2008-08-20 13:23:10 +0000
+++ b/sql/share/errmsg.txt	2008-08-21 08:38:12 +0000
@@ -6177,7 +6177,7 @@ ER_BACKUP_INVALID_LOC
 ER_BACKUP_READ_LOC
         eng "Can't read backup location '%-.64s'"
 ER_BACKUP_WRITE_LOC
-        eng "Can't write to backup location '%-.64s' (file already exists?)"
+        eng "Can't write to backup location '%-.64s' (file already exists or running with --secure-file-priv option?)"
 ER_BACKUP_LIST_DBS
         eng "Can't enumerate server databases"
 ER_BACKUP_LIST_TABLES

Thread
bzr commit into mysql-6.0-backup branch (jorgen.loland:2679) Bug#34171Jorgen Loland21 Aug
  • Re: bzr commit into mysql-6.0-backup branch (jorgen.loland:2679)Bug#34171Sergei Golubchik21 Aug
    • Re: bzr commit into mysql-6.0-backup branch (jorgen.loland:2679)Bug#34171Jørgen Løland21 Aug
  • RE: bzr commit into mysql-6.0-backup branch (jorgen.loland:2679) Bug#34171Chuck Bell21 Aug
  • Re: bzr commit into mysql-6.0-backup branch (jorgen.loland:2679) Bug#34171Alexander Nozdrin21 Aug