summaryrefslogtreecommitdiff
path: root/mysys/my_open.c
diff options
context:
space:
mode:
authorSergei Golubchik <serg@mariadb.org>2017-02-15 18:45:19 +0100
committerSergei Golubchik <serg@mariadb.org>2017-02-27 12:35:10 +0100
commitb27fd90ad36f4194665744cc1dcdd05f2d0b47ef (patch)
treea48e90c7facfabf56074685a342fabf7584b8b48 /mysys/my_open.c
parentd78d0d459d10dd12069de82d6735f1acf183c631 (diff)
downloadmariadb-git-b27fd90ad36f4194665744cc1dcdd05f2d0b47ef.tar.gz
MDEV-11902 mi_open race condition
TOCTOU bug. The path is checked to be valid, symlinks are resolved. Then the resolved path is opened. Between the check and the open, there's a window when one can replace some path component with a symlink, bypassing validity checks. Fix: after we resolved all symlinks in the path, don't allow open() to resolve symlinks, there should be none. Compared to the old MyISAM/Aria code: * fastpath. Opening of not-symlinked files is just one open(), no fn_format() and lstat() anymore. * opening of symlinked tables doesn't do fn_format() and lstat() either. it also doesn't to realpath() (which was lstat-ing every path component), instead if opens every path component with O_PATH. * share->data_file_name stores realpath(path) not readlink(path). So, SHOW CREATE TABLE needs to do lstat/readlink() now (see ::info()), and certain error messages (cannot open file "XXX") show the real file path with all symlinks resolved.
Diffstat (limited to 'mysys/my_open.c')
-rw-r--r--mysys/my_open.c90
1 files changed, 88 insertions, 2 deletions
diff --git a/mysys/my_open.c b/mysys/my_open.c
index 24d5c881372..0effc4bedda 100644
--- a/mysys/my_open.c
+++ b/mysys/my_open.c
@@ -15,9 +15,14 @@
#include "mysys_priv.h"
#include "mysys_err.h"
-#include <my_dir.h>
+#include <m_string.h>
#include <errno.h>
+#if !defined(O_PATH) && defined(O_EXEC) /* FreeBSD */
+#define O_PATH O_EXEC
+#endif
+
+static int open_nosymlinks(const char *pathname, int flags, int mode);
/*
Open a file
@@ -46,7 +51,10 @@ File my_open(const char *FileName, int Flags, myf MyFlags)
#if defined(_WIN32)
fd= my_win_open(FileName, Flags);
#else
- fd = open(FileName, Flags, my_umask);
+ if (MyFlags & MY_NOSYMLINKS)
+ fd = open_nosymlinks(FileName, Flags, my_umask);
+ else
+ fd = open(FileName, Flags, my_umask);
#endif
fd= my_register_filename(fd, FileName, FILE_BY_OPEN,
@@ -174,3 +182,81 @@ void my_print_open_files(void)
}
#endif
+
+/**
+ like open(), but with symlinks are not accepted anywhere in the path
+
+ This is used for opening symlinked tables for DATA/INDEX DIRECTORY.
+ The paths there have been realpath()-ed. So, we can assume here that
+
+ * `pathname` is an absolute path
+ * no '.', '..', and '//' in the path
+ * file exists
+*/
+static int open_nosymlinks(const char *pathname, int flags, int mode)
+{
+#ifndef O_PATH
+#ifdef HAVE_REALPATH
+ char buf[PATH_MAX+1];
+ if (realpath(pathname, buf) == NULL)
+ return -1;
+ if (strcmp(pathname, buf))
+ {
+ errno= ENOTDIR;
+ return -1;
+ }
+#endif
+ return open(pathname, flags, mode | O_NOFOLLOW);
+#else
+
+ char buf[PATH_MAX+1];
+ char *s= buf, *e= buf+1, *end= strnmov(buf, pathname, sizeof(buf));
+ int fd, dfd= -1;
+
+ if (*end)
+ {
+ errno= ENAMETOOLONG;
+ return -1;
+ }
+
+ if (*s != '/') /* not an absolute path */
+ {
+ errno= ENOENT;
+ return -1;
+ }
+
+ for (;;)
+ {
+ if (*e == '/') /* '//' in the path */
+ {
+ errno= ENOENT;
+ goto err;
+ }
+ while (*e && *e != '/')
+ e++;
+ *e= 0;
+ if (!memcmp(s, ".", 2) || !memcmp(s, "..", 3))
+ {
+ errno= ENOENT;
+ goto err;
+ }
+
+ fd = openat(dfd, s, O_NOFOLLOW | (e < end ? O_PATH : flags), mode);
+ if (fd < 0)
+ goto err;
+
+ if (dfd >= 0)
+ close(dfd);
+
+ dfd= fd;
+ s= ++e;
+
+ if (e >= end)
+ return fd;
+ }
+err:
+ if (dfd >= 0)
+ close(dfd);
+ return -1;
+#endif
+}