From: Date: February 14 2007 2:44pm Subject: bk commit into 5.0 tree (msvensson:1.2395) BUG#18628 List-Archive: http://lists.mysql.com/commits/19868 X-Bug: 18628 Message-Id: <200702141344.l1EDieIi014768@pilot> Below is the list of changes that have just been committed into a local 5.0 repository of msvensson. When msvensson does a push these changes will be propagated to the main repository and, within 24 hours after the push, to the public repository. For information on how to access the public repository see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html ChangeSet@stripped, 2007-02-14 14:44:34+01:00, msvensson@stripped +17 -0 Bug#18628 mysql-test-run: security problem(part1) - Implement --secure-file-priv= option that limits "load_file", "LOAD DATA" and "SELECT .. INTO OUTFILE" to work with files in specified dir. - Use above option for mysqld in mysql-test-run.pl mysql-test/mysql-test-run.pl@stripped, 2007-02-14 14:44:30+01:00, msvensson@stripped +6 -0 Add usage of --secure-file-priv=vardir when starting mysqld mysql-test/r/loaddata.result@stripped, 2007-02-14 14:44:30+01:00, msvensson@stripped +16 -0 Update test result after adding test to check that secure-file-priv works for "load data" and "load_file" mysql-test/r/outfile.result@stripped, 2007-02-14 14:44:30+01:00, msvensson@stripped +6 -1 Update result mysql-test/r/query_cache.result@stripped, 2007-02-14 14:44:30+01:00, msvensson@stripped +1 -1 Can't load from outside of vardir anymore mysql-test/r/type_blob.result@stripped, 2007-02-14 14:44:30+01:00, msvensson@stripped +12 -12 Can't load from outside of vardir anymore mysql-test/t/loaddata.test@stripped, 2007-02-14 14:44:30+01:00, msvensson@stripped +23 -0 Update test result after adding test to check that secure-file-priv works for "load data" and "load_file" mysql-test/t/outfile.test@stripped, 2007-02-14 14:44:30+01:00, msvensson@stripped +12 -0 Update test result after adding test to check that secure-file-priv works for "SELECT .. INTO OUTFILE" mysql-test/t/query_cache.test@stripped, 2007-02-14 14:44:30+01:00, msvensson@stripped +2 -2 Can't load from outside of vardir anymore mysql-test/t/type_blob.test@stripped, 2007-02-14 14:44:31+01:00, msvensson@stripped +13 -14 Can't load from outside of vardir anymore sql/item_strfunc.cc@stripped, 2007-02-14 14:44:31+01:00, msvensson@stripped +5 -0 Check that the path "load_file" uses for the file is within what's specified with --secure-file-priv sql/mysql_priv.h@stripped, 2007-02-14 14:44:31+01:00, msvensson@stripped +1 -0 Add secure_file_priv sql/mysqld.cc@stripped, 2007-02-14 14:44:31+01:00, msvensson@stripped +19 -1 Add "--secure_file_priv" sql/set_var.cc@stripped, 2007-02-14 14:44:31+01:00, msvensson@stripped +4 -0 Add variable "secure_file_priv" to "show variables" sql/share/errmsg.txt@stripped, 2007-02-14 14:44:32+01:00, msvensson@stripped +1 -1 Fix swedish error message for ER_OPTION_PREVENTS_STATMENT wich was hardcoded to --skip-grant-tables sql/sql_class.cc@stripped, 2007-02-14 14:44:31+01:00, msvensson@stripped +10 -2 Check that the path "load_file" uses for the file is within what's specified with --secure-file-priv sql/sql_class.h@stripped, 2007-02-14 14:44:31+01:00, msvensson@stripped +1 -1 Fix spelling error sql/sql_load.cc@stripped, 2007-02-14 14:44:31+01:00, msvensson@stripped +9 -0 Check that the path "load_file" uses for the file is within what's specified with --secure-file-priv # This is a BitKeeper patch. What follows are the unified diffs for the # set of deltas contained in the patch. The rest of the patch, the part # that BitKeeper cares about, is below these diffs. # User: msvensson # Host: pilot.mysql.com # Root: /home/msvensson/mysql/bug18628/my50-bug18628 --- 1.292/sql/item_strfunc.cc 2006-12-31 09:39:17 +01:00 +++ 1.293/sql/item_strfunc.cc 2007-02-14 14:44:31 +01:00 @@ -2767,6 +2767,11 @@ (void) fn_format(path, file_name->c_ptr(), mysql_real_data_home, "", MY_RELATIVE_PATH | MY_UNPACK_FILENAME); + /* Read only allowed from within dir specified by secure_file_priv */ + if (opt_secure_file_priv && + strncmp(opt_secure_file_priv, path, strlen(opt_secure_file_priv))) + goto err; + if (!my_stat(path, &stat_info, MYF(0))) goto err; --- 1.429/sql/mysql_priv.h 2007-01-17 19:43:41 +01:00 +++ 1.430/sql/mysql_priv.h 2007-02-14 14:44:31 +01:00 @@ -1259,6 +1259,7 @@ extern my_bool opt_readonly, lower_case_file_system; extern my_bool opt_enable_named_pipe, opt_sync_frm, opt_allow_suspicious_udfs; extern my_bool opt_secure_auth; +extern char* opt_secure_file_priv; extern my_bool opt_log_slow_admin_statements; extern my_bool sp_automatic_privileges, opt_noacl; extern my_bool opt_old_style_user_limits, trust_function_creators; --- 1.589/sql/mysqld.cc 2007-01-18 18:05:03 +01:00 +++ 1.590/sql/mysqld.cc 2007-02-14 14:44:31 +01:00 @@ -369,6 +369,7 @@ my_bool opt_readonly, use_temp_pool, relay_log_purge; my_bool opt_sync_frm, opt_allow_suspicious_udfs; my_bool opt_secure_auth= 0; +char* opt_secure_file_priv= 0; my_bool opt_log_slow_admin_statements= 0; my_bool lower_case_file_system= 0; my_bool opt_large_pages= 0; @@ -1145,6 +1146,7 @@ #endif x_free(opt_bin_logname); x_free(opt_relay_logname); + x_free(opt_secure_file_priv); bitmap_free(&temp_pool); free_max_user_conn(); #ifdef HAVE_REPLICATION @@ -4698,7 +4700,8 @@ OPT_TABLE_LOCK_WAIT_TIMEOUT, OPT_PORT_OPEN_TIMEOUT, OPT_MERGE, - OPT_INNODB_ROLLBACK_ON_TIMEOUT + OPT_INNODB_ROLLBACK_ON_TIMEOUT, + OPT_SECURE_FILE_PRIV }; @@ -5337,6 +5340,10 @@ {"secure-auth", OPT_SECURE_AUTH, "Disallow authentication for accounts that have old (pre-4.1) passwords.", (gptr*) &opt_secure_auth, (gptr*) &opt_secure_auth, 0, GET_BOOL, NO_ARG, my_bool(0), 0, 0, 0, 0, 0}, + {"secure-file-priv", OPT_SECURE_FILE_PRIV, + "Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files within specified directory", + (gptr*) &opt_secure_file_priv, (gptr*) &opt_secure_file_priv, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"server-id", OPT_SERVER_ID, "Uniquely identifies the server instance in the community of replication partners.", (gptr*) &server_id, (gptr*) &server_id, 0, GET_ULONG, REQUIRED_ARG, 0, 0, 0, @@ -6366,6 +6373,7 @@ opt_logname= opt_update_logname= opt_binlog_index_name= opt_slow_logname= 0; opt_tc_log_file= (char *)"tc.log"; // no hostname in tc_log file name ! opt_secure_auth= 0; + opt_secure_file_priv= 0; opt_bootstrap= opt_myisam_log= 0; mqh_used= 0; segfaulted= kill_in_progress= 0; @@ -7404,6 +7412,16 @@ exit(1); } #endif /* HAVE_REPLICATION */ + /* + Convert the secure-file-priv option to system format, allowing + a quick strcmp to check if read or write is in an allowed dir + */ + if (opt_secure_file_priv) + { + convert_dirname(buff, opt_secure_file_priv, NullS); + my_free(opt_secure_file_priv, MYF(0)); + opt_secure_file_priv= my_strdup(buff, MYF(MY_FAE)); + } } --- 1.256/sql/sql_class.cc 2007-01-11 19:52:55 +01:00 +++ 1.257/sql/sql_class.cc 2007-02-14 14:44:31 +01:00 @@ -1084,7 +1084,7 @@ IO_CACHE *cache) { File file; - uint option= MY_UNPACK_FILENAME; + uint option= MY_UNPACK_FILENAME | MY_RELATIVE_PATH; #ifdef DONT_ALLOW_FULL_LOAD_DATA_PATHS option|= MY_REPLACE_DIR; // Force use of db directory @@ -1097,7 +1097,15 @@ } else (void) fn_format(path, exchange->file_name, mysql_real_data_home, "", option); - + + if (opt_secure_file_priv && + strncmp(opt_secure_file_priv, path, strlen(opt_secure_file_priv))) + { + /* Write only allowed to dir or subdir specified by secure_file_priv */ + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--secure-file-priv"); + return -1; + } + if (!access(path, F_OK)) { my_error(ER_FILE_EXISTS_ERROR, MYF(0), exchange->file_name); --- 1.317/sql/sql_class.h 2007-01-18 18:05:04 +01:00 +++ 1.318/sql/sql_class.h 2007-02-14 14:44:31 +01:00 @@ -1674,7 +1674,7 @@ #define SYSTEM_THREAD_SLAVE_SQL 4 /* - Used to hold information about file and file structure in exchainge + Used to hold information about file and file structure in exchange via non-DB file (...INTO OUTFILE..., ...LOAD DATA...) XXX: We never call destructor for objects of this class. */ --- 1.104/sql/sql_load.cc 2006-12-30 21:02:07 +01:00 +++ 1.105/sql/sql_load.cc 2007-02-14 14:44:31 +01:00 @@ -302,6 +302,15 @@ if ((stat_info.st_mode & S_IFIFO) == S_IFIFO) is_fifo = 1; #endif + + if (opt_secure_file_priv && + strncmp(opt_secure_file_priv, name, strlen(opt_secure_file_priv))) + { + /* Read only allowed from within dir specified by secure_file_priv */ + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--secure-file-priv"); + DBUG_RETURN(TRUE); + } + } if ((file=my_open(name,O_RDONLY,MYF(MY_WME))) < 0) DBUG_RETURN(TRUE); --- 1.75/sql/share/errmsg.txt 2006-11-13 17:06:41 +01:00 +++ 1.76/sql/share/errmsg.txt 2007-02-14 14:44:32 +01:00 @@ -5045,7 +5045,7 @@ ger "Der MySQL-Server läuft mit der Option %s und kann diese Anweisung deswegen nicht ausführen" por "O servidor MySQL está rodando com a opção %s razão pela qual não pode executar esse commando" spa "El servidor MySQL está rodando con la opción %s tal que no puede ejecutar este comando" - swe "MySQL är startad med --skip-grant-tables. Pga av detta kan du inte använda detta kommando" + swe "MySQL är startad med %s. Pga av detta kan du inte använda detta kommando" ER_DUPLICATED_VALUE_IN_TYPE eng "Column '%-.100s' has duplicated value '%-.64s' in %s" ger "Feld '%-.100s' hat doppelten Wert '%-.64s' in %s" Binary files /tmp/bk_outfile.result-1.6_1O33vX and /tmp/bk_outfile.result-1.7_aWd8fv differ --- 1.191/mysql-test/mysql-test-run.pl 2007-01-22 08:59:23 +01:00 +++ 1.192/mysql-test/mysql-test-run.pl 2007-02-14 14:44:30 +01:00 @@ -3581,6 +3581,12 @@ mtr_add_arg($args, "%s--basedir=%s", $prefix, $path_my_basedir); mtr_add_arg($args, "%s--character-sets-dir=%s", $prefix, $path_charsetsdir); + if ( $mysql_version_id >= 50036) + { + # Prevent the started mysqld to access files outside of vardir + mtr_add_arg($args, "%s--secure-file-priv=%s", $prefix, $opt_vardir); + } + if ( $mysql_version_id >= 50000 ) { mtr_add_arg($args, "%s--log-bin-trust-function-creators", $prefix); --- 1.53/mysql-test/r/type_blob.result 2006-10-04 13:09:34 +02:00 +++ 1.54/mysql-test/r/type_blob.result 2007-02-14 14:44:30 +01:00 @@ -506,26 +506,26 @@ Warnings: Warning 1101 BLOB/TEXT column 'imagem' can't have a default value insert into t1 (id) values (1); -select -charset(load_file('../../std_data/words.dat')), -collation(load_file('../../std_data/words.dat')), -coercibility(load_file('../../std_data/words.dat')); -charset(load_file('../../std_data/words.dat')) collation(load_file('../../std_data/words.dat')) coercibility(load_file('../../std_data/words.dat')) +select +charset(load_file('../std_data_ln/words.dat')), +collation(load_file('../std_data_ln/words.dat')), +coercibility(load_file('../std_data_ln/words.dat')); +charset(load_file('../std_data_ln/words.dat')) collation(load_file('../std_data_ln/words.dat')) coercibility(load_file('../std_data_ln/words.dat')) binary binary 4 -explain extended select -charset(load_file('../../std_data/words.dat')), -collation(load_file('../../std_data/words.dat')), -coercibility(load_file('../../std_data/words.dat')); +explain extended select +charset(load_file('MYSQLTEST_VARDIR/std_data_ln/words.dat')), +collation(load_file('MYSQLTEST_VARDIR/std_data_ln/words.dat')), +coercibility(load_file('MYSQLTEST_VARDIR/std_data_ln/words.dat')); id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE NULL NULL NULL NULL NULL NULL NULL No tables used Warnings: -Note 1003 select charset(load_file(_latin1'../../std_data/words.dat')) AS `charset(load_file('../../std_data/words.dat'))`,collation(load_file(_latin1'../../std_data/words.dat')) AS `collation(load_file('../../std_data/words.dat'))`,coercibility(load_file(_latin1'../../std_data/words.dat')) AS `coercibility(load_file('../../std_data/words.dat'))` -update t1 set imagem=load_file('../../std_data/words.dat') where id=1; +Note 1003 select charset(load_file(_latin1'MYSQLTEST_VARDIR/std_data_ln/words.dat')) AS `charset(load_file('MYSQLTEST_VARDIR/std_data_ln/words.dat'))`,collation(load_file(_latin1'MYSQLTEST_VARDIR/std_data_ln/words.dat')) AS `collation(load_file('MYSQLTEST_VARDIR/std_data_ln/words.dat'))`,coercibility(load_file(_latin1'MYSQLTEST_VARDIR/std_data_ln/words.dat')) AS `coercibility(load_file('MYSQLTEST_VARDIR/std_data_ln/words.dat'))` +update t1 set imagem=load_file('MYSQLTEST_VARDIR/std_data_ln/words.dat') where id=1; select if(imagem is null, "ERROR", "OK"),length(imagem) from t1 where id = 1; if(imagem is null, "ERROR", "OK") length(imagem) OK 581 drop table t1; -create table t1 select load_file('../../std_data/words.dat') l; +create table t1 select load_file('MYSQLTEST_VARDIR/std_data_ln/words.dat') l; show full fields from t1; Field Type Collation Null Key Default Extra Privileges Comment l longblob NULL YES NULL # --- 1.14/mysql-test/t/outfile.test 2006-05-12 09:15:11 +02:00 +++ 1.15/mysql-test/t/outfile.test 2007-02-14 14:44:30 +01:00 @@ -84,3 +84,15 @@ FROM schemata LIMIT 0, 5; enable_query_log; --exec rm $MYSQLTEST_VARDIR/tmp/outfile-test.4 +use test; + +# +# Bug#18628 mysql-test-run: security problem +# +# It should not be possible to write to a file outside of vardir +create table t1(a int); +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +--error 1290 +eval select * into outfile "$MYSQL_TEST_DIR/outfile-test1" from t1; +drop table t1; + --- 1.33/mysql-test/t/type_blob.test 2006-10-03 16:14:19 +02:00 +++ 1.34/mysql-test/t/type_blob.test 2007-02-14 14:44:31 +01:00 @@ -307,22 +307,21 @@ create table t1 (id integer auto_increment unique,imagem LONGBLOB not null default ''); insert into t1 (id) values (1); # We have to clean up the path in the results for safe comparison ---replace_result $MYSQL_TEST_DIR ../.. -eval select - charset(load_file('$MYSQL_TEST_DIR/std_data/words.dat')), - collation(load_file('$MYSQL_TEST_DIR/std_data/words.dat')), - coercibility(load_file('$MYSQL_TEST_DIR/std_data/words.dat')); ---replace_result $MYSQL_TEST_DIR ../.. -eval explain extended select - charset(load_file('$MYSQL_TEST_DIR/std_data/words.dat')), - collation(load_file('$MYSQL_TEST_DIR/std_data/words.dat')), - coercibility(load_file('$MYSQL_TEST_DIR/std_data/words.dat')); ---replace_result $MYSQL_TEST_DIR ../.. -eval update t1 set imagem=load_file('$MYSQL_TEST_DIR/std_data/words.dat') where id=1; +eval select + charset(load_file('../std_data_ln/words.dat')), + collation(load_file('../std_data_ln/words.dat')), + coercibility(load_file('../std_data_ln/words.dat')); +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +eval explain extended select + charset(load_file('$MYSQLTEST_VARDIR/std_data_ln/words.dat')), + collation(load_file('$MYSQLTEST_VARDIR/std_data_ln/words.dat')), + coercibility(load_file('$MYSQLTEST_VARDIR/std_data_ln/words.dat')); +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +eval update t1 set imagem=load_file('$MYSQLTEST_VARDIR/std_data_ln/words.dat') where id=1; select if(imagem is null, "ERROR", "OK"),length(imagem) from t1 where id = 1; drop table t1; ---replace_result $MYSQL_TEST_DIR ../.. -eval create table t1 select load_file('$MYSQL_TEST_DIR/std_data/words.dat') l; +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +eval create table t1 select load_file('$MYSQLTEST_VARDIR/std_data_ln/words.dat') l; # We mask out the Privileges column because it differs for embedded server --replace_column 8 # show full fields from t1; --- 1.77/mysql-test/r/query_cache.result 2006-11-16 20:19:22 +01:00 +++ 1.78/mysql-test/r/query_cache.result 2007-02-14 14:44:30 +01:00 @@ -622,7 +622,7 @@ show status like "Qcache_queries_in_cache"; Variable_name Value Qcache_queries_in_cache 1 -load data infile 'TEST_DIR/std_data/words.dat' into table t1; +load data infile 'MYSQLTEST_VARDIR/std_data_ln/words.dat' into table t1; show status like "Qcache_queries_in_cache"; Variable_name Value Qcache_queries_in_cache 0 --- 1.56/mysql-test/t/query_cache.test 2006-11-16 20:19:23 +01:00 +++ 1.57/mysql-test/t/query_cache.test 2007-02-14 14:44:30 +01:00 @@ -405,8 +405,8 @@ create table t1 (word char(20) not null); select * from t1; show status like "Qcache_queries_in_cache"; ---replace_result $MYSQL_TEST_DIR TEST_DIR -eval load data infile '$MYSQL_TEST_DIR/std_data/words.dat' into table t1; +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +eval load data infile '$MYSQLTEST_VARDIR/std_data_ln/words.dat' into table t1; show status like "Qcache_queries_in_cache"; select count(*) from t1; drop table t1; --- 1.176/sql/set_var.cc 2007-01-12 12:22:49 +01:00 +++ 1.177/sql/set_var.cc 2007-02-14 14:44:31 +01:00 @@ -358,6 +358,8 @@ &SV::query_cache_wlock_invalidate); #endif /* HAVE_QUERY_CACHE */ sys_var_bool_ptr sys_secure_auth("secure_auth", &opt_secure_auth); +sys_var_const_str_ptr sys_secure_file_priv("secure_file_priv", + &opt_secure_file_priv); sys_var_long_ptr sys_server_id("server_id", &server_id, fix_server_id); sys_var_bool_ptr sys_slave_compressed_protocol("slave_compressed_protocol", &opt_slave_compressed_protocol); @@ -719,6 +721,7 @@ &sys_rpl_recovery_rank, &sys_safe_updates, &sys_secure_auth, + &sys_secure_file_priv, &sys_select_limit, &sys_server_id, #ifdef HAVE_REPLICATION @@ -1027,6 +1030,7 @@ #endif {sys_rpl_recovery_rank.name,(char*) &sys_rpl_recovery_rank, SHOW_SYS}, {"secure_auth", (char*) &sys_secure_auth, SHOW_SYS}, + {"secure_file_priv", (char*) &sys_secure_file_priv, SHOW_SYS}, #ifdef HAVE_SMEM {"shared_memory", (char*) &opt_enable_shared_memory, SHOW_MY_BOOL}, {"shared_memory_base_name", (char*) &shared_memory_base_name, SHOW_CHAR_PTR}, --- 1.24/mysql-test/r/loaddata.result 2006-07-20 10:41:05 +02:00 +++ 1.25/mysql-test/r/loaddata.result 2007-02-14 14:44:30 +01:00 @@ -139,4 +139,20 @@ a b c 10 NULL Ten 15 NULL Fifteen +show variables like "secure_file_pri%"; +Variable_name Value +secure_file_priv MYSQLTEST_VARDIR/ +select @@secure_file_priv; +@@secure_file_priv +MYSQLTEST_VARDIR/ +set @@secure_file_priv= 0; +ERROR HY000: Variable 'secure_file_priv' is a read only variable +truncate table t1; +load data infile 'MYSQL_TEST_DIR/Makefile' into table t1; +ERROR HY000: The MySQL server is running with the --secure-file-priv option so it cannot execute this statement +select * from t1; +a b c +select load_file("MYSQL_TEST_DIR/Makefile"); +load_file("MYSQL_TEST_DIR/Makefile") +NULL drop table t1, t2; --- 1.18/mysql-test/t/loaddata.test 2006-03-29 17:56:08 +02:00 +++ 1.19/mysql-test/t/loaddata.test 2007-02-14 14:44:30 +01:00 @@ -110,6 +110,29 @@ load data infile '../std_data_ln/rpl_loaddata.dat' into table t1 (@dummy,@n) set a= @n, c= (select str from t2 where num=@n); select * from t1; +# +# Bug#18628 mysql-test-run: security problem +# +# It should not be possible to load from a file outside of vardir + +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +show variables like "secure_file_pri%"; +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +select @@secure_file_priv; +--error 1238 +set @@secure_file_priv= 0; + +# Test "load data" +truncate table t1; +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +--error 1290 +eval load data infile '$MYSQL_TEST_DIR/Makefile' into table t1; +select * from t1; + +# Test "load_file" returns NULL +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +eval select load_file("$MYSQL_TEST_DIR/Makefile"); + # cleanup drop table t1, t2;