libfaketime:基于系统调用拦截技术的时间伪造工具项目

libfaketime modifies the system time for a single application

分支4Tags13
当前项目代码仓暂无内容

libfaketime,版本 0.9.12(2025 年 6 月)

持续集成覆盖范围

必备的 CI 门禁覆盖 macOS arm64/arm64e,以及 Linux glibc、musl、 32-bit/time64、ARM64、Fedora、Debian、Rocky Linux 和 Arch Linux。 Sanitizer 和容器任务设有时间限制,并在某次运行失败时上传完整的测试日志。 针对特定运行时的检查被有意设为可选:某些语言运行时使用 vDSO 或直接系统调用, 能够绕过 LD_PRELOAD/dyld 拦截。这类绕过会被记录为兼容性限制, 而不会被视为 libfaketime 核心失败的证据。

要在本地执行有界的 Linux 运行,请使用兼容的镜像运行 test/docker_baseline.sh, 例如 gcc:13-bookworm。当需要显式选择镜像架构时,设置 DOCKER_PLATFORM=linux/amd64。仅当可选的 Rust 运行时探测及其工具链可用时, 才设置 LIBFAKETIME_RUN_OPTIONAL_RUNTIME_TESTS=1

本文件内容:

  1. 简介

  2. 兼容性问题

  3. 安装

  4. 使用 a) 基础用法 b) 使用绝对日期 c) 使用“start at”日期 d) 使用偏移量表示相对日期 e) 高级功能与注意事项 f) 在全系统范围内伪造日期和时间 g) 使用 “faketime” 包装脚本 h) 基于经过的时间或调用次数“限制”libfaketime i) 按进程“限制”libfaketime j) 派生外部进程 k) 将时间戳保存到文件,并从文件加载 l) 使用确定性数列替换随机数

  5. 许可证

  6. 联系方式

  7. 简介


libfaketime 会拦截程序用于获取当前日期和时间的各种系统调用,然后向这些程序 报告经过修改(伪造)的日期和时间(由您作为用户指定)。这意味着,您可以修改 某个程序所看到的系统时间,而无需更改全系统的时间。

libfaketime 允许您指定绝对日期(例如 01/01/2004)和相对日期(例如 10 天前)。

libfaketime 可用于多种目的,例如

  • 确定性构建流程
  • 调试时间相关问题,例如 SSL 证书过期
  • 测试软件的 year-2038 兼容性

