Skip to content

save_time: non-EINTR write failure wraps size_t and hangs the process (FAKETIME_SAVE_FILE) #550

Description

@yayong3

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions