I wrote this patch/script to deal with a very irritating case of
circular dependency failure I recently encountered on an APU2 system.

On systems with non-existent or dead battery real time clocks, with DNS
over TLS being used, if your clock gets reset to a much earlier date
due to power off, DNS over TLS will cease to function due to certs not
being valid yet, thus DNS will fail entirely, meaning that pool.ntp.org
will fail to be looked up, and ntpd(8) can not fix the date. Thus
you arrive at a situation where DNS is broken because time is broken
because DNS is broken that is not fixable without manual intervention.

Similar situations can arise with DNSSEC, ntpd.conf(4) constraint
settings, as well as stuff like 802.1X authentication (although I have not
run into that specific case).

My solution is to sync the current system time to a file every 30
minutes, as well as on shutdown, and restore from it on boot if the
boot system time fails with some basic sanity checks. It is not a
perfect solution but there can't be a perfect solution as far as I
can see for this specific class of brokenness, and demanding users
bolt on an RTC to an embedded system so networking isn't fundamentally
broken seems like pretty extreme overkill. In any case this solution
is better than taking the system back to January 1st 1970.

It seems pretty universally useful, as RTC batteries die all the time,
and it rendering your network connectivity totally broken except for
manual intervention seems pretty awful for something like a remote
system, so I have not provided any knobs for turning it on and off.

It would make sense to emit the current date into the
/var/db/fakertc.time file from the installer, as well as do a clock
sanity check from /etc/daily but I wasn't certain how to modify these
files in the correct manner so I left them untouched.

Looking for feedback, thanks for your time
Index: libexec/fakertc/fakertc
===================================================================
RCS file: libexec/fakertc/fakertc
diff -N libexec/fakertc/fakertc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libexec/fakertc/fakertc	4 Jul 2018 16:49:28 -0000
@@ -0,0 +1,109 @@
+#!/bin/ksh
+#
+# $OpenBSD: fakertc.sh,v 1.0 2018/07/03 22:14:32 aal Exp $
+#
+# Copyright (c) 2018 Aaron Lancaster <[email protected]>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+set +o nounset 
+
+#subroutines
+
+# check to see if timefile is newer than current system clock, if it is 
+# not it is likely indicative of a broken or nonexistent RTC, which will 
+# play all sorts of havok with braindead crap like TLS cert expiry, 
+# which may hinder network connectivity all together, thus make ntpd 
+# unable to update the time, this creating a circular dependancy that
+# becomes unresovable without manual intervention
+
+check_timefile() {
+	if [[ -s $FILE ]]; then 
+		if [[ $(date -u +%s) -le $(cat $FILE) ]]; then
+			return 3
+		fi
+	else
+		echo "fakertc: $FILE missing!"
+		exit 2
+	fi
+}		
+
+# Restore time to the value stored in the timefile if we pass some basic
+# sanity checks, its not as good as a real RTC, but its better than 
+# time traveling back to January 1st 1970.
+
+restore_time() {
+	check_timefile
+	if [[ $? -eq 3 ]]; then
+		date $(date -ur $(cat $FILE) "+%C%y%m%d%H%M.%S")
+		echo "fakertc: Broken RTC, restoring last known time"
+	fi
+}
+
+# Apply basic sanity checks to see if the current time and the time 
+# in the fakertc file (if it exists) is both newer than /bsd and newer  
+# than the creation of this file 
+sane_time() {
+	if [[ $FAILSAFE_TIME -ge $(date -u +%s) ]] || \
+	   [[ $KERNEL_TIME -ge $(date -u +%s) ]]; then
+
+		echo -n "fakertc: Your clock is extremely out of date, "
+                echo "you may lack an RTC, and not" 
+                echo -n "fakertc: have your NTPD_FLAGS containing -s in " 
+                echo "/etc/rc.conf.local "
+		logger -p daemon.notice -t fakertc \
+			"Your clock is extremely out of date, check RTC"
+		exit 1
+	fi
+}	
+
+# save RTC time if if passes basic sanity checks for not being far in  
+# the past
+
+save_time() {
+	sane_time
+	date -u +%s > $FILE
+}
+
+export PATH=/bin:/sbin:/usr/bin:/usr/sbin
+
+FILE=/var/db/fakertc.time
+
+#No matter what it is impossible for time to be earlier
+#then when this file was created, or the mtime of the currently 
+#installed kernel. In UTC Unix time
+
+FAILSAFE_TIME=1530678400
+KERNEL_TIME=$(stat -f %m /bsd)
+
+
+case $1 in
+	check)
+		check_timefile
+		if [[ $? == 3 ]]; then
+			echo -n "Your system clock is badly out of date"
+		fi
+		;;
+	restore)
+		restore_time
+		;;
+	save) 
+		save_time
+		;;
+	*)
+		echo "usage: $0 check|restore|save"
+ 		exit 4
+		;;
+esac
+
+exit 0
Index: libexec/fakertc/Makefile
===================================================================
RCS file: libexec/fakertc/Makefile
diff -N libexec/fakertc/Makefile
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libexec/fakertc/Makefile	4 Jul 2018 16:49:54 -0000
@@ -0,0 +1,7 @@
+#	$OpenBSD: Makefile,v 1.0 2018/07/04 06:48:40 aal Exp $
+
+realinstall: 
+	${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} -m ${BINMODE} \
+		${.CURDIR}/fakertc ${DESTDIR}${BINDIR}/fakertc
+
+.include <bsd.prog.mk>
Index: libexec/Makefile
===================================================================
RCS file: /cvs/src/libexec/Makefile,v
retrieving revision 1.66
diff -u -p -u -r1.66 Makefile
--- libexec/Makefile	22 Aug 2017 06:44:06 -0000	1.66
+++ libexec/Makefile	4 Jul 2018 16:50:08 -0000
@@ -3,7 +3,7 @@
 
 .include <bsd.own.mk>
 
-SUBDIR= comsat fingerd ftpd getty ld.so lockspool login_chpass \
+SUBDIR= comsat fakertc fingerd ftpd getty ld.so lockspool login_chpass \
 	login_lchpass login_passwd login_radius login_reject \
 	login_skey login_token login_yubikey mail.local \
 	reorder_kernel \
Index: etc/rc
===================================================================
RCS file: /cvs/src/etc/rc,v
retrieving revision 1.525
diff -u -p -u -r1.525 rc
--- etc/rc	18 Feb 2018 18:52:02 -0000	1.525
+++ etc/rc	4 Jul 2018 16:50:27 -0000
@@ -322,6 +322,9 @@ if [[ $1 == shutdown ]]; then
 		echo warning: cannot write random seed to disk
 	fi
 
+	# save current RTC time on shutdown
+	/usr/libexec/fakertc save
+
 	# If we are in secure level 0, assume single user mode.
 	if (($(sysctl -n kern.securelevel) == 0)); then
 		echo 'single user: not running shutdown scripts'
@@ -427,6 +430,9 @@ fill_baddynamic udp
 fill_baddynamic tcp
 
 sysctl_conf
+
+#restore system time from file if RTC is dead or not present
+/usr/libexec/fakertc restore
 
 start_daemon slaacd >/dev/null 2>&1
 
Index: etc/crontab
===================================================================
RCS file: /cvs/src/etc/crontab,v
retrieving revision 1.22
diff -u -p -u -r1.22 crontab
--- etc/crontab	4 Sep 2016 09:37:26 -0000	1.22
+++ etc/crontab	4 Jul 2018 16:50:40 -0000
@@ -13,6 +13,8 @@ HOME=/var/log
 # send log file notifications, if necessary
 #1-59	*	*	*	*	/usr/bin/newsyslog -m
 #
+# save current time incase of rtc issues
+0,30	*	*	*	*	/usr/libexec/fakertc save > /dev/null 2>&1
 # do daily/weekly/monthly maintenance
 30	1	*	*	*	/bin/sh /etc/daily
 30	3	*	*	6	/bin/sh /etc/weekly
Index: share/man/man8/fakertc.8
===================================================================
RCS file: share/man/man8/fakertc.8
diff -N share/man/man8/fakertc.8
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ share/man/man8/fakertc.8	4 Jul 2018 16:51:36 -0000
@@ -0,0 +1,67 @@
+.\"	$OpenBSD$
+.\"
+.\" Copyright (c) 2018 Aaron Lancaster <[email protected]>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 4 2018 $
+.Dt FAKERTC 8
+‰.Os
+.Sh NAME
+.Nm fakertc
+.Nd RTC clock simulator
+.Sh SYNOPSIS
+.Nm
+.Ar check | set | restore
+.Sh DESCRIPTION
+.Nm
+provides a simulated real time clock for systems with nonexistant or 
+dead real time clocks.
+.Pp
+.Nm
+achieves this by saving the current system time periodically and 
+restoring it upon boot if it has found that the system time at boot is 
+in the past relative to the time stored in 
+.Pa /var/db/fakertc.time
+.Pp
+This is useful to help resolve certain circular dependancies that can 
+affect network connectivity, such as DNS via TLS. A grossly incorrect 
+date can cause certificate verification to fail, thus breaking 
+connectivity before it is possible to set time with 
+.Xr ntpd 8
+.Pp
+The arguements are as follows:
+.Bl -tag -width restore
+.It Cm check
+Check if .Pa /var/db/fakertc.time exists and contains a time that is not 
+in the future 
+.It Cm save
+Save the current time in .Pa /var/db/fakertc.time after performing 
+sanity checks
+.It Cm restore
+Set the current system clock to the time contained in .Pa 
+/var/db/fakerc.time after performing sanity checks.
+.El
+.Sh FILES
+.Bl -tag -width "/var/db/fakertc.time" -compact
+.It Pa /var/db/fakertc.time
+Time storage file 
+.El
+.Sh SEE ALSO
+.Xr date 1 , 
+.Xr ntpd 8
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 6.4 .

Reply via email to