common/clock_time: wrapper for time_now() so we can override it.
What changed, and why it matters
This commit adds a developer-only feature that lets Core Lightning's internal clock be overridden via an environment variable (CLN_DEV_SET_TIME). It is intended for reproducible testing, not normal operation. The change itself is not a vulnerability, but it creates a new mechanism that could be misused if an attacker can set environment variables before launching the node.
Treat as a developer/testing aid rather than a security fix. If auditing, verify that CLN_DEV_SET_TIME is stripped from production service environments and that downstream commits replace all security-relevant time_now() calls with clock_time().
Security signals we found
New environment-variable-controlled time override (CLN_DEV_SET_TIME)
Developer-only code path in daemon_developer_mode()
Assertion that override must happen before first clock_time() use
No production use indicated; comments and naming suggest testing/reproducibility
Evidence from the diff
The commit introduces common/clock_time.{c,h}, wrapping time_now() with clock_time() so that a static dev_override can replace real time. A new environment variable CLN_DEV_SET_TIME is read in daemon_developer_mode() and passed to dev_override_clock_time(). The override is guarded by an assertion that it must be set before clock_time() is first used. The code is clearly marked as developer tooling (‘dev-’ prefix, getenv, comment ‘control TIME ITSELF’).
Changed components
common/clock_time.ccommon/clock_time.hcommon/daemon.cInspect captured patch +67 / −1
diff --git a/common/Makefile b/common/Makefile
index e5d3fa69..333e3321 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -21,6 +21,7 @@ COMMON_SRC_NOGEN := \
common/channel_config.c \
common/channel_id.c \
common/channel_type.c \
+ common/clock_time.c \
common/close_tx.c \
common/codex32.c \
common/coin_mvt.c \
diff --git a/common/clock_time.c b/common/clock_time.c
new file mode 100644
index 00000000..e3a982d7
--- /dev/null
+++ b/common/clock_time.c
@@ -0,0 +1,36 @@
+#include "config.h"
+#include <assert.h>
+#include <common/clock_time.h>
+
+static bool used = false;
+static struct timeabs dev_override;
+
+bool clock_time_overridden(void)
+{
+ return dev_override.ts.tv_sec != 0;
+}
+
+struct timeabs clock_time(void)
+{
+ used = true;
+ if (!clock_time_overridden())
+ return time_now(); /* discouraged: use clock_time so we can override */
+
+ return dev_override;
+}
+
+struct timeabs clock_time_progresses_(u64 *progress)
+{
+ if (!clock_time_overridden())
+ return clock_time();
+
+ return timeabs_add(dev_override, time_from_sec((*progress)++));
+}
+
+void dev_override_clock_time(struct timeabs now)
+{
+ assert(!used);
+
+ dev_override = now;
+ assert(clock_time_overridden());
+}
diff --git a/common/clock_time.h b/common/clock_time.h
new file mode 100644
index 00000000..a267abf4
--- /dev/null
+++ b/common/clock_time.h
@@ -0,0 +1,19 @@
+#ifndef LIGHTNING_COMMON_CLOCK_TIME_H
+#define LIGHTNING_COMMON_CLOCK_TIME_H
+#include "config.h"
+#include <ccan/short_types/short_types.h>
+#include <ccan/time/time.h>
+
+/* We use this instead of time_now, for overriding when we want reproducibility */
+struct timeabs clock_time(void);
+
+/* If you need a clock that progresses even when reproducible, use this. */
+#define clock_time_progresses() ({static u64 progress; clock_time_progresses_(&progress);})
+struct timeabs clock_time_progresses_(u64 *progress);
+
+/* dev setting to override time */
+void dev_override_clock_time(struct timeabs now);
+
+/* Did someone override time? */
+bool clock_time_overridden(void);
+#endif /* LIGHTNING_COMMON_CLOCK_TIME_H */
diff --git a/common/daemon.c b/common/daemon.c
index f7170ace..822b715a 100644
--- a/common/daemon.c
+++ b/common/daemon.c
@@ -8,6 +8,7 @@
#include <ccan/err/err.h>
#include <ccan/io/io.h>
#include <ccan/tal/str/str.h>
+#include <common/clock_time.h>
#include <common/daemon.h>
#include <common/memleak.h>
#include <common/randbytes.h>
@@ -202,7 +203,7 @@ void daemon_shutdown(void)
bool daemon_developer_mode(char *argv[])
{
bool developer = false, debug = false;
- const char *entropy_override;
+ const char *entropy_override, *time_override;
for (int i = 1; argv[i]; i++) {
if (streq(argv[i], "--dev-debug-self"))
@@ -233,6 +234,15 @@ bool daemon_developer_mode(char *argv[])
if (entropy_override)
dev_override_randbytes(argv[0], atol(entropy_override));
+ /* We can also control TIME ITSELF! */
+ time_override = getenv("CLN_DEV_SET_TIME");
+ if (time_override) {
+ struct timeabs t;
+ t.ts.tv_nsec = 0;
+ t.ts.tv_sec = atol(time_override);
+ dev_override_clock_time(t);
+ }
+
/* This checks for any tal_steal loops, but it's not free:
* only use if we're already using the fairly heavy memleak
* detection. */
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.