libfaketime 附带一个名为 "faketime" 的命令行包装器,便于使用,但并未暴露 libfaketime 的全部功能。如果您的使用场景无法由 faketime 命令覆盖,请查阅 本文档,确认能否直接使用 libfaketime 实现。

  1. 兼容性问题

  • libfaketime 应在 Linux 和 macOS 上可用。 实际效果可能因环境而异;也有用户报告某些其他 *NIX 系统可以正常工作。

  • libfaketime 使用操作系统链接器(其参与启动程序)的库预加载机制,因此无法 用于静态链接的二进制文件,或已设置 setuid 标志的二进制文件(例如 ping 或 passwd 这类 suidroot 程序)。更多细节请参见系统链接器的手册页。

  • libfaketime 支持 pthreads 环境。会单独构建一个库(libfaketimeMT.so.1), 其中包含 pthread 同步调用。该库还会将经过 time() 拦截的调用单线程化, 因为库中静态缓存了若干变量,如果在不同步的情况下访问可能会引发问题。

    不过,这种性能开销可能会影响某些应用。如果情况如此,您可以尝试使用未同步 的 time() 拦截:从 Makefile 中移除 -DPTHREAD_SINGLETHREADED_TIME, 然后重新构建 libfaketimeMT.so.1

  • 基于 Java/JVM 的应用可以工作,但您需要额外传入一个参数 (FAKETIME_DONT_FAKE_MONOTONIC)。具体细节见下文的基础用法。 如果不传该参数,java 命令通常会挂起。

  • 应用如果显式自行动态加载系统库(例如 librt),而不是依赖链接器在应用启动时 完成加载,最终可能会绕过 libfaketime。除非您能修改这些应用,否则 libfaketime 对它们不起作用。

    这种情况在复杂的运行时环境中似乎十分常见,例如使用 golang 编写的程序、 某些 Java 虚拟机实现等。在这些情况下,libfaketime 实际上已被绕过, 因此我们无能为力。建议您向相应的开发者和供应商提出请求,让其以支持通过 LD_PRELOAD 拦截指定系统调用的方式实现运行时环境。

  • 应用可以被明确设计成阻止 libfaketime 工作,例如检查特定环境变量是否已设置, 或检查 libfaketime 专用文件是否存在。

  • 运行时兼容性:当语言运行时或框架报告真实时间时,请先确认它使用哪种时钟, 以及是否动态加载其自身的系统库。在将某个特定应用的报告判定为 libfaketime 缺陷之前,必须先提供一个使用相同时钟 API 的最小 C 复现用例。尤其需要留意: Go、Python、.NET、Boost.Asio、jemalloc 的使用者、Proton/Wine 以及 JVM 可能会缓存时钟、使用 vDSO/直接系统调用、在启动后加载库,或混合使用真实 时钟域与伪造时钟域。即使 libfaketime 基础测试套件通过,这些情况仍可能导致 挂起或忽略预加载。请显式保持预加载顺序,并在运行时要求真实单调时钟时使用 FAKETIME_DONT_FAKE_MONOTONIC。这类限制属于兼容性约束,而不是安全边界。

  • CLOCK_MONOTONIC 测试:运行 "make test" 会在 libfaketime 库成功编译后执行 一系列测试。在部分平台上,"CLOCK_MONOTONIC 测试" 似乎会永久挂起。 当且仅当这种情况出现在您的平台上时,才在 src/Makefile 中添加 CFLAG -DFORCE_MONOTONIC_FIX 并重新编译 libfaketime。 对于测试不会挂起的平台,请勿设置 FORCE_MONOTONIC_FIX。

    如果在 CLOCK_REALTIME 测试中观察到挂起,请添加 CFLAG -DFORCE_PTHREAD_NONVER。如果仅设置 FORCE_MONOTONIC_FIX 无法解决 MONOTONIC_CLOCK 测试中的挂起问题,也要设置 FORCE_PTHREAD_NONVER 标志。

    如果未将 FORCE_MONOTONIC_FIX 设置为编译期标志,您也可以通过设置环境变量 FAKETIME_FORCE_MONOTONIC_FIX=1 在运行时启用该修复,或设置为 0 以显式禁用它。如果 libfaketime 是在底层 libc 实现为 glibc 的系统上编译的, 并且运行时检测到假设需要该变通方案的 glibc 版本,则该修复会自动启用。 如果 CLOCK_MONOTONIC 测试期间观察到任何挂起,请通过 https://github.com/wolfcw/libfaketime/issues 的 Github issues 报告, 并附上 CPU 架构、libc 实现(例如 glibc 2.30)以及其他可能有用的细节 (例如 Linux 发行版、是否运行在 Docker 容器中等)。

    请尽量避免在无需该标志的平台上使用 FORCE_MONOTONIC_FIX 编译。 虽然大多数情况下不会有影响,但根据所使用 FAKETIME 的具体设置, 它可能导致某些被拦截函数(例如 pthread_cond_timedwait())过早或过晚 返回超时结果,从而破坏某些应用。请先尝试不设置 FORCE_MONOTONIC_FIX 进行编译,并检查测试是否出现挂起。如果出现,您可以将 FAKETIME_FORCE_MONOTONIC_FIX 环境变量设为 1,或者设置 FORCE_MONOTONIC_FIX 重新编译。

  1. 安装

运行 "make" 会编译两个库版本和一个测试程序,随后也会执行该测试程序。

如果测试正常通过,您应把 libfaketime 库(libfaketime.so.1 和 libfaketimeMT.so.1)复制到期望的位置。运行 "make install" 会尝试将它们 放入 /usr/local/lib/faketime,并把包装 shell 脚本 "faketime" 安装到 /usr/local/bin,这两项操作很可能都需要 root 权限。不过,从技术角度看, 并不一定要进行全系统安装,因此即使在没有 root 权限的机器上也可以使用 libfaketime。您可能需要根据相应 Makefile 调整 PREFIX 变量。

默认情况下,Makefile 会针对您的默认系统架构编译/链接 libfaketime。 如果您需要在 64 位平台上构建 32 位文件,请参见 src/Makefile 中关于 CFLAGS 和 LDFLAGS 的说明。

自 0.6 版本起,针对文件时间戳的系统调用也会被拦截,这得益于 Philipp Hachtmann 的贡献。当程序读写文件且这些文件的时间戳也需要被伪造时, 结合下文 4d) 中说明的相对时间偏移,这一功能尤为有用。如果您不需要该功能, 或它会使您希望配合 FTPL 使用的应用感到困惑,请定义环境变量 NO_FAKE_STAT, 这样被拦截的 stat 调用将原样透传。

在 macOS 上,由于 dyld 的行为不同,必须以不同方式编译。请使用提供的 Makefile.OSX 文件编译 libfaketime.1.dylib。此外,不要使用 LD_PRELOAD, 而应将变量 DYLD_INSERT_LIBRARIES 设置为 libfaketime.1.dylib 的路径, 并设置变量 DYLD_FORCE_FLAT_NAMESPACE(可设为任意值)。macOS 用户应阅读 README.OSX 以获取更多细节。

在 macOS 和 Linux 上测试

标准测试命令会构建特定平台的库,并运行完整的功能测试套件:

make test

在 macOS 上,顶层 Makefile 会自动选择 Darwin 的 makefiles。 Linux 构建可以在一次性 Docker 容器中进行测试,而不会将构建产物写入代码检出目录。基线测试脚本接受一组本地可用的镜像:

test/docker_baseline.sh gcc:13-bookworm debian:13 alpine:3.20

在 ARM64 macOS 主机上,需要显式指定目标平台。专用封装脚本还会在启动容器前检查每个镜像的架构:

