Bug #11764517: 57359: POSSIBLE TO CIRCUMVENT SECURE_FILE_PRIV
USING '..' ON WINDOWS Backport of the fix to 5.0 (to be null-merged to 5.1). Moved the test into the main test suite. Made mysql-test-run.pl to not use symlinks for sdtdata as the symlinks are now properly recognized by secure_file_priv. Made sure the paths in load_file(), LOAD DATA and SELECT .. INTO OUTFILE that are checked against secure_file_priv in a correct way similarly to 5.1 by the extended is_secure_file_path() backport before the comparison. Added an extensive test with all the variants of upper/lower case, slash/backslash and case sensitivity. Added few comments to the code.
This commit is contained in:
parent
8f449c36de
commit
4c5dfc00f7
@ -2412,17 +2412,9 @@ sub setup_vardir() {
|
|||||||
mkpath("$data_dir/test");
|
mkpath("$data_dir/test");
|
||||||
}
|
}
|
||||||
|
|
||||||
# Make a link std_data_ln in var/ that points to std_data
|
# copy all files from std_data into var/std_data_ln
|
||||||
if ( ! $glob_win32 )
|
mkpath("$opt_vardir/std_data_ln");
|
||||||
{
|
mtr_copy_dir("$glob_mysql_test_dir/std_data", "$opt_vardir/std_data_ln");
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
# Remove old log files
|
# Remove old log files
|
||||||
foreach my $name (glob("r/*.progress r/*.log r/*.warnings"))
|
foreach my $name (glob("r/*.progress r/*.log r/*.warnings"))
|
||||||
|
38
mysql-test/r/secure_file_priv_win.result
Normal file
38
mysql-test/r/secure_file_priv_win.result
Normal file
@ -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;
|
1
mysql-test/t/secure_file_priv_win-master.opt
Normal file
1
mysql-test/t/secure_file_priv_win-master.opt
Normal file
@ -0,0 +1 @@
|
|||||||
|
--secure_file_priv=$MYSQL_TMP_DIR
|
79
mysql-test/t/secure_file_priv_win.test
Normal file
79
mysql-test/t/secure_file_priv_win.test
Normal file
@ -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;
|
@ -149,8 +149,23 @@ int my_realpath(char *to, const char *filename,
|
|||||||
result= -1;
|
result= -1;
|
||||||
}
|
}
|
||||||
DBUG_RETURN(result);
|
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
|
#else
|
||||||
my_load_path(to, filename, NullS);
|
my_load_path(to, filename, NullS);
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
@ -2843,8 +2843,7 @@ String *Item_load_file::val_str(String *str)
|
|||||||
MY_RELATIVE_PATH | MY_UNPACK_FILENAME);
|
MY_RELATIVE_PATH | MY_UNPACK_FILENAME);
|
||||||
|
|
||||||
/* Read only allowed from within dir specified by secure_file_priv */
|
/* Read only allowed from within dir specified by secure_file_priv */
|
||||||
if (opt_secure_file_priv &&
|
if (!is_secure_file_path(path))
|
||||||
strncmp(opt_secure_file_priv, path, strlen(opt_secure_file_priv)))
|
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
if (!my_stat(path, &stat_info, MYF(0)))
|
if (!my_stat(path, &stat_info, MYF(0)))
|
||||||
|
@ -1264,6 +1264,8 @@ bool init_errmessage(void);
|
|||||||
|
|
||||||
bool fn_format_relative_to_data_home(my_string to, const char *name,
|
bool fn_format_relative_to_data_home(my_string to, const char *name,
|
||||||
const char *dir, const char *extension);
|
const char *dir, const char *extension);
|
||||||
|
bool is_secure_file_path(char *path);
|
||||||
|
|
||||||
File open_binlog(IO_CACHE *log, const char *log_file_name,
|
File open_binlog(IO_CACHE *log, const char *log_file_name,
|
||||||
const char **errmsg);
|
const char **errmsg);
|
||||||
|
|
||||||
|
@ -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)
|
static void fix_paths(void)
|
||||||
{
|
{
|
||||||
char buff[FN_REFLEN],*pos;
|
char buff[FN_REFLEN],*pos;
|
||||||
|
@ -1211,8 +1211,7 @@ static File create_file(THD *thd, char *path, sql_exchange *exchange,
|
|||||||
else
|
else
|
||||||
(void) fn_format(path, exchange->file_name, mysql_real_data_home, "", option);
|
(void) fn_format(path, exchange->file_name, mysql_real_data_home, "", option);
|
||||||
|
|
||||||
if (opt_secure_file_priv &&
|
if (!is_secure_file_path(path))
|
||||||
strncmp(opt_secure_file_priv, path, strlen(opt_secure_file_priv)))
|
|
||||||
{
|
{
|
||||||
/* Write only allowed to dir or subdir specified by 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");
|
my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--secure-file-priv");
|
||||||
|
@ -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, "",
|
(void) fn_format(name, ex->file_name, mysql_real_data_home, "",
|
||||||
MY_RELATIVE_PATH | MY_UNPACK_FILENAME);
|
MY_RELATIVE_PATH | MY_UNPACK_FILENAME);
|
||||||
#if !defined(__WIN__) && !defined(OS2) && ! defined(__NETWARE__)
|
|
||||||
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
|
|
||||||
#ifndef __EMX__
|
|
||||||
(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;
|
|
||||||
#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 (!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);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
#endif
|
||||||
|
((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 ((file=my_open(name,O_RDONLY,MYF(MY_WME))) < 0)
|
if ((file=my_open(name,O_RDONLY,MYF(MY_WME))) < 0)
|
||||||
DBUG_RETURN(TRUE);
|
DBUG_RETURN(TRUE);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user