From 2135853b41f2d124f1869371154b0f9ab9a7b2da Mon Sep 17 00:00:00 2001 From: Honza Horak Date: Fri, 9 Dec 2016 17:48:50 +0100 Subject: [PATCH 3/4] Bug#24388753: PRIVILEGE ESCALATION USING MYSQLD_SAFE MySQL 5.1 upstream backport of: https://github.com/mysql/mysql-server/commit/48bd8b16fe382be302c6f0b45931be5aa6f29a0e 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. --- sql/log.cc | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- sql/log.h | 10 ++++++++ sql/mysqld.cc | 16 ++++++++++++ sql/set_var.cc | 5 ++++ 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/sql/log.cc b/sql/log.cc index 60692b7..d53b487 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1933,6 +1933,73 @@ bool MYSQL_LOG::init_and_set_log_file_name(const char *log_name, } +bool is_valid_log_name(const char *name, size_t len) +{ + if (len > 3) + { + const char *tail= name + len - 4; + if (my_strcasecmp(system_charset_info, tail, ".ini") == 0 || + my_strcasecmp(system_charset_info, tail, ".cnf") == 0) + { + return false; + } + } + return true; +} + + +/** + Get the real log file name, and possibly reopen file. + + Use realpath() to get the path with symbolic links + expanded. Then, close the file, and reopen the real path using the + O_NOFOLLOW flag. This will reject following symbolic links. + + @param file File descriptor. + @param open_flags Flags to use for opening the file. + @param opened_file_name Name of the open fd. + + @retval file descriptor to open file with 'real_file_name', or '-1' + in case of errors. +*/ + +#ifndef _WIN32 +static File mysql_file_real_name_reopen(File file, + int open_flags, + const char *opened_file_name) +{ + DBUG_ASSERT(file); + DBUG_ASSERT(opened_file_name); + + /* Buffer for realpath must have capacity for PATH_MAX. */ + char real_file_name[PATH_MAX]; + + /* Get realpath, validate, open realpath with O_NOFOLLOW. */ + if (realpath(opened_file_name, real_file_name) == NULL) + { + (void) my_close(file, MYF(0)); + return -1; + } + + if (my_close(file, MYF(0))) + return -1; + + if (strlen(real_file_name) > FN_REFLEN) + return -1; + + if (!is_valid_log_name(real_file_name, strlen(real_file_name))) + { + sql_print_error("Invalid log file name after expanding symlinks: '%s'", + real_file_name); + return -1; + } + + return my_open(real_file_name, + open_flags | O_NOFOLLOW, + MYF(MY_WME | ME_WAITTANG)); +} +#endif // _WIN32 + /* Open a (new) log file. @@ -1983,8 +2050,18 @@ bool MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg, db[0]= 0; if ((file= my_open(log_file_name, open_flags, - MYF(MY_WME | ME_WAITTANG))) < 0 || - init_io_cache(&log_file, file, IO_SIZE, io_cache_type, + MYF(MY_WME | ME_WAITTANG))) < 0) + goto err; + +#ifndef _WIN32 + /* Reopen and validate path. */ + if ((log_type_arg == LOG_UNKNOWN || log_type_arg == LOG_NORMAL) && + (file= mysql_file_real_name_reopen(file, + open_flags, + log_file_name)) < 0) + goto err; +#endif // _WIN32 + if (init_io_cache(&log_file, file, IO_SIZE, io_cache_type, my_tell(file, MYF(MY_WME)), 0, MYF(MY_WME | MY_NABP | ((log_type == LOG_BIN) ? MY_WAIT_IF_FULL : 0)))) diff --git a/sql/log.h b/sql/log.h index 02721f1..5354447 100644 --- a/sql/log.h +++ b/sql/log.h @@ -605,4 +605,14 @@ extern TYPELIB binlog_format_typelib; int query_error_code(THD *thd, bool not_killed); +/** + Check given log name against certain blacklisted names/extensions. + + @param name Log name to check + @param len Length of log name + + @returns true if name is valid, false otherwise. +*/ +bool is_valid_log_name(const char *name, size_t len); + #endif /* LOG_H */ diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 41cef57..aa1dab4 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -3458,6 +3458,22 @@ static int init_common_variables(const char *conf_file_name, int argc, "--log_slow_queries option, log tables are used. " "To enable logging to files use the --log-output=file option."); + if (opt_logname && + !is_valid_log_name(opt_logname, strlen(opt_logname))) + { + sql_print_error("Invalid value for --general_log_file: %s", + opt_logname); + return 1; + } + + if (opt_slow_logname && + !is_valid_log_name(opt_slow_logname, strlen(opt_slow_logname))) + { + sql_print_error("Invalid value for --slow_query_log_file: %s", + opt_slow_logname); + return 1; + } + s= opt_logname ? opt_logname : make_default_log_name(buff, ".log"); sys_var_general_log_path.value= my_strdup(s, MYF(0)); sys_var_general_log_path.value_length= strlen(s); diff --git a/sql/set_var.cc b/sql/set_var.cc index f4035ed..73727c8 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -2493,6 +2493,11 @@ static int sys_check_log_path(THD *thd, set_var *var) log_file_str= res->c_ptr(); bzero(&f_stat, sizeof(MY_STAT)); + if (!is_valid_log_name(log_file_str, strlen(log_file_str))) + { + goto err; + } + path_length= unpack_filename(path, log_file_str); if (!path_length) -- 2.7.4