1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
From 2135853b41f2d124f1869371154b0f9ab9a7b2da Mon Sep 17 00:00:00 2001
From: Honza Horak <hhorak@redhat.com>
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
|