diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index f0100593516cdfa72b4e268db6a0c0050c97d8a9..f43cadd3784d9d83aa83a2453f7f117b38d42bfc 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -2412,17 +2412,9 @@ sub setup_vardir() { mkpath("$data_dir/test"); } - # Make a link std_data_ln in var/ that points to std_data - if ( ! $glob_win32 ) - { - symlink("$glob_mysql_test_dir/std_data", "$opt_vardir/std_data_ln"); - } - else - { - # on windows, copy all files from std_data into var/std_data_ln - mkpath("$opt_vardir/std_data_ln"); - mtr_copy_dir("$glob_mysql_test_dir/std_data", "$opt_vardir/std_data_ln"); - } + # copy all files from std_data into var/std_data_ln + mkpath("$opt_vardir/std_data_ln"); + mtr_copy_dir("$glob_mysql_test_dir/std_data", "$opt_vardir/std_data_ln"); # Remove old log files foreach my $name (glob("r/*.progress r/*.log r/*.warnings")) diff --git a/mysql-test/r/secure_file_priv_win.result b/mysql-test/r/secure_file_priv_win.result new file mode 100644 index 0000000000000000000000000000000000000000..497a5d04b1fa86bbe08458ef0bc207a5bfcf6653 --- /dev/null +++ b/mysql-test/r/secure_file_priv_win.result @@ -0,0 +1,38 @@ +CREATE TABLE t1 (c1 longtext); +INSERT INTO t1 values ('a'); +SELECT * FROM t1 INTO OUTFILE 'd:/mysql/work/test-5.0-security/mysql-test/var/tmp/B11764517.tmp'; +show global variables like 'secure_file_priv'; +Variable_name Value +secure_file_priv MYSQL_TMP_DIR/ +SELECT load_file('MYSQL_TMP_DIR\\B11764517.tmp') AS x; +x +a + +SELECT load_file('MYSQL_TMP_DIR/B11764517.tmp') AS x; +x +a + +SELECT load_file('MYSQL_TMP_DIR_UCASE/B11764517.tmp') AS x; +x +a + +SELECT load_file('MYSQL_TMP_DIR_LCASE/B11764517.tmp') AS x; +x +a + +SELECT load_file('MYSQL_TMP_DIR\\..a..\\..\\..\\B11764517.tmp') AS x; +x +NULL +LOAD DATA INFILE 'MYSQL_TMP_DIR\\B11764517.tmp' INTO TABLE t1; +LOAD DATA INFILE 'MYSQL_TMP_DIR/B11764517.tmp' INTO TABLE t1; +LOAD DATA INFILE 'MYSQL_TMP_DIR_UCASE/B11764517.tmp' INTO TABLE t1; +LOAD DATA INFILE 'MYSQL_TMP_DIR_LCASE/B11764517.tmp' INTO TABLE t1; +LOAD DATA INFILE "MYSQL_TMP_DIR\\..a..\\..\\..\\B11764517.tmp" 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 INTO OUTFILE 'MYSQL_TMP_DIR\\..a..\\..\\..\\B11764517-2.tmp'; +ERROR HY000: The MySQL server is running with the --secure-file-priv option so it cannot execute this statement +SELECT * FROM t1 INTO OUTFILE 'MYSQL_TMP_DIR\\B11764517-2.tmp'; +SELECT * FROM t1 INTO OUTFILE 'MYSQL_TMP_DIR/B11764517-3.tmp'; +SELECT * FROM t1 INTO OUTFILE 'MYSQL_TMP_DIR_UCASE/B11764517-4.tmp'; +SELECT * FROM t1 INTO OUTFILE 'MYSQL_TMP_DIR_LCASE/B11764517-5.tmp'; +DROP TABLE t1; diff --git a/mysql-test/t/secure_file_priv_win-master.opt b/mysql-test/t/secure_file_priv_win-master.opt new file mode 100644 index 0000000000000000000000000000000000000000..e9a43a5584d8fb7f4536c50c25b34c753975b633 --- /dev/null +++ b/mysql-test/t/secure_file_priv_win-master.opt @@ -0,0 +1 @@ +--secure_file_priv=$MYSQL_TMP_DIR diff --git a/mysql-test/t/secure_file_priv_win.test b/mysql-test/t/secure_file_priv_win.test new file mode 100644 index 0000000000000000000000000000000000000000..07e012e42b46824b44a02a5139cb918d0dde5bf1 --- /dev/null +++ b/mysql-test/t/secure_file_priv_win.test @@ -0,0 +1,79 @@ +# +# Bug58747 breaks secure_file_priv+not secure yet+still accesses other folders +# + +# we do the windows specific relative directory testing + +--source include/windows.inc + +CREATE TABLE t1 (c1 longtext); +INSERT INTO t1 values ('a'); + +LET $MYSQL_TMP_DIR_UCASE= `SELECT upper('$MYSQL_TMP_DIR')`; +LET $MYSQL_TMP_DIR_LCASE= `SELECT lower('$MYSQL_TMP_DIR')`; + +#create the file +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +eval SELECT * FROM t1 INTO OUTFILE '$MYSQL_TMP_DIR_LCASE/B11764517.tmp'; + +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +show global variables like 'secure_file_priv'; + +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +eval SELECT load_file('$MYSQL_TMP_DIR\\\\B11764517.tmp') AS x; + +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +eval SELECT load_file('$MYSQL_TMP_DIR/B11764517.tmp') AS x; + +--replace_result $MYSQL_TMP_DIR_UCASE MYSQL_TMP_DIR_UCASE +eval SELECT load_file('$MYSQL_TMP_DIR_UCASE/B11764517.tmp') AS x; + +--replace_result $MYSQL_TMP_DIR_LCASE MYSQL_TMP_DIR_LCASE +eval SELECT load_file('$MYSQL_TMP_DIR_LCASE/B11764517.tmp') AS x; + +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +eval SELECT load_file('$MYSQL_TMP_DIR\\\\..a..\\\\..\\\\..\\\\B11764517.tmp') AS x; + +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +eval LOAD DATA INFILE '$MYSQL_TMP_DIR\\\\B11764517.tmp' INTO TABLE t1; + +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +eval LOAD DATA INFILE '$MYSQL_TMP_DIR/B11764517.tmp' INTO TABLE t1; + +--replace_result $MYSQL_TMP_DIR_UCASE MYSQL_TMP_DIR_UCASE +eval LOAD DATA INFILE '$MYSQL_TMP_DIR_UCASE/B11764517.tmp' INTO TABLE t1; + +--replace_result $MYSQL_TMP_DIR_LCASE MYSQL_TMP_DIR_LCASE +eval LOAD DATA INFILE '$MYSQL_TMP_DIR_LCASE/B11764517.tmp' INTO TABLE t1; + +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--error ER_OPTION_PREVENTS_STATEMENT +eval LOAD DATA INFILE "$MYSQL_TMP_DIR\\\\..a..\\\\..\\\\..\\\\B11764517.tmp" into table t1; + +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +--error ER_OPTION_PREVENTS_STATEMENT +eval SELECT * FROM t1 INTO OUTFILE '$MYSQL_TMP_DIR\\\\..a..\\\\..\\\\..\\\\B11764517-2.tmp'; + +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +eval SELECT * FROM t1 INTO OUTFILE '$MYSQL_TMP_DIR\\\\B11764517-2.tmp'; + +--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR +eval SELECT * FROM t1 INTO OUTFILE '$MYSQL_TMP_DIR/B11764517-3.tmp'; + +--replace_result $MYSQL_TMP_DIR_UCASE MYSQL_TMP_DIR_UCASE +eval SELECT * FROM t1 INTO OUTFILE '$MYSQL_TMP_DIR_UCASE/B11764517-4.tmp'; + +--replace_result $MYSQL_TMP_DIR_LCASE MYSQL_TMP_DIR_LCASE +eval SELECT * FROM t1 INTO OUTFILE '$MYSQL_TMP_DIR_LCASE/B11764517-5.tmp'; + +--error 0,1 +--remove_file $MYSQL_TMP_DIR/B11764517.tmp; +--error 0,1 +--remove_file $MYSQL_TMP_DIR/B11764517-2.tmp; +--error 0,1 +--remove_file $MYSQL_TMP_DIR/B11764517-3.tmp; +--error 0,1 +--remove_file $MYSQL_TMP_DIR/B11764517-4.tmp; +--error 0,1 +--remove_file $MYSQL_TMP_DIR/B11764517-5.tmp; +DROP TABLE t1; diff --git a/mysys/my_symlink.c b/mysys/my_symlink.c index 7f2be5644e8a5a74b7132f46582f88399ae75400..e17cd8bbe0c227a8c8b35de5c9c64e423dcf8972 100644 --- a/mysys/my_symlink.c +++ b/mysys/my_symlink.c @@ -149,8 +149,23 @@ int my_realpath(char *to, const char *filename, result= -1; } DBUG_RETURN(result); +#elif defined(_WIN32) + int ret= GetFullPathName(filename,FN_REFLEN, to, NULL); + if (ret == 0 || ret > FN_REFLEN) + { + my_errno= (ret > FN_REFLEN) ? ENAMETOOLONG : GetLastError(); + if (MyFlags & MY_WME) + my_error(EE_REALPATH, MYF(0), filename, my_errno); + /* + GetFullPathName didn't work : use my_load_path() which is a poor + substitute original name but will at least be able to resolve + paths that starts with '.'. + */ + my_load_path(to, filename, NullS); + return -1; + } #else my_load_path(to, filename, NullS); +#endif return 0; -#endif } diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 6f697a1665a9cc7b11ab91f90b1cb6c039ed76c0..8f9a04329d3511a4b94c9a30a5125d67fb1f12ec 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -2843,8 +2843,7 @@ String *Item_load_file::val_str(String *str) 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))) + if (!is_secure_file_path(path)) goto err; if (!my_stat(path, &stat_info, MYF(0))) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index d88e629b91b60505b768fc05abb3f1a28218eef7..a811bbafdb6a133972d5231a219b0f57854c9c6c 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1264,6 +1264,8 @@ bool init_errmessage(void); bool fn_format_relative_to_data_home(my_string to, const char *name, const char *dir, const char *extension); +bool is_secure_file_path(char *path); + File open_binlog(IO_CACHE *log, const char *log_file_name, const char **errmsg); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index f026bab1c3259a4ceec4d0e0440f6fc870000dbe..3291085f380d9ee453b794e4b7586e361eb797e8 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -7855,6 +7855,64 @@ fn_format_relative_to_data_home(my_string to, const char *name, } +/** + Test a file path to determine if the path is compatible with the secure file + path restriction. + + @param path null terminated character string + + @return + @retval TRUE The path is secure + @retval FALSE The path isn't secure +*/ + +bool is_secure_file_path(char *path) +{ + char buff1[FN_REFLEN], buff2[FN_REFLEN]; + size_t opt_secure_file_priv_len; + /* + All paths are secure if opt_secure_file_path is 0 + */ + if (!opt_secure_file_priv) + return TRUE; + + opt_secure_file_priv_len= strlen(opt_secure_file_priv); + + if (strlen(path) >= FN_REFLEN) + return FALSE; + + if (my_realpath(buff1, path, 0)) + { + /* + The supplied file path might have been a file and not a directory. + */ + int length= (int) dirname_length(path); + if (length >= FN_REFLEN) + return FALSE; + memcpy(buff2, path, length); + buff2[length]= '\0'; + if (length == 0 || my_realpath(buff1, buff2, 0)) + return FALSE; + } + convert_dirname(buff2, buff1, NullS); + if (!lower_case_file_system) + { + if (strncmp(opt_secure_file_priv, buff2, opt_secure_file_priv_len)) + return FALSE; + } + else + { + if (files_charset_info->coll->strnncoll(files_charset_info, + (uchar *) buff2, strlen(buff2), + (uchar *) opt_secure_file_priv, + opt_secure_file_priv_len, + TRUE)) + return FALSE; + } + return TRUE; +} + + static void fix_paths(void) { char buff[FN_REFLEN],*pos; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 06f2229a050886d2b41664ab0652dfd861b48daf..cd2f2029ca2de6b6354a08c656c11fb64c6f6526 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1211,8 +1211,7 @@ 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))) + if (!is_secure_file_path(path)) { /* Write only allowed to dir or subdir specified by secure_file_priv */ my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--secure-file-priv"); diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 83af6d477dbf1de7d12f7cf4d10ce6132103fa69..9cead8c0ff1611bb0ec4080d6e187ea03664fec3 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -287,36 +287,36 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, { (void) fn_format(name, ex->file_name, mysql_real_data_home, "", MY_RELATIVE_PATH | MY_UNPACK_FILENAME); + } + + if (!is_secure_file_path(name)) + { + /* 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 !defined(__WIN__) && !defined(OS2) && ! defined(__NETWARE__) - MY_STAT stat_info; - if (!my_stat(name,&stat_info,MYF(MY_WME))) - DBUG_RETURN(TRUE); + MY_STAT stat_info; + if (!my_stat(name, &stat_info, MYF(MY_WME))) + DBUG_RETURN(TRUE); - // if we are not in slave thread, the file must be: - if (!thd->slave_thread && - !((stat_info.st_mode & S_IROTH) == S_IROTH && // readable by others + // if we are not in slave thread, the file must be: + if (!thd->slave_thread && + !((stat_info.st_mode & S_IROTH) == S_IROTH && // readable by others #ifndef __EMX__ - (stat_info.st_mode & S_IFLNK) != S_IFLNK && // and not a symlink + (stat_info.st_mode & S_IFLNK) != S_IFLNK && // and not a symlink #endif - ((stat_info.st_mode & S_IFREG) == S_IFREG || - (stat_info.st_mode & S_IFIFO) == S_IFIFO))) - { - my_error(ER_TEXTFILE_NOT_READABLE, MYF(0), name); - DBUG_RETURN(TRUE); - } - if ((stat_info.st_mode & S_IFIFO) == S_IFIFO) - is_fifo = 1; + ((stat_info.st_mode & S_IFREG) == S_IFREG || // and a regular file + (stat_info.st_mode & S_IFIFO) == S_IFIFO))) // or FIFO + { + my_error(ER_TEXTFILE_NOT_READABLE, MYF(0), name); + DBUG_RETURN(TRUE); + } + 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); }