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.
This commit is contained in:
Sergei Golubchik 2017-02-15 18:45:19 +01:00
commit b27fd90ad3
13 changed files with 298 additions and 68 deletions

View file

@ -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
}