Skip to content

Commit 1278f5d

Browse files
ssorumgardHery Ramilison
authored andcommitted
Bug#24388753: PRIVILEGE ESCALATION USING MYSQLD_SAFE
[This is the 5.7/8.0 version of the bugfix]. The problem was that it was possible to write log files ending in .ini/.cnf that later could be parsed as an options file. This made it possible for users to specify startup options without the permissions to do so. This patch fixes the problem by disallowing general query log and slow query log to be written to files ending in .ini and .cnf. (cherry picked from commit 173c8ae067098acdedf031740ab78b99652abbdb)
1 parent b4abd20 commit 1278f5d

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

sql/log.cc

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,100 @@ static int make_iso8601_timestamp(char *buf, ulonglong utime= 0)
519519
}
520520

521521

522+
bool is_valid_log_name(const char *name, size_t len)
523+
{
524+
if (len > 3)
525+
{
526+
const char *tail= name + len - 4;
527+
if (my_strcasecmp(system_charset_info, tail, ".ini") == 0 ||
528+
my_strcasecmp(system_charset_info, tail, ".cnf") == 0)
529+
{
530+
return false;
531+
}
532+
}
533+
return true;
534+
}
535+
536+
537+
/**
538+
Get the real log file name, and possibly reopen file.
539+
540+
The implementation is platform dependent due to differences in how this is
541+
supported:
542+
543+
On Windows, we get the actual path based on the file descriptor. This path is
544+
copied into the supplied buffer. The 'file' parameter is returned without
545+
re-opening.
546+
547+
On other platforms, we use realpath() to get the path with symbolic links
548+
expanded. Then, we close the file, and reopen the real path using the
549+
O_NOFOLLOW flag. This will reject folowing symbolic links.
550+
551+
@param file File descriptor.
552+
@param log_file_key Key for P_S instrumentation.
553+
@param open_flags Flags to use for opening the file.
554+
@param opened_file_name Name of the open fd.
555+
@param [out] real_file_name Buffer for actual name of the fd.
556+
557+
@retval file descriptor to open file with 'real_file_name', or '-1'
558+
in case of errors.
559+
*/
560+
561+
static File mysql_file_real_name_reopen(File file,
562+
#ifdef HAVE_PSI_INTERFACE
563+
PSI_file_key log_file_key,
564+
#endif
565+
int open_flags,
566+
const char *opened_file_name,
567+
char *real_file_name)
568+
{
569+
DBUG_ASSERT(file);
570+
DBUG_ASSERT(opened_file_name);
571+
DBUG_ASSERT(real_file_name);
572+
573+
#ifdef _WIN32
574+
/* On Windows, O_NOFOLLOW is not supported. Verify real path from fd. */
575+
DWORD real_length= GetFinalPathNameByHandle(my_get_osfhandle(file),
576+
real_file_name,
577+
FN_REFLEN,
578+
FILE_NAME_OPENED);
579+
580+
/* May ret 0 if e.g. on a ramdisk. Ignore - return open file and name. */
581+
if (real_length == 0)
582+
{
583+
strcpy(real_file_name, opened_file_name);
584+
return file;
585+
}
586+
587+
if (real_length > FN_REFLEN)
588+
{
589+
mysql_file_close(file, MYF(0));
590+
return -1;
591+
}
592+
593+
return file;
594+
#else
595+
/* On *nix, get realpath, open realpath with O_NOFOLLOW. */
596+
if (realpath(opened_file_name, real_file_name) == NULL)
597+
{
598+
(void) mysql_file_close(file, MYF(0));
599+
return -1;
600+
}
601+
602+
if (mysql_file_close(file, MYF(0)))
603+
return -1;
604+
605+
/* Make sure the real path is not too long. */
606+
if (strlen(real_file_name) > FN_REFLEN)
607+
return -1;
608+
609+
return mysql_file_open(log_file_key, real_file_name,
610+
open_flags | O_NOFOLLOW,
611+
MYF(MY_WME));
612+
#endif //_WIN32
613+
}
614+
615+
522616
bool File_query_log::open()
523617
{
524618
File file= -1;
@@ -552,12 +646,36 @@ bool File_query_log::open()
552646

553647
db[0]= 0;
554648

649+
/* First, open the file to make sure it exists. */
555650
if ((file= mysql_file_open(m_log_file_key,
556651
log_file_name,
557652
O_CREAT | O_BINARY | O_WRONLY | O_APPEND,
558653
MYF(MY_WME))) < 0)
559654
goto err;
560655

656+
#ifdef _WIN32
657+
char real_log_file_name[FN_REFLEN];
658+
#else
659+
/* File name must have room for PATH_MAX. Checked against F_REFLEN later. */
660+
char real_log_file_name[PATH_MAX];
661+
#endif // _Win32
662+
663+
/* Reopen and get real path. */
664+
if ((file= mysql_file_real_name_reopen(file,
665+
#ifdef HAVE_PSI_INTERFACE
666+
m_log_file_key,
667+
#endif
668+
O_CREAT | O_BINARY | O_WRONLY | O_APPEND,
669+
log_file_name, real_log_file_name)) < 0)
670+
goto err;
671+
672+
if (!is_valid_log_name(real_log_file_name, strlen(real_log_file_name)))
673+
{
674+
sql_print_error("Invalid log file name after expanding symlinks: '%s'",
675+
real_log_file_name);
676+
goto err;
677+
}
678+
561679
if ((pos= mysql_file_tell(file, MYF(MY_WME))) == MY_FILEPOS_ERROR)
562680
{
563681
if (my_errno() == ESPIPE)

sql/log.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,16 @@ extern Query_logger query_logger;
557557
*/
558558
char *make_query_log_name(char *buff, enum_log_table_type log_type);
559559

560+
/**
561+
Check given log name against certain blacklisted names/extensions.
562+
563+
@param name Log name to check
564+
@param len Length of log name
565+
566+
@returns true if name is valid, false otherwise.
567+
*/
568+
bool is_valid_log_name(const char *name, size_t len);
569+
560570
/**
561571
Check whether we need to write the current statement (or its rewritten
562572
version if it exists) to the slow query log.

sql/mysqld.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3022,6 +3022,22 @@ int init_common_variables()
30223022
"--slow-query-log-file option, log tables are used. "
30233023
"To enable logging to files use the --log-output=file option.");
30243024

3025+
if (opt_general_logname &&
3026+
!is_valid_log_name(opt_general_logname, strlen(opt_general_logname)))
3027+
{
3028+
sql_print_error("Invalid value for --general_log_file: %s",
3029+
opt_general_logname);
3030+
return 1;
3031+
}
3032+
3033+
if (opt_slow_logname &&
3034+
!is_valid_log_name(opt_slow_logname, strlen(opt_slow_logname)))
3035+
{
3036+
sql_print_error("Invalid value for --slow_query_log_file: %s",
3037+
opt_slow_logname);
3038+
return 1;
3039+
}
3040+
30253041
#define FIX_LOG_VAR(VAR, ALT) \
30263042
if (!VAR || !*VAR) \
30273043
VAR= ALT;

sql/sys_vars.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4658,6 +4658,14 @@ static bool check_log_path(sys_var *self, THD *thd, set_var *var)
46584658
if (!var->save_result.string_value.str)
46594659
return true;
46604660

4661+
if (!is_valid_log_name(var->save_result.string_value.str,
4662+
var->save_result.string_value.length))
4663+
{
4664+
my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0),
4665+
self->name.str, var->save_result.string_value.str);
4666+
return true;
4667+
}
4668+
46614669
if (var->save_result.string_value.length > FN_REFLEN)
46624670
{ // path is too long
46634671
my_error(ER_PATH_LENGTH, MYF(0), self->name.str);

0 commit comments

Comments
 (0)