Skip to content

Commit fcb7c76

Browse files
mhaggergitster
authored andcommitted
resolve_ref_unsafe(): close race condition reading loose refs
We read loose references in two steps. The code is roughly: lstat() if error ENOENT: loose ref is missing; look for corresponding packed ref else if S_ISLNK: readlink() if error: report failure else if S_ISDIR: report failure else open() if error: report failure read() The problem is that the first filesystem call, to lstat(), is not atomic with the second filesystem call, to readlink() or open(). Therefore it is possible for another process to change the file between our two calls, for example: * If the other process deletes the file, our second call will fail with ENOENT, which we *should* interpret as "loose ref is missing; look for corresponding packed ref". This can arise if the other process is pack-refs; it might have just written a new packed-refs file containing the old contents of the reference then deleted the loose ref. * If the other process changes a symlink into a plain file, our call to readlink() will fail with EINVAL, which we *should* respond to by trying to open() and read() the file. The old code treats the reference as missing in both of these cases, which is incorrect. So instead, handle errors more selectively: if the result of readline()/open() is a failure that is inconsistent with the result of the previous lstat(), then something is fishy. In this case jump back and start over again with a fresh call to lstat(). One race is still possible and undetected: another process could change the file from a regular file into a symlink between the call to lstat and the call to open(). The open() call would silently follow the symlink and not know that something is wrong. This situation could be detected in two ways: * On systems that support O_NOFOLLOW, pass that option to the open(). * On other systems, call fstat() on the fd returned by open() and make sure that it agrees with the stat info from the original lstat(). However, we don't use symlinks anymore, so this situation is unlikely. Moreover, it doesn't appear that treating a symlink as a regular file would have grave consequences; after all, this is exactly how the code handles non-relative symlinks. So this commit leaves that race unaddressed. Note that this solves only the part of the race within resolve_ref_unsafe. In the situation described above, we may still be depending on a cached view of the packed-refs file; that race will be dealt with in a future patch. This problem was reported and diagnosed by Jeff King <[email protected]>, and this solution is derived from his patch. Signed-off-by: Michael Haggerty <[email protected]> Reviewed-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2884c06 commit fcb7c76

File tree

1 file changed

+24
-4
lines changed

1 file changed

+24
-4
lines changed

refs.c

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,16 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
12521252

12531253
git_snpath(path, sizeof(path), "%s", refname);
12541254

1255+
/*
1256+
* We might have to loop back here to avoid a race
1257+
* condition: first we lstat() the file, then we try
1258+
* to read it as a link or as a file. But if somebody
1259+
* changes the type of the file (file <-> directory
1260+
* <-> symlink) between the lstat() and reading, then
1261+
* we don't want to report that as an error but rather
1262+
* try again starting with the lstat().
1263+
*/
1264+
stat_ref:
12551265
if (lstat(path, &st) < 0) {
12561266
if (errno == ENOENT)
12571267
return handle_missing_loose_ref(refname, sha1,
@@ -1263,8 +1273,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
12631273
/* Follow "normalized" - ie "refs/.." symlinks by hand */
12641274
if (S_ISLNK(st.st_mode)) {
12651275
len = readlink(path, buffer, sizeof(buffer)-1);
1266-
if (len < 0)
1267-
return NULL;
1276+
if (len < 0) {
1277+
if (errno == ENOENT || errno == EINVAL)
1278+
/* inconsistent with lstat; retry */
1279+
goto stat_ref;
1280+
else
1281+
return NULL;
1282+
}
12681283
buffer[len] = 0;
12691284
if (!prefixcmp(buffer, "refs/") &&
12701285
!check_refname_format(buffer, 0)) {
@@ -1287,8 +1302,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
12871302
* a ref
12881303
*/
12891304
fd = open(path, O_RDONLY);
1290-
if (fd < 0)
1291-
return NULL;
1305+
if (fd < 0) {
1306+
if (errno == ENOENT)
1307+
/* inconsistent with lstat; retry */
1308+
goto stat_ref;
1309+
else
1310+
return NULL;
1311+
}
12921312
len = read_in_full(fd, buffer, sizeof(buffer)-1);
12931313
close(fd);
12941314
if (len < 0)

0 commit comments

Comments
 (0)