Skip to content

prepare_config_contents mishandles comments and CR: .faketimerc with a # line exits or silently ignores the timestamp #549

Description

@yayong3

Summary

When FAKETIME is unset, libfaketime reads FAKETIME_TIMESTAMP_FILE, ~/.faketimerc, or /etc/faketimerc and runs the buffer through prepare_config_contents() before parse_ft_string().

That helper is documented as removing \r/\n and dropping lines that start with # or ;. Two independent bugs in the in-place scan mean that documented behaviour does not hold:

  1. The “skip newline” test reads *write_position, not *read_position. After a comment line, the original LF is copied to the front of the buffer; the write pointer then lands on that same LF still sitting in the uncompacted tail, and the rest of the timestamp is dropped. parse_ft_string() then exit()s.
  2. Only \n clears in_comment / beginning_of_line. A CR-terminated comment swallows everything after it. The result is an empty string, which parse_ft_string() treats as “unchanged” and returns without error — fake time is never applied.

A one-line file without comments still works (that is what README describes). The implementation, however, explicitly supports comments and multi-line files, and any such file is enough to break the default config path.

Present on 0.9.12 (bbb6c08).

Code

/* src/libfaketime.c — prepare_config_contents */

if (*read_position == '\n') {          /* CR does not end a comment */
    in_comment = false;
    beginning_of_line = true;
}

if (!in_comment && *read_position != '\r' && *write_position != '\n') {
    *write_position = *read_position;  /* should test *read_position */
    write_position++;
}

Call chain when FAKETIME is unset:

ftpl_really_init / fake_clock_gettime
  -> read_config_file()                 /* :3516 */
       open ~/.faketimerc or /etc/faketimerc
       prepare_config_contents()        /* :3543 */
       parse_ft_string()                /* :3544; empty string returns at :2760 */

Worked example (LF + comment), buffer # comment\n@2005-03-29 14:14:14\n:

  • Comment-line LF is copied to index 0 because *write_position is still '#'.
  • After copying @2005-03, the write pointer sits on the original LF at index 9.
  • *write_position != '\n' is false, so 29 14:14:14 is never copied.
  • Result: "\n@2005-03". parse_ft_string() prints failed to parse FAKETIME timestamp and exits.

CR + comment (# comment\r@2005-03-29 14:14:14\r) leaves an empty string. parse_ft_string sees it equal to the initial user_faked_time_saved and returns; the process keeps the real clock.

Reproduce

make -C src
WORKDIR=$(mktemp -d)

# 1) LF file with a comment line — parse fails and the process exits
printf '# comment\n@2005-03-29 14:14:14\n' > "$WORKDIR/.faketimerc"
env -u FAKETIME -u FAKETIME_TIMESTAMP_FILE HOME="$WORKDIR" \
  LD_PRELOAD=./src/libfaketime.so.1 date
# libfaketime: In parse_ft_string(), failed to parse FAKETIME timestamp.
# Please check specification \n@2005-03 with format %Y-%m-%d %T

# 2) same timestamp, no comment — fakes correctly
printf '@2005-03-29 14:14:14\n' > "$WORKDIR/.faketimerc"
env -u FAKETIME -u FAKETIME_TIMESTAMP_FILE HOME="$WORKDIR" \
  LD_PRELOAD=./src/libfaketime.so.1 date +%s
# 1112076854

# 3) CR-terminated comment — no error, real clock is used
printf '# comment\r@2005-03-29 14:14:14\r' > "$WORKDIR/.faketimerc"
env -u FAKETIME -u FAKETIME_TIMESTAMP_FILE HOME="$WORKDIR" \
  LD_PRELOAD=./src/libfaketime.so.1 date +%s
# real epoch (not 1112076854)

# 4) CR timestamp without comment — still fakes (control)
printf '@2005-03-29 14:14:14\r' > "$WORKDIR/.faketimerc"
env -u FAKETIME -u FAKETIME_TIMESTAMP_FILE HOME="$WORKDIR" \
  LD_PRELOAD=./src/libfaketime.so.1 date +%s
# 1112076854

Use a throwaway HOME; do not overwrite a real ~/.faketimerc.

Suggested fix

Test the input character, and treat both \n and \r as end-of-line (consume CRLF as one separator):

--- a/src/libfaketime.c
+++ b/src/libfaketime.c
@@ -3449,16 +3449,21 @@ static void prepare_config_contents(char *contents)
       in_comment = true;
     }
     beginning_of_line = false;
 
-    if (*read_position == '\n') {
+    if (*read_position == '\n' || *read_position == '\r') {
       in_comment = false;
       beginning_of_line = true;
+      if (*read_position == '\r' && *(read_position + 1) == '\n')
+        read_position++;
     }
 
-    if (!in_comment && *read_position != '\r' && *write_position != '\n') {
+    if (!in_comment && *read_position != '\r' && *read_position != '\n') {
       *write_position = *read_position;
       write_position++;
     }
     read_position++;

After this, a leading # line is dropped and the timestamp line is kept, for LF, CR, and CRLF.

Environment

  • wolfcw/libfaketime 0.9.12 (bbb6c08)
  • Linux x86_64, stock make (-DFAKE_PTHREAD -DFAKE_STAT -DFAKE_UTIME …)

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