Summary
FAKETIME_SAVE_FILE (README §4k) appends each faked timestamp to a file from save_time(). The retry loop treats a failed write() as “keep going”:
do {
written = write(outfile, &(((char*)&time_write)[n]), sizeof(time_write) - n);
} while (((written == -1) && (errno == EINTR)) ||
(sizeof(time_write) < (n += written)));
n is size_t. For any error other than EINTR (ENOSPC, EIO, EBADF, …), written == -1 and n += written wraps n to SIZE_MAX. The loop condition stays true, the pointer &((char*)&time_write)[n] is out of range, and the process never leaves the loop. The perror after the loop is unreachable.
write() returning 0 (no progress) has the same outcome: n does not advance and the loop does not terminate.
Observed with FAKETIME_SAVE_FILE=/dev/full (ENOSPC): date under LD_PRELOAD spins until killed. Present on 0.9.12 (bbb6c08).
Code
/* src/libfaketime.c — save_time, :948 */
lseek(outfile, 0, SEEK_END);
do
{
written = write(outfile, &(((char*)&time_write)[n]), sizeof(time_write) - n);
}
while (((written == -1) && (errno == EINTR)) ||
(sizeof(time_write) < (n += written)));
if ((written == -1) || (n < sizeof(time_write)))
{
perror("libfaketime: In save_time(), saving timestamp to file failed");
}
outfile is opened in ftpl_really_init() when FAKETIME_SAVE_FILE is set (:3289). save_time() is called from fake_clock_gettime() on every successful fake (:3796), so the first clock_gettime/gettimeofday after init is enough.
The || binds “EINTR retry” to “not yet complete”, and n += written runs for every evaluation, including the error path.
Reproduce
make -C src
timeout 2s env FAKETIME=@2005-03-29\ 14:14:14 \
FAKETIME_SAVE_FILE=/dev/full \
LD_PRELOAD=./src/libfaketime.so.1 date
# GNU timeout: exit 124 (process never returns on its own)
env FAKETIME=@2005-03-29\ 14:14:14 \
LD_PRELOAD=./src/libfaketime.so.1 date +%s
# 1112076854
The same operators, isolated:
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
int main(void)
{
int outfile = open("/dev/full", O_RDWR);
struct { int64_t sec; uint64_t nsec; } time_write = {1, 2};
ssize_t written;
size_t n = 0;
int i = 0;
do {
written = write(outfile, &(((char*)&time_write)[n]), sizeof(time_write) - n);
fprintf(stderr, "i=%d written=%zd n=%zu errno=%d\n", i, written, n, errno);
if (++i > 3)
return 0;
} while (((written == -1) && (errno == EINTR)) ||
(sizeof(time_write) < (n += written)));
return 1;
}
gcc -o loop loop.c && ./loop
# i=0 written=-1 n=0 errno=28
# i=1 written=-1 n=18446744073709551615 errno=28
Suggested fix
Retry only on EINTR. Advance n only when written > 0. Break on any other error or on written == 0, then keep the existing perror:
--- a/src/libfaketime.c
+++ b/src/libfaketime.c
@@ -946,13 +946,24 @@ static void save_time(struct timespec *tp)
}
lseek(outfile, 0, SEEK_END);
- do
- {
- written = write(outfile, &(((char*)&time_write)[n]), sizeof(time_write) - n);
- }
- while (((written == -1) && (errno == EINTR)) ||
- (sizeof(time_write) < (n += written)));
-
- if ((written == -1) || (n < sizeof(time_write)))
+ while (n < sizeof(time_write))
+ {
+ written = write(outfile, ((char *)&time_write) + n, sizeof(time_write) - n);
+ if (written > 0) {
+ n += (size_t)written;
+ continue;
+ }
+ if (written == -1 && errno == EINTR)
+ continue;
+ break;
+ }
+
+ if ((written <= 0) || (n < sizeof(time_write)))
{
perror("libfaketime: In save_time(), saving timestamp to file failed");
}
This restores the intended “short write / EINTR” loop without wrapping n or spinning on a full filesystem.
Environment
wolfcw/libfaketime 0.9.12 (bbb6c08)
- Linux x86_64, stock
make
Summary
FAKETIME_SAVE_FILE(README §4k) appends each faked timestamp to a file fromsave_time(). The retry loop treats a failedwrite()as “keep going”:nissize_t. For any error other thanEINTR(ENOSPC,EIO,EBADF, …),written == -1andn += writtenwrapsntoSIZE_MAX. The loop condition stays true, the pointer&((char*)&time_write)[n]is out of range, and the process never leaves the loop. Theperrorafter the loop is unreachable.write()returning0(no progress) has the same outcome:ndoes not advance and the loop does not terminate.Observed with
FAKETIME_SAVE_FILE=/dev/full(ENOSPC):dateunderLD_PRELOADspins until killed. Present on 0.9.12 (bbb6c08).Code
outfileis opened inftpl_really_init()whenFAKETIME_SAVE_FILEis set (:3289).save_time()is called fromfake_clock_gettime()on every successful fake (:3796), so the firstclock_gettime/gettimeofdayafter init is enough.The
||binds “EINTR retry” to “not yet complete”, andn += writtenruns for every evaluation, including the error path.Reproduce
The same operators, isolated:
Suggested fix
Retry only on
EINTR. Advancenonly whenwritten > 0. Break on any other error or onwritten == 0, then keep the existingperror:This restores the intended “short write / EINTR” loop without wrapping
nor spinning on a full filesystem.Environment
wolfcw/libfaketime0.9.12 (bbb6c08)make