summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xmysql-test/mysql-test-run.pl6
-rw-r--r--mysql-test/r/loaddata.result16
-rw-r--r--mysql-test/r/outfile.resultbin1159 -> 1382 bytes
-rw-r--r--mysql-test/r/query_cache.result2
-rw-r--r--mysql-test/r/type_blob.result24
-rw-r--r--mysql-test/t/loaddata.test23
-rw-r--r--mysql-test/t/outfile.test12
-rw-r--r--mysql-test/t/query_cache.test4
-rw-r--r--mysql-test/t/type_blob.test27
-rw-r--r--sql/item_strfunc.cc5
-rw-r--r--sql/mysql_priv.h1
-rw-r--r--sql/mysqld.cc20
-rw-r--r--sql/set_var.cc4
-rw-r--r--sql/share/errmsg.txt2
-rw-r--r--sql/sql_class.cc12
-rw-r--r--sql/sql_class.h2
-rw-r--r--sql/sql_load.cc9
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
index 040dff576f8..023c4ea205f 100644
--- a/mysql-test/r/outfile.result
+++ b/mysql-test/r/outfile.result
Binary files differ
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);