0
0
mirror of https://github.com/vim/vim.git synced 2025-09-24 03:44:06 -04:00

patch 8.0.1345: race condition between stat() and open() for viminfo

Problem:    Race condition between stat() and open() for the viminfo temp
            file. (Simon Ruderich)
Solution:   use open() with O_EXCL to atomically check if the file exists.
            Don't try using a temp file, renaming it will fail anyway.
This commit is contained in:
Bram Moolenaar
2017-11-26 16:50:41 +01:00
parent 2877d334ad
commit c41838aa01
2 changed files with 122 additions and 111 deletions

View File

@@ -1825,7 +1825,6 @@ write_viminfo(char_u *file, int forceit)
FILE *fp_out = NULL; /* output viminfo file */ FILE *fp_out = NULL; /* output viminfo file */
char_u *tempname = NULL; /* name of temp viminfo file */ char_u *tempname = NULL; /* name of temp viminfo file */
stat_T st_new; /* mch_stat() of potential new file */ stat_T st_new; /* mch_stat() of potential new file */
char_u *wp;
#if defined(UNIX) || defined(VMS) #if defined(UNIX) || defined(VMS)
mode_t umask_save; mode_t umask_save;
#endif #endif
@@ -1847,27 +1846,29 @@ write_viminfo(char_u *file, int forceit)
fp_in = mch_fopen((char *)fname, READBIN); fp_in = mch_fopen((char *)fname, READBIN);
if (fp_in == NULL) if (fp_in == NULL)
{ {
int fd;
/* if it does exist, but we can't read it, don't try writing */ /* if it does exist, but we can't read it, don't try writing */
if (mch_stat((char *)fname, &st_new) == 0) if (mch_stat((char *)fname, &st_new) == 0)
goto end; goto end;
#if defined(UNIX) || defined(VMS)
/* /* Create the new .viminfo non-accessible for others, because it may
* For Unix we create the .viminfo non-accessible for others, * contain text from non-accessible documents. It is up to the user to
* because it may contain text from non-accessible documents. * widen access (e.g. to a group). This may also fail if there is a
*/ * race condition, then just give up. */
umask_save = umask(077); fd = mch_open((char *)fname,
#endif O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
fp_out = mch_fopen((char *)fname, WRITEBIN); if (fd < 0)
#if defined(UNIX) || defined(VMS) goto end;
(void)umask(umask_save); fp_out = fdopen(fd, WRITEBIN);
#endif
} }
else else
{ {
/* /*
* There is an existing viminfo file. Create a temporary file to * There is an existing viminfo file. Create a temporary file to
* write the new viminfo into, in the same directory as the * write the new viminfo into, in the same directory as the
* existing viminfo file, which will be renamed later. * existing viminfo file, which will be renamed once all writing is
* successful.
*/ */
#ifdef UNIX #ifdef UNIX
/* /*
@@ -1901,12 +1902,18 @@ write_viminfo(char_u *file, int forceit)
#endif #endif
/* /*
* Make tempname. * Make tempname, find one that does not exist yet.
* Beware of a race condition: If someone logs out and all Vim
* instances exit at the same time a temp file might be created between
* stat() and open(). Use mch_open() with O_EXCL to avoid that.
* May try twice: Once normal and once with shortname set, just in * May try twice: Once normal and once with shortname set, just in
* case somebody puts his viminfo file in an 8.3 filesystem. * case somebody puts his viminfo file in an 8.3 filesystem.
*/ */
for (;;) for (;;)
{ {
int next_char = 'z';
char_u *wp;
tempname = buf_modname( tempname = buf_modname(
#ifdef UNIX #ifdef UNIX
shortname, shortname,
@@ -1924,127 +1931,129 @@ write_viminfo(char_u *file, int forceit)
break; break;
/* /*
* Check if tempfile already exists. Never overwrite an * Try a series of names. Change one character, just before
* existing file! * the extension. This should also work for an 8.3
* file name, when after adding the extension it still is
* the same file as the original.
*/ */
if (mch_stat((char *)tempname, &st_new) == 0) wp = tempname + STRLEN(tempname) - 5;
if (wp < gettail(tempname)) /* empty file name? */
wp = gettail(tempname);
for (;;)
{ {
/*
* Check if tempfile already exists. Never overwrite an
* existing file!
*/
if (mch_stat((char *)tempname, &st_new) == 0)
{
#ifdef UNIX #ifdef UNIX
/*
* Check if tempfile is same as original file. May happen
* when modname() gave the same file back. E.g. silly
* link, or file name-length reached. Try again with
* shortname set.
*/
if (!shortname && st_new.st_dev == st_old.st_dev
&& st_new.st_ino == st_old.st_ino)
{
vim_free(tempname);
tempname = NULL;
shortname = TRUE;
continue;
}
#endif
/*
* Try another name. Change one character, just before
* the extension. This should also work for an 8.3
* file name, when after adding the extension it still is
* the same file as the original.
*/
wp = tempname + STRLEN(tempname) - 5;
if (wp < gettail(tempname)) /* empty file name? */
wp = gettail(tempname);
for (*wp = 'z'; mch_stat((char *)tempname, &st_new) == 0;
--*wp)
{
/* /*
* They all exist? Must be something wrong! Don't * Check if tempfile is same as original file. May happen
* write the viminfo file then. * when modname() gave the same file back. E.g. silly
* link, or file name-length reached. Try again with
* shortname set.
*/ */
if (*wp == 'a') if (!shortname && st_new.st_dev == st_old.st_dev
&& st_new.st_ino == st_old.st_ino)
{ {
EMSG2(_("E929: Too many viminfo temp files, like %s!"),
tempname);
vim_free(tempname); vim_free(tempname);
tempname = NULL; tempname = NULL;
shortname = TRUE;
break; break;
} }
#endif
} }
else
{
/* Try creating the file exclusively. This may fail if
* another Vim tries to do it at the same time. */
#ifdef VMS
/* fdopen() fails for some reason */
umask_save = umask(077);
fp_out = mch_fopen((char *)tempname, WRITEBIN);
(void)umask(umask_save);
#else
int fd;
/* Use mch_open() to be able to use O_NOFOLLOW and set file
* protection:
* Unix: same as original file, but strip s-bit. Reset
* umask to avoid it getting in the way.
* Others: r&w for user only. */
# ifdef UNIX
umask_save = umask(0);
fd = mch_open((char *)tempname,
O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW,
(int)((st_old.st_mode & 0777) | 0600));
(void)umask(umask_save);
# else
fd = mch_open((char *)tempname,
O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
# endif
if (fd < 0)
{
fp_out = NULL;
# ifdef EEXIST
/* Avoid trying lots of names while the problem is lack
* of premission, only retry if the file already
* exists. */
if (errno != EEXIST)
break;
# endif
}
else
fp_out = fdopen(fd, WRITEBIN);
#endif /* VMS */
if (fp_out != NULL)
break;
}
/* Assume file exists, try again with another name. */
if (next_char == 'a' - 1)
{
/* They all exist? Must be something wrong! Don't write
* the viminfo file then. */
EMSG2(_("E929: Too many viminfo temp files, like %s!"),
tempname);
break;
}
*wp = next_char;
--next_char;
} }
break;
if (tempname != NULL)
break;
/* continue if shortname was set */
} }
if (tempname != NULL)
{
#ifdef VMS
/* fdopen() fails for some reason */
umask_save = umask(077);
fp_out = mch_fopen((char *)tempname, WRITEBIN);
(void)umask(umask_save);
#else
int fd;
/* Use mch_open() to be able to use O_NOFOLLOW and set file
* protection:
* Unix: same as original file, but strip s-bit. Reset umask to
* avoid it getting in the way.
* Others: r&w for user only. */
# ifdef UNIX
umask_save = umask(0);
fd = mch_open((char *)tempname,
O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW,
(int)((st_old.st_mode & 0777) | 0600));
(void)umask(umask_save);
# else
fd = mch_open((char *)tempname,
O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
# endif
if (fd < 0)
fp_out = NULL;
else
fp_out = fdopen(fd, WRITEBIN);
#endif /* VMS */
/*
* If we can't create in the same directory, try creating a
* "normal" temp file. This is just an attempt, renaming the temp
* file might fail as well.
*/
if (fp_out == NULL)
{
vim_free(tempname);
if ((tempname = vim_tempname('o', TRUE)) != NULL)
fp_out = mch_fopen((char *)tempname, WRITEBIN);
}
#if defined(UNIX) && defined(HAVE_FCHOWN) #if defined(UNIX) && defined(HAVE_FCHOWN)
if (tempname != NULL && fp_out != NULL)
{
stat_T tmp_st;
/* /*
* Make sure the original owner can read/write the tempfile and * Make sure the original owner can read/write the tempfile and
* otherwise preserve permissions, making sure the group matches. * otherwise preserve permissions, making sure the group matches.
*/ */
if (fp_out != NULL) if (mch_stat((char *)tempname, &tmp_st) >= 0)
{ {
stat_T tmp_st; if (st_old.st_uid != tmp_st.st_uid)
/* Changing the owner might fail, in which case the
if (mch_stat((char *)tempname, &tmp_st) >= 0) * file will now owned by the current user, oh well. */
{ ignored = fchown(fileno(fp_out), st_old.st_uid, -1);
if (st_old.st_uid != tmp_st.st_uid) if (st_old.st_gid != tmp_st.st_gid
/* Changing the owner might fail, in which case the && fchown(fileno(fp_out), -1, st_old.st_gid) == -1)
* file will now owned by the current user, oh well. */ /* can't set the group to what it should be, remove
ignored = fchown(fileno(fp_out), st_old.st_uid, -1); * group permissions */
if (st_old.st_gid != tmp_st.st_gid
&& fchown(fileno(fp_out), -1, st_old.st_gid) == -1)
/* can't set the group to what it should be, remove
* group permissions */
(void)mch_setperm(tempname, 0600);
}
else
/* can't stat the file, set conservative permissions */
(void)mch_setperm(tempname, 0600); (void)mch_setperm(tempname, 0600);
} }
#endif else
/* can't stat the file, set conservative permissions */
(void)mch_setperm(tempname, 0600);
} }
} }
#endif
/* /*
* Check if the new viminfo file can be written to. * Check if the new viminfo file can be written to.

View File

@@ -771,6 +771,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1345,
/**/ /**/
1344, 1344,
/**/ /**/