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:
231
src/ex_cmds.c
231
src/ex_cmds.c
@@ -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.
|
||||||
|
@@ -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,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user