DOCKER_PLATFORM=linux/arm64 test/docker_arm64.sh
gcc:13-bookworm debian:13 alpine:3.20

标记为其他架构的镜像必须替换为 ARM64 镜像;Docker 模拟无法让一个不包含 ARM64 的镜像清单被该测试脚本使用。一旦有匹配的 ARM64 镜像可用,就可以添加 Arch Linux:

DOCKER_PLATFORM=linux/arm64 test/docker_arm64.sh archlinux:base-devel

在 Linux 上进行内存安全检查时,sanitizer 测试脚本会使用 AddressSanitizer 和 UndefinedBehaviorSanitizer 进行构建,并在 LD_PRELOAD 中将 sanitizer 运行时放在 libfaketime 之前:

DOCKER_PLATFORM=linux/arm64 test/docker_sanitizers.sh gcc:13-bookworm

sanitizer 运行只会跳过那些 preload 配置与 sanitizer 插桩不兼容的辅助进程检查。仅限 Linux 的功能在 macOS 上会被报告为跳过;这样可以保持两个平台上通用时钟、配置、进程和生命周期覆盖范围具有可比性。

  1. 使用

4a) 使用基础

在任意指定程序上使用 libfaketime 包括两个步骤:

  1. 确保 libfaketime 被系统链接器加载。
  2. 指定伪造的时间。

举个例子,我们希望 "date" 命令报告我们伪造的时间。为此,可以在 Linux 上使用以下命令行:

user@host> date Tue Nov 23 12:01:05 CEST 2016

user@host> LD_PRELOAD=/usr/local/lib/libfaketime.so.1 FAKETIME="-15d" date Mon Nov 8 12:01:12 CEST 2016

user@host> LD_PRELOAD=/usr/local/lib/libfaketime.so.1 FAKETIME="-15d" FAKETIME_DONT_FAKE_MONOTONIC=1 java -version java version "1.8.0_111" Java(TM) SE Runtime Environment (build 1.8.0_111-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)

启用 libfaketime 来运行任意命令/程序的基本方法是确保环境变量 LD_PRELOAD 包含 libfaketime 库的路径和文件名。可以提前一次性设置:

export LD_PRELOAD=/path/to/libfaketime.so.1 (现在运行任意你想要的命令)

也可以在命令行中直接指定:

LD_PRELOAD=/path/to/libfaketime.so.1 your_command_here

(这些示例适用于 bash shell;在你的系统上,设置环境变量的方式可能有所不同。)

在 Linux 上,库搜索路径可以作为链接器配置的一部分进行设置。 这样,LD_PRELOAD 也可以使用相对路径。例如,如果 libfaketime.so.1 被安装为 /path/to/libfaketime.so.1,你可以将 /path/to 添加到相应的 链接器配置文件中,例如 /etc/ld.so.conf.d/local.conf,然后运行 "ldconfig" 命令。之后,使用 LD_PRELOAD=libfaketime.so.1 即可。

不过,还需要指定伪造的时间;否则,libfaketime 虽然会加载,但只会报告真实的系统时间。指定伪造时间有多种方式:

a) 通过设置环境变量 FAKETIME。 b) 通过使用环境变量 FAKETIME_TIMESTAMP_FILE 指定的文件 c) 通过使用你主目录下的 .faketimerc 文件。 d) 通过使用 /etc/faketimerc 文件作为系统级默认设置。 e) 通过使用 FAKETIME_UPDATE_TIMESTAMP_FILE 和 date -s "" 或类似命令。

如果你想使用 b)、c) 或 d),$HOME/.faketimerc 或 /etc/faketimerc 仅包含一行文本,其内容与下文所述的 FAKETIME 环境变量完全相同。请注意,只有当不存在 $HOME/.faketimerc,且不存在 FAKETIME_TIMESTAMP_FILE 文件时,才会使用 /etc/faketimerc。 此外,FAKETIME 环境变量的优先级 始终 高于这些文件。关于 FAKETIME_UPDATE_TIMESTAMP_FILE,请参见下文。

4b) 使用绝对日期

必须用于 绝对 日期的格式是 "YYYY-MM-DD hh:mm:ss"。 例如,2020 年 12 月 24 日 20:30 必须指定为 FAKETIME="2020-12-24 20:30:00"。

4c) 使用 'start at' 日期

(感谢 David North 在 0.7 版中对 TDI 的重要贡献)

必须用于 start_at 日期的格式是 "@YYYY-MM-DD hh:mm:ss"。 例如,2020 年 12 月 24 日 20:30 必须指定为 FAKETIME="@2020-12-24 20:30:00"。

4b) 中描述的绝对日期会模拟一个停留在指定绝对时间的系统时钟。'start at' 格式允许实现如下 4d) 节所述的“相对”时钟行为,但它使用的是 'start at' 时间,而不是偏移时间。

如果已启动的进程本身又启动其他(子)进程,它们默认会再次从指定的 start-at 日期开始。如果这不是你需要的行为,请设置环境变量 FAKETIME_DONT_RESET=1。可通过这些示例查看区别:

