Skip to content

Commit c41838a

Browse files
committed
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.
1 parent 2877d33 commit c41838a

2 files changed

Lines changed: 113 additions & 102 deletions

File tree

src/ex_cmds.c

Lines changed: 111 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1825,7 +1825,6 @@ write_viminfo(char_u *file, int forceit)
18251825
FILE *fp_out = NULL; /* output viminfo file */
18261826
char_u *tempname = NULL; /* name of temp viminfo file */
18271827
stat_T st_new; /* mch_stat() of potential new file */
1828-
char_u *wp;
18291828
#if defined(UNIX) || defined(VMS)
18301829
mode_t umask_save;
18311830
#endif
@@ -1847,27 +1846,29 @@ write_viminfo(char_u *file, int forceit)
18471846
fp_in = mch_fopen((char *)fname, READBIN);
18481847
if (fp_in == NULL)
18491848
{
1849+
int fd;
1850+
18501851
/* if it does exist, but we can't read it, don't try writing */
18511852
if (mch_stat((char *)fname, &st_new) == 0)
18521853
goto end;
1853-
#if defined(UNIX) || defined(VMS)
1854-
/*
1855-
* For Unix we create the .viminfo non-accessible for others,
1856-
* because it may contain text from non-accessible documents.
1857-
*/
1858-
umask_save = umask(077);
1859-
#endif
1860-
fp_out = mch_fopen((char *)fname, WRITEBIN);
1861-
#if defined(UNIX) || defined(VMS)
1862-
(void)umask(umask_save);
1863-
#endif
1854+
1855+
/* Create the new .viminfo non-accessible for others, because it may
1856+
* contain text from non-accessible documents. It is up to the user to
1857+
* widen access (e.g. to a group). This may also fail if there is a
1858+
* race condition, then just give up. */
1859+
fd = mch_open((char *)fname,
1860+
O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
1861+
if (fd < 0)
1862+
goto end;
1863+
fp_out = fdopen(fd, WRITEBIN);
18641864
}
18651865
else
18661866
{
18671867
/*
18681868
* There is an existing viminfo file. Create a temporary file to
18691869
* write the new viminfo into, in the same directory as the
1870-
* existing viminfo file, which will be renamed later.
1870+
* existing viminfo file, which will be renamed once all writing is
1871+
* successful.
18711872
*/
18721873
#ifdef UNIX
18731874
/*
@@ -1901,12 +1902,18 @@ write_viminfo(char_u *file, int forceit)
19011902
#endif
19021903

19031904
/*
1904-
* Make tempname.
1905+
* Make tempname, find one that does not exist yet.
1906+
* Beware of a race condition: If someone logs out and all Vim
1907+
* instances exit at the same time a temp file might be created between
1908+
* stat() and open(). Use mch_open() with O_EXCL to avoid that.
19051909
* May try twice: Once normal and once with shortname set, just in
19061910
* case somebody puts his viminfo file in an 8.3 filesystem.
19071911
*/
19081912
for (;;)
19091913
{
1914+
int next_char = 'z';
1915+
char_u *wp;
1916+
19101917
tempname = buf_modname(
19111918
#ifdef UNIX
19121919
shortname,
@@ -1924,127 +1931,129 @@ write_viminfo(char_u *file, int forceit)
19241931
break;
19251932

19261933
/*
1927-
* Check if tempfile already exists. Never overwrite an
1928-
* existing file!
1934+
* Try a series of names. Change one character, just before
1935+
* the extension. This should also work for an 8.3
1936+
* file name, when after adding the extension it still is
1937+
* the same file as the original.
19291938
*/
1930-
if (mch_stat((char *)tempname, &st_new) == 0)
1939+
wp = tempname + STRLEN(tempname) - 5;
1940+
if (wp < gettail(tempname)) /* empty file name? */
1941+
wp = gettail(tempname);
1942+
for (;;)
19311943
{
1932-
#ifdef UNIX
1933-
/*
1934-
* Check if tempfile is same as original file. May happen
1935-
* when modname() gave the same file back. E.g. silly
1936-
* link, or file name-length reached. Try again with
1937-
* shortname set.
1938-
*/
1939-
if (!shortname && st_new.st_dev == st_old.st_dev
1940-
&& st_new.st_ino == st_old.st_ino)
1941-
{
1942-
vim_free(tempname);
1943-
tempname = NULL;
1944-
shortname = TRUE;
1945-
continue;
1946-
}
1947-
#endif
19481944
/*
1949-
* Try another name. Change one character, just before
1950-
* the extension. This should also work for an 8.3
1951-
* file name, when after adding the extension it still is
1952-
* the same file as the original.
1945+
* Check if tempfile already exists. Never overwrite an
1946+
* existing file!
19531947
*/
1954-
wp = tempname + STRLEN(tempname) - 5;
1955-
if (wp < gettail(tempname)) /* empty file name? */
1956-
wp = gettail(tempname);
1957-
for (*wp = 'z'; mch_stat((char *)tempname, &st_new) == 0;
1958-
--*wp)
1948+
if (mch_stat((char *)tempname, &st_new) == 0)
19591949
{
1950+
#ifdef UNIX
19601951
/*
1961-
* They all exist? Must be something wrong! Don't
1962-
* write the viminfo file then.
1952+
* Check if tempfile is same as original file. May happen
1953+
* when modname() gave the same file back. E.g. silly
1954+
* link, or file name-length reached. Try again with
1955+
* shortname set.
19631956
*/
1964-
if (*wp == 'a')
1957+
if (!shortname && st_new.st_dev == st_old.st_dev
1958+
&& st_new.st_ino == st_old.st_ino)
19651959
{
1966-
EMSG2(_("E929: Too many viminfo temp files, like %s!"),
1967-
tempname);
19681960
vim_free(tempname);
19691961
tempname = NULL;
1962+
shortname = TRUE;
19701963
break;
19711964
}
1965+
#endif
19721966
}
1973-
}
1974-
break;
1975-
}
1976-
1977-
if (tempname != NULL)
1978-
{
1967+
else
1968+
{
1969+
/* Try creating the file exclusively. This may fail if
1970+
* another Vim tries to do it at the same time. */
19791971
#ifdef VMS
1980-
/* fdopen() fails for some reason */
1981-
umask_save = umask(077);
1982-
fp_out = mch_fopen((char *)tempname, WRITEBIN);
1983-
(void)umask(umask_save);
1972+
/* fdopen() fails for some reason */
1973+
umask_save = umask(077);
1974+
fp_out = mch_fopen((char *)tempname, WRITEBIN);
1975+
(void)umask(umask_save);
19841976
#else
1985-
int fd;
1977+
int fd;
19861978

1987-
/* Use mch_open() to be able to use O_NOFOLLOW and set file
1988-
* protection:
1989-
* Unix: same as original file, but strip s-bit. Reset umask to
1990-
* avoid it getting in the way.
1991-
* Others: r&w for user only. */
1979+
/* Use mch_open() to be able to use O_NOFOLLOW and set file
1980+
* protection:
1981+
* Unix: same as original file, but strip s-bit. Reset
1982+
* umask to avoid it getting in the way.
1983+
* Others: r&w for user only. */
19921984
# ifdef UNIX
1993-
umask_save = umask(0);
1994-
fd = mch_open((char *)tempname,
1995-
O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW,
1996-
(int)((st_old.st_mode & 0777) | 0600));
1997-
(void)umask(umask_save);
1985+
umask_save = umask(0);
1986+
fd = mch_open((char *)tempname,
1987+
O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW,
1988+
(int)((st_old.st_mode & 0777) | 0600));
1989+
(void)umask(umask_save);
19981990
# else
1999-
fd = mch_open((char *)tempname,
2000-
O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
1991+
fd = mch_open((char *)tempname,
1992+
O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
20011993
# endif
2002-
if (fd < 0)
2003-
fp_out = NULL;
2004-
else
2005-
fp_out = fdopen(fd, WRITEBIN);
1994+
if (fd < 0)
1995+
{
1996+
fp_out = NULL;
1997+
# ifdef EEXIST
1998+
/* Avoid trying lots of names while the problem is lack
1999+
* of premission, only retry if the file already
2000+
* exists. */
2001+
if (errno != EEXIST)
2002+
break;
2003+
# endif
2004+
}
2005+
else
2006+
fp_out = fdopen(fd, WRITEBIN);
20062007
#endif /* VMS */
2008+
if (fp_out != NULL)
2009+
break;
2010+
}
20072011

2008-
/*
2009-
* If we can't create in the same directory, try creating a
2010-
* "normal" temp file. This is just an attempt, renaming the temp
2011-
* file might fail as well.
2012-
*/
2013-
if (fp_out == NULL)
2014-
{
2015-
vim_free(tempname);
2016-
if ((tempname = vim_tempname('o', TRUE)) != NULL)
2017-
fp_out = mch_fopen((char *)tempname, WRITEBIN);
2012+
/* Assume file exists, try again with another name. */
2013+
if (next_char == 'a' - 1)
2014+
{
2015+
/* They all exist? Must be something wrong! Don't write
2016+
* the viminfo file then. */
2017+
EMSG2(_("E929: Too many viminfo temp files, like %s!"),
2018+
tempname);
2019+
break;
2020+
}
2021+
*wp = next_char;
2022+
--next_char;
20182023
}
20192024

2025+
if (tempname != NULL)
2026+
break;
2027+
/* continue if shortname was set */
2028+
}
2029+
20202030
#if defined(UNIX) && defined(HAVE_FCHOWN)
2031+
if (tempname != NULL && fp_out != NULL)
2032+
{
2033+
stat_T tmp_st;
2034+
20212035
/*
20222036
* Make sure the original owner can read/write the tempfile and
20232037
* otherwise preserve permissions, making sure the group matches.
20242038
*/
2025-
if (fp_out != NULL)
2039+
if (mch_stat((char *)tempname, &tmp_st) >= 0)
20262040
{
2027-
stat_T tmp_st;
2028-
2029-
if (mch_stat((char *)tempname, &tmp_st) >= 0)
2030-
{
2031-
if (st_old.st_uid != tmp_st.st_uid)
2032-
/* Changing the owner might fail, in which case the
2033-
* file will now owned by the current user, oh well. */
2034-
ignored = fchown(fileno(fp_out), st_old.st_uid, -1);
2035-
if (st_old.st_gid != tmp_st.st_gid
2036-
&& fchown(fileno(fp_out), -1, st_old.st_gid) == -1)
2037-
/* can't set the group to what it should be, remove
2038-
* group permissions */
2039-
(void)mch_setperm(tempname, 0600);
2040-
}
2041-
else
2042-
/* can't stat the file, set conservative permissions */
2041+
if (st_old.st_uid != tmp_st.st_uid)
2042+
/* Changing the owner might fail, in which case the
2043+
* file will now owned by the current user, oh well. */
2044+
ignored = fchown(fileno(fp_out), st_old.st_uid, -1);
2045+
if (st_old.st_gid != tmp_st.st_gid
2046+
&& fchown(fileno(fp_out), -1, st_old.st_gid) == -1)
2047+
/* can't set the group to what it should be, remove
2048+
* group permissions */
20432049
(void)mch_setperm(tempname, 0600);
20442050
}
2045-
#endif
2051+
else
2052+
/* can't stat the file, set conservative permissions */
2053+
(void)mch_setperm(tempname, 0600);
20462054
}
20472055
}
2056+
#endif
20482057

20492058
/*
20502059
* Check if the new viminfo file can be written to.

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,8 @@ static char *(features[]) =
771771

772772
static int included_patches[] =
773773
{ /* Add new patch number below this line */
774+
/**/
775+
1345,
774776
/**/
775777
1344,
776778
/**/

0 commit comments

Comments
 (0)