diff options
author | Sergei Golubchik <serg@mariadb.org> | 2017-02-15 18:45:19 +0100 |
---|---|---|
committer | Sergei Golubchik <serg@mariadb.org> | 2017-02-27 12:35:10 +0100 |
commit | b27fd90ad36f4194665744cc1dcdd05f2d0b47ef (patch) | |
tree | a48e90c7facfabf56074685a342fabf7584b8b48 /mysys | |
parent | d78d0d459d10dd12069de82d6735f1acf183c631 (diff) | |
download | mariadb-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')
-rw-r--r-- | mysys/my_open.c | 90 |
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 +} |