LD_PRELOAD=src/libfaketime.so.1 FAKETIME="@2000-01-01 11:12:13"
FAKETIME_DONT_RESET=1
bash -c 'while [ $SECONDS -lt 5 ]; do date; sleep 1; done'

LD_PRELOAD=src/libfaketime.so.1 FAKETIME="@2000-01-01 11:12:13"
bash -c 'while [ $SECONDS -lt 5 ]; do date; sleep 1; done'

在第二个示例中,"date" 命令始终会打印相同的时间;而在第一个示例中,由于设置了 FAKETIME_DONT_RESET,即使所有 "date" 命令都是新进程,时间仍会继续递增。

4d) 使用偏移量处理相对日期

相对日期偏移量可以为正或负,因此写入 FAKETIME 的内容 必须 以 + 或 - 开头,后跟一个数字,并可选地后跟一个乘数:

  • 默认情况下,指定的偏移量以秒为单位。示例:

    export FAKETIME="-120" 将伪造时间设置为比真实时间慢 2 分钟(120 秒)。

  • 乘数 "m"、"h"、"d" 和 "y" 可分别用于将偏移量指定为分钟、小时、天和年(每年按 365 天计算)。示例:

    export FAKETIME="-10m" 将伪造时间设置为比真实时间慢 10 分钟。 export FAKETIME="+14d" 将伪造时间设置为 14 天后的未来时间。

    请注意,如果你需要其他乘数(周、月等)或更高精度(例如正确处理闰年),应使用 faketime wrapper 或 GNU date 命令,如下面三个示例中的第一个所示。

至此,你应该能够理解我们之前使用的完整示例:

LD_PRELOAD=/usr/local/lib/libfaketime.so.1 FAKETIME="-15d" date

该命令行确保加载 libfaketime,并将伪造时间设置为 15 天前。

Moreno Baricevic 贡献了对 FAKETIME_FMT 环境变量的支持,使你能够可选地设置 strptime() 格式:

一些简单示例: LD_PRELOAD=./libfaketime.so.1 FAKETIME_FMT=%s FAKETIME="date +%s -d'1 year ago'" date LD_PRELOAD=./libfaketime.so.1 FAKETIME_FMT=%s FAKETIME="stat -c %Y somefile" date LD_PRELOAD=./libfaketime.so.1 FAKETIME_FMT=%c FAKETIME="date" date

4e) 高级功能与注意事项

高级时间指定选项:

自 0.8 版本起,感谢 Karl Chen 的贡献,时间偏移量的指定可以使用小数。例如,

FAKETIME="+1,5h"

等价于 FAKETIME="+90m"。请注意,小数分隔符取决于你的区域设置,因此实际上你可能需要使用

FAKETIME="+1.5h"

你应该自行确认正确的分隔符,例如对 /bin/date 等命令使用 libfaketime,这样就能立刻验证其是否按预期生效。

Karl Chen 在 v0.8 中还贡献了一个选项,可用于加速或减慢使用 libfaketime 执行的程序的墙钟时间。例如,

FAKETIME="+1y x2"

会将伪造时间设置为未来一年,并让时钟以两倍速度运行。同样,

FAKETIME="+1y x0,5"

会让时钟仅以一半速度运行。如上所述,小数分隔符取决于你的区域设置。此外,

FAKETIME="+1y i2,0"

会让时钟在每次 time() 等调用时前进两秒,完全独立于系统时钟。这有助于以一定的确定性运行程序。仅此一种情况下,所有派生进程都将使用同一个全局时钟,而不会在每个进程启动时重新初始化它。

请注意,在 FAKETIME 中使用 "x" 或 "i" 仍然需要给出偏移量(参见 4d 节)。这意味着 "+1y x2" 可以生效,但仅写 "x2" 不行。如果你不想伪造时间,只想修改时钟速度,请使用类似 "+0 x2" 的写法,即在 FAKETIME 中显式使用零偏移量作为前缀。

为了测试,你应该运行类似以下命令:

LD_PRELOAD=./libfaketime.so.1 FAKETIME="+1,5y x10,0"
bash -c 'while true; do echo $SECONDS ; sleep 1 ; done'

每当无限循环休眠一秒时,正在运行的 bash 外壳会认为已经过去了 10 秒($SECONDS 是 bash 内置变量,用于测量自该外壳启动以来经过的时间)。

(请注意,例如将 "echo $SECONDS" 替换为对 "/bin/date" 的调用,不会得到预期结果,因为 /bin/date 始终会作为新进程启动,libfaketime 也会为其重新初始化。它会显示正确的偏移量(未来 1.5 年),但不会体现加速或减速。)

对于每次运行时都应使用不同日期和时间的应用程序,可以考虑使用附带的 timeprivacy 封装 Shell 脚本(由 adrelanos at riseup dot net 提供)。

注意事项:

尽可能使用相对偏移量或“start at”日期, 而不要使用绝对日期。

为什么?因为你设置的绝对日期/时间是固定的,也就是说,如果某个程序 获取当前时间,并在 5 分钟后再次获取当前时间, 它两次仍然会得到相同的结果。这很可能会破坏那些通过测量经过时间来工作的程序 (例如,每 X 分钟检查新邮件的邮件程序)。

使用相对偏移量或“start at”日期可以解决此问题。 libfaketime 随后会始终基于真实当前时间和你指定的偏移量 报告伪造时间。

也请注意,为了提升库的性能,伪造时间的默认配置会被缓存 10 秒。因此,如果一个程序正在运行时,你更改了 $HOME/.faketimerc 或 /etc/faketimerc 的内容, 新的伪造时间可能需要最多 10 秒才会生效。如果你的场景中这是一个问题, 你可以通过环境变量 FAKETIME_CACHE_DURATION 更改文件被重新读取前等待的秒数, 或者使用 FAKETIME_NO_CACHE=1 完全禁用缓存。请记住,禁用缓存可能 会对性能产生负面影响(尤其是未使用 FAKETIME 环境变量, 而使用配置文件,例如 FAKETIME_TIMESTAMP_FILE 时)。

通过文件时间戳设置 FAKETIME

基于 Hitoshi Harada(umitanuki)的提案,“start at”时间现在可以通过 文件系统中的任意文件进行设置:将 FAKETIME 环境变量 设置为“%”(百分号),并将 FAKETIME_FOLLOW_FILE 设置为 其修改时间戳将用作“start at”时间来源的文件名。

文件时间戳钩子契约

FAKE_FILE_TIMESTAMPS(也可由 FAKE_UTIME 启用)是一个选择性启用的特性。 其经过测试的契约目前涵盖诸如 utimensat() 等设置时间戳的调用,以及 stat()lstat() 返回的时间戳结果。 fstatat() 仍然可以调用,并针对成功执行进行了测试,但 其结果并不保证在所有 libc 上都会带有相同的虚拟时间戳: Linux libc 版本使用不同的 __fxstatat() 和时间宽度符号路径, 而 macOS 使用不同的 dyld 符号拦截模型。需要 此行为的应用程序应使用 stat()/lstat(),直到提供 特定 ABI 的契约。

使用示例:

创建任意文件,并设置其时间戳为 2009 年 12 月 24 日 12:34:56

touch -t 0912241234.56 /tmp/my-demo-file.tmp

运行一个 bash shell,通过无限循环打印当前时间

LD_PRELOAD=/path/to/libfaketime.so.1
FAKETIME='%' FAKETIME_FOLLOW_FILE=/tmp/my-demo-file.tmp
FAKETIME_DONT_RESET=1
bash -c 'while true ; do date ; sleep 1 ; done'

现在,在上述进程运行期间,修改该文件的时间戳

(在另一个终端窗口或其他任意位置)

touch -t 2002290123.45 /tmp/my-demo-file.tmp

设置环境变量 FAKETIME_FOLLOW_ABSOLUTE=1 可启用 FAKETIME_FOLLOW_FILE 行为的一种子模式:在这种模式下,模拟时间仅当被跟踪文件的时间戳前进时才前进。在此模式中,未受 libfaketime 的 LD_PRELOAD 拦截的应用可以完全控制被 libfaketime 挂钩的应用的时间。例如,宿主应用可以控制映射到容器中的被跟踪文件的时间戳,从而为容器内运行的模拟时间应用实现(相对)干净的暂停/恢复行为。

在运行时更改 'x' 修饰符

使用 FAKETIME_TIMESTAMP_FILE 可以方便地在程序运行期间更改 FAKETIME 设置:

echo "+0 x1" > /tmp/my-faketime.rc LD_PRELOAD=libfaketime.so.1 FAKETIME_TIMESTAMP_FILE="/tmp/my-faketime.rc"
FAKETIME_NO_CACHE=1 ./some-program & sleep 10 ; echo "+0 x10" > /tmp/my-faketime.rc

在运行时改变墙钟时间的速度,也就是使用不同的 'x' 修饰符,默认情况下可能会导致较大的时间跳变,使程序产生混乱。例如,如果程序已在 'x1' 下运行了 10 秒,随后设置改为 'x10',模拟时间在程序看来将像是已经运行了 100 多秒,而不仅仅是 10 多秒。

将环境变量 FAKETIME_XRESET 设置为任意值,可显著平滑不同 'x' 修饰符值之间的切换:

LD_PRELOAD=libfaketime.so.1 FAKETIME_TIMESTAMP_FILE="/tmp/my-faketime.rc"
FAKETIME_NO_CACHE=1 FAKETIME_XRESET=1 ./some-program &

设置 FAKETIME_XRESET 可确保墙钟时间仅在 'x' 修饰符被增大后才开始变快,并确保所报告的模拟时间不会跳回到过去的时间值(在减小时)。

请注意,FAKETIME_XRESET 在内部通过重置 libfaketime 的计时数据结构来实现,这可能会影响所报告的文件时间戳。目前,FAKETIME_XRESET 应被视为实验性功能。

清理共享内存

在支持信号量和共享内存的平台上,libfaketime 会使用信号量和共享内存, 以便在父进程与子进程之间同步 faketime 配置。

请注意,这并不会共享通过 settimeofday 设置的时间 (相关内容见下文中的 FAKETIME_UPDATE_TIMESTAMP_FILE)。

当 libfaketime 正常退出时,它会自行清理。 但是,当进程被终止(例如在命令行按 Ctrl-C)时, 共享内存无法被正确清理。在这种情况下, 应定期删除

/dev/shm/faketime_shm_* 以及 /dev/shm/sem.faketime_sem_*

(手动操作,或妥善自动化)。已终止进程遗留在此处的文件通常没有问题, 但如果下次启动进程时,其进程 ID 对应着已存在的陈旧信号量或共享内存, 就会引发 libfaketime 错误。因此,如果遇到以下错误信息

libfaketime: In ft_shm_create(), shm_open failed: File exists

请按上述说明清理 /dev/shm。这对长期运行的系统(长时间保持运行的服务器) 以及频繁启动大量进程的系统(例如承载许多容器 或类似虚拟化机制的服务器)尤其重要。

可以通过设置环境变量 FAKETIME_DISABLE_SHM 来禁用共享内存的使用, 或者等效地向 faketime 传递 --disable-shm 参数。

拦截时间设置调用

编译 libfaketime 时可以使用 CFLAG “-DFAKE_SETTIME”, 以便同时拦截时间设置函数,即 clock_settime()、 settimeofday() 和 adjtime()。每次调用时,环境变量 FAKETIME 都会被调整。

当设置了环境变量 FAKETIME_TIMESTAMP_FILE,并且它指向一个 可写(可创建)的自定义配置文件,同时环境变量 FAKETIME_UPDATE_TIMESTAMP_FILE 为 “1” 时,该文件也会随每次调用更新。 由此,多个进程可以共享同一套“虚拟时间”, 并且每个进程都可以为所有进程调整时间。

在独立进程间共享“虚拟可设定时间”

当 libfaketime 使用 FAKETIME_COMPILE_CFLAGS="-DFAKE_SETTIME" 编译后, 可以配置为支持多个进程共享同一个时间偏移量。 例如,这允许普通用户使用 “ntpdate” 而不影响系统时钟, 以不同日期交互式测试软件,或测试多个独立进程且这些进程内部使用 settime 的复杂软件。

示例:

$ export LD_PRELOAD=libfaketime.so.1 $ export FAKETIME_TIMESTAMP_FILE="/tmp/my-faketime.rc" $ export FAKETIME_UPDATE_TIMESTAMP_FILE=1 $ export FAKETIME_CACHE_DURATION=1 # 以秒为单位

或:export FAKETIME_NO_CACHE=1

$ date -s "1999-12-24 16:00:00" Fri Dec 24 16:00:00 CET 1999 $ LD_PRELOAD="" date Thu Apr 9 15:19:38 CEST 2020 $ date Fri Dec 24 16:00:02 CET 1999 $ /usr/sbin/ntpdate -u clock.isc.org 9 Apr 15:18:37 ntpdate[718]: step time server xx offset 640390517.057257 sec $ date Thu Apr 9 15:18:40 CEST 2020

在另一个终端、脚本或环境中,可以设置相同的变量, 并打印相同的时间。 这同样避免了为使用不同时间而直接更新 rc 配置文件的需要, 但当然只支持时间偏移量。

请注意,该功能与若干其他功能不兼容, 例如 FAKETIME_FOLLOW_FILE、FAKETIME_XRESET,以及其他可能的功能。 首次 settime 之后,FAKETIME_TIMESTAMP_FILE 中将使用偏移量,即使它 最初使用的是高级时间指定选项。

4f) 在系统范围内伪造日期和时间

SourceForge, Inc. 的 David Burley 报告了将 libfaketime 应用于系统范围的一个有趣用例:目前,所有运行在 OpenVZ 主机内的虚拟机都具有相同的系统日期和时间。为了使用多个 系统日期不同的沙箱,可以将 libfaketime 库放入 /etc/ld.so.preload;之后,它会自动应用到所有命令和程序。 这当然最好与系统范围的 /etc/faketimerc 文件一起使用。感谢 SourceForge, Inc. 提供的补丁!

注意:如果运行虚拟机,重启时其实时时钟可能会重置为现实世界 的日期和时间。根据你的 FAKETIME 设置, 这可能带来副作用,例如每次重启都强制进行文件系统检查。 系统范围的伪造时间也可能导致软件自动更新工具 出现意外的副作用,如果现实世界时间与伪造系统时间 之间的偏移量过大。如有疑问,请将系统日期设置为伪造时间, 先验证一切是否仍按预期工作,然后再在系统范围内应用 libfaketime。

4g) 使用 “faketime” 封装器

从 0.8 版本开始,libfaketime 提供了一个名为 “faketime” 的命令, 它会在 “make install” 时安装到 /usr/bin。它省去了手动设置 LD_PRELOAD 和 FAKETIME 环境变量的麻烦,但只开放了 libfaketime 功能的一部分。另一方面,它借助 /bin/date 的日期解析功能, 从而在指定伪造日期和时间方面提供更高的灵活性。例如,你可以使用

faketime 'last Friday 5 pm' /your/command/here

当然,也可以使用绝对日期,例如:

faketime '2018-12-24 08:15:42' /bin/date

感谢 Daniel Kahn Gillmor 提供这些建议!

Balint Reczey 在 0.9.5 中将该封装器从一个简单的 Shell 脚本 重写为一个高效的封装器程序。

4h) 基于已用时间或调用次数“限制” libfaketime

从 0.9 版本开始,libfaketime 可以配置为不持续启用, 而仅在某个特定时间区间内启用。

例如,你可能希望程序以当前真实时间启动,但在使用 5 分钟后, 让它看到一个伪造时间,例如一年之后的时间。

也可以通过以下方式动态更改伪造时间:

  • 在运行时更改 FAKETIME 环境变量;如果你作为程序员使用 libfaketime 进行调试和测试,这是首选方式,因为它能让你最直接地控制 libfaketime, 且不会带来任何性能损失。

  • 不使用 FAKETIME 环境变量,而是在文件(如 ~/.faketimerc)中指定伪造时间。 你可以在运行时更改该文件的内容。这种方式在禁用缓存时效果最好, 但会带来性能开销,因为每次都需要读取并解析该文件。

这里描述的功能基于两对环境变量,

FAKETIME_START_AFTER_SECONDS 和 FAKETIME_STOP_AFTER_SECONDS,以及 FAKETIME_START_AFTER_NUMCALLS 和 FAKETIME_STOP_AFTER_NUMCALLS

这些环境变量的默认值均为 -1,表示“忽略该值”。

如果你希望 libfaketime 仅在应用运行后的第 2 到第 5 分钟内启用, 请设置

FAKETIME_START_AFTER_SECONDS=60 FAKETIME_STOP_AFTER_SECONDS=300

这意味着你的应用将从启动(第 0 秒)开始到第 60 秒使用真实时间。 随后,从运行第 60 秒到第 300 秒(第 2、3、4 和 5 分钟), 它将看到伪造时间。在运行第 600 秒后,它又会看到 真实(未伪造)时间。

这种方式不如在运行时更改 FAKETIME 环境变量灵活,但可能更容易使用, 作用于单个程序范围(而不是按用户或系统范围),且只带来轻微的性能开销。

使用另一对环境变量,你可以基于被启动程序执行的时间相关函数调用次数, 而不是实际经过的秒数,来限制 libfaketime 的活动时间。这种替代方式 可能只适合那些了解程序代码以便确定有用的启动/停止值, 或想要执行模糊测试的程序员。

两对环境变量可以组合使用,以进一步限制 libfaketime 的活动,尽管这种用法只在极少数场景中有用。

通常不建议以这种方式限制 libfaketime 的活动。许多 程序在遭遇时间突变时会出错,尤其是当它们以当前(真实)时间启动, 随后在例如 5 分钟后被送回过去时。例如,它们可能看似冻结 或卡住,因为在等待某个时间点,然而由于 libfaketime 延迟启用, 该时间点永远不会到达。除非你确定确实需要该功能并清楚 自己在做什么,否则请避免使用此功能。

4i) 按进程“限制”libfaketime

faketime 可以被配置为仅对指定命令模拟时间相关调用,或者对除某些命令以外的每条命令模拟时间。

环境变量分别为 FAKETIME_ONLY_CMDS 和 FAKETIME_SKIP_CMDS。

示例: FAKETIME_ONLY_CMDS=javadoc faketime '2008-12-24 08:15:42' make 这将运行 “make” 命令,但时间模拟仅应用于 javadoc 进程。

多个命令之间使用逗号分隔。

示例: FAKETIME_SKIP_CMDS="javadoc,ctags" faketime '2008-12-24 08:15:42' make 这将运行 “make” 命令,并对 “make” 所执行的所有内容应用时间模拟,但 javadoc 和 ctags 进程除外。

FAKETIME_ONLY_CMDS 和 FAKETIME_SKIP_CMDS 互斥,即不能同时设置二者。如果两个环境变量都被设置,faketime 将打印错误信息并终止。

4j) 启动外部进程

从 0.9 版本开始,libfaketime 可以在 a) 经过任意秒数,或 b) 程序自启动以来已经发起一定数量的时间相关系统调用之后,执行一次 shell 命令。需要注意,这有两个限制:

  • 启动外部进程发生在原始程序的一次时间相关系统调用期间。如果希望外部进程在程序启动 5 秒后启动,但该程序在运行到第 8 秒前都没有发起任何时间相关系统调用,外部进程的启动就会被延迟到运行时间的第 8 秒。

  • 原始程序会被阻塞,直到外部进程结束,因为被拦截的时间相关系统调用不会提前返回。如果需要启动一个长时间运行的外部进程,请确保其通过 fork 转入后台。

启动外部进程由以下三个环境变量控制: FAKETIME_SPAWN_TARGET、FAKETIME_SPAWN_SECONDS、FAKETIME_SPAWN_NUMCALLS。

安全警告:FAKETIME_SPAWN_TARGET 会被传递给 shell。这是有意设计的命令执行功能,并不是安全的参数传递接口。当环境变量可能受到不可信用户影响时,请勿启用它;也不要将其用于 setuid/setgid 程序、特权守护进程或其他安全敏感进程。这些使用场景需要无 shell 的基于 argv 的接口。该接口通过 FAKETIME_SPAWN_EXEC 以及 FAKETIME_SPAWN_ARG_1 至 FAKETIME_SPAWN_ARG_8 提供。FAKETIME_SPAWN_EXEC 会在不经过 shell 的情况下直接执行指定文件;带编号的参数必须连续,并且基于 shell 的 FAKETIME_SPAWN_TARGET 与 FAKETIME_SPAWN_EXEC 设置不能组合使用。

示例(在 Linux 上使用 bash):

(... 此处为通常的 libfaketime 配置,设置 LD_PRELOAD 和 FAKETIME ...) export FAKETIME_SPAWN_TARGET="/bin/echo 'Hello world'" export FAKETIME_SPAWN_SECONDS=5 /opt/local/bin/myprogram

这将在 “myprogram” 运行满 5 秒后,于其第一次执行时间相关系统调用期间,使用给定参数运行 “echo” 命令。

4k) 将时间戳保存到文件,以及从文件加载时间戳

要通过 同一个 文件存储和加载时间戳 偏移量,以便在独立进程之间共享公共“虚拟时间”,请参阅上文中的 FAKETIME_UPDATE_TIMESTAMP_FILE。FAKETIME_SAVE_FILE 功能则有所不同。

faketime 可以将模拟时间戳保存到由 FAKETIME_SAVE_FILE 环境变量指定的文件。它也可以使用 FAKETIME_LOAD_FILE 指定的文件来回放其中的时间戳。在处理完整个文件后,libfaketime 将恢复使用 FAKETIME 变量中设置的规则,但时间戳进程 将从文件中的最后一个时间戳开始计数。

该文件将每个时间戳保存为 saved_timestamp 结构体流, 不包含任何元数据或填充:

/* 写入文件的时间戳存储格式。大端序。 */ struct saved_timestamp { int64_t sec; uint64_t nsec; };

若要使用这些文件,faketime 必须通过 faketime 封装器运行。此 功能由 Balint Reczey 在 v0.9.5 中添加。

4l) 用确定性数字序列替换随机数

编译 libfaketime 时可设置 CFLAG FAKE_RANDOM(参见 src/Makefile)。

此可选功能目前仅在 Linux 上受支持。 请勿在 macOS 构建中启用 FAKE_RANDOM:Darwin 的 getentropy() 拦截路径 无法安全调用原始系统实现,并可能在进程启动期间递归或 中止。 Darwin 支持仍是后续工作。

以这种方式编译后,libfaketime 还会拦截对 函数 getrandom() 的调用,该函数是 Linux 专有的。

此功能旨在向使用 getrandom() 的应用程序提供一系列确定性、可重复 的数字,而不是 /dev/[u]random 提供的随机数。

为了生成确定性数字序列,libfaketime 内部 使用 Bernard Widynski 的 Middle Square Weyl Sequence Random Number Generator, 参见 https://mswsrng.wixsite.com/rand。

它需要一个 64 位种子值,必须通过环境变量 FAKERANDOM_SEED 传递,例如

LD_PRELOAD=src/libfaketime.so.1
FAKERANDOM_SEED="0x12345678DEADBEEF"
test/getrandom_test

只要使用相同的种子值,就会生成相同的“看似随机” 数字序列。

请注意,这必然会破坏 getrandom() 所提供的随机数可能具有的任何安全属性,例如在 密码学操作场景中。仅将其用于确定性测试 目的。切勿在生产环境中使用。

可选 FAKE_PID 功能仅在 Linux 上受支持。在 macOS 构建中提供 FAKETIME_FAKEPID 可能使进程启动不稳定并导致 中止,因为 Darwin 运行时代码依赖真实进程身份。 请勿在 macOS 上启用它;Darwin 支持仍是后续工作。

关于为何将这一看似与日期/时间无关的函数 加入 libfaketime,以及它可能如何演变,请参阅 Github issue #275。

  1. 许可证

libfaketime 已按照 GNU General Public License, GPL 发布。 请参阅随附的 COPYING 文件。

  1. 联系方式

非常欢迎提交错误报告、功能建议、成功反馈以及补丁/合并请求:

    https://github.com/wolfcw/libfaketime

项目介绍

time-warping功能使单个应用程序的时间扭曲成为可能libfaketime通过修改单个应用程序的系统时间,实现了时间扭曲的功能。该功能让单个应用程序的时间调整变得可行。【此简介由AI生成】

定制我的领域
503.11 K378访问 GitHub