diff options
-rwxr-xr-x | mysql-test/mysql-test-run.pl | 6 | ||||
-rw-r--r-- | mysql-test/r/loaddata.result | 16 | ||||
-rw-r--r-- | mysql-test/r/outfile.result | bin | 1159 -> 1382 bytes | |||
-rw-r--r-- | mysql-test/r/query_cache.result | 2 | ||||
-rw-r--r-- | mysql-test/r/type_blob.result | 24 | ||||
-rw-r--r-- | mysql-test/t/loaddata.test | 23 | ||||
-rw-r--r-- | mysql-test/t/outfile.test | 12 | ||||
-rw-r--r-- | mysql-test/t/query_cache.test | 4 | ||||
-rw-r--r-- | mysql-test/t/type_blob.test | 27 | ||||
-rw-r--r-- | sql/item_strfunc.cc | 5 | ||||
-rw-r--r-- | sql/mysql_priv.h | 1 | ||||
-rw-r--r-- | sql/mysqld.cc | 20 | ||||
-rw-r--r-- | sql/set_var.cc | 4 | ||||
-rw-r--r-- | sql/share/errmsg.txt | 2 | ||||
-rw-r--r-- | sql/sql_class.cc | 12 | ||||
-rw-r--r-- | sql/sql_class.h | 2 | ||||
-rw-r--r-- | sql/sql_load.cc | 9 |
17 files changed, 135 insertions, 34 deletions
diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index ca967655e06..c4ce4317fe7 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -3581,6 +3581,12 @@ sub mysqld_arguments ($$$$$) { 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); diff --git a/mysql-test/r/loaddata.result b/mysql-test/r/loaddata.result index d415bd468e0..83c7b37d914 100644 --- a/mysql-test/r/loaddata.result +++ b/mysql-test/r/loaddata.result @@ -139,4 +139,20 @@ select * from t1; 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; diff --git a/mysql-test/r/outfile.result b/mysql-test/r/outfile.result Binary files differindex 040dff576f8..023c4ea205f 100644 --- a/mysql-test/r/outfile.result +++ b/mysql-test/r/outfile.result diff --git a/mysql-test/r/query_cache.result b/mysql-test/r/query_cache.result index d2b022a93fb..d77745176f7 100644 --- a/mysql-test/r/query_cache.result +++ b/mysql-test/r/query_cache.result @@ -622,7 +622,7 @@ word 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 diff --git a/mysql-test/r/type_blob.result b/mysql-test/r/type_blob.result index 73b67a2241e..95ba9bb6f25 100644 --- a/mysql-test/r/type_blob.result +++ b/mysql-test/r/type_blob.result @@ -506,26 +506,26 @@ create table t1 (id integer auto_increment unique,imagem LONGBLOB not null defau 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 # diff --git a/mysql-test/t/loaddata.test b/mysql-test/t/loaddata.test index 27c8005ca0c..0dc91c36a09 100644 --- a/mysql-test/t/loaddata.test +++ b/mysql-test/t/loaddata.test @@ -110,6 +110,29 @@ truncate table t1; 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; diff --git a/mysql-test/t/outfile.test b/mysql-test/t/outfile.test index c48e6c9730d..7c90fd32909 100644 --- a/mysql-test/t/outfile.test +++ b/mysql-test/t/outfile.test @@ -84,3 +84,15 @@ FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' 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; + diff --git a/mysql-test/t/query_cache.test b/mysql-test/t/query_cache.test index f6e480282ff..427334805ce 100644 --- a/mysql-test/t/query_cache.test +++ b/mysql-test/t/query_cache.test @@ -405,8 +405,8 @@ select * from t1 where id=2; 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; diff --git a/mysql-test/t/type_blob.test b/mysql-test/t/type_blob.test index 6d79dcc863b..ba9f374a24c 100644 --- a/mysql-test/t/type_blob.test +++ b/mysql-test/t/type_blob.test @@ -307,22 +307,21 @@ drop table t1; 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; diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index f98a14d2cc6..6897ffda56a 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -2767,6 +2767,11 @@ String *Item_load_file::val_str(String *str) (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; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 8ffa73d1707..f27dabab832 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1259,6 +1259,7 @@ extern my_bool opt_slave_compressed_protocol, use_temp_pool; 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; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index e57e46bc30e..87760c3ded5 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -369,6 +369,7 @@ ulong opt_ndb_nodeid; 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 @@ void clean_up(bool print_message) #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 @@ enum options_mysqld 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 @@ Can't be set to 1 if --log-slave-updates is used.", {"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 @@ static void mysql_init_variables(void) 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 @@ static void fix_paths(void) 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)); + } } diff --git a/sql/set_var.cc b/sql/set_var.cc index 9b02a192fe5..8562d052411 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -358,6 +358,8 @@ sys_query_cache_wlock_invalidate("query_cache_wlock_invalidate", &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_var *sys_variables[]= &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 @@ struct show_var_st init_vars[]= { #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}, diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index e09629a1f3e..adafdf2818b 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5045,7 +5045,7 @@ ER_OPTION_PREVENTS_STATEMENT 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" diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 0794d4c797a..8fc0fb0a1b7 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1084,7 +1084,7 @@ static File create_file(THD *thd, char *path, sql_exchange *exchange, 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 @@ static File create_file(THD *thd, char *path, sql_exchange *exchange, } 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); diff --git a/sql/sql_class.h b/sql/sql_class.h index 05034ebd573..1fe5ef54043 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1674,7 +1674,7 @@ public: #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. */ diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 0e4057d9ae4..ede70adc378 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -302,6 +302,15 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, 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); |