GitHub's downtime as a FreeBSD entropy source.

For each pair of consecutive resolved GitHub incidents, emit 1 if the later

outage lasted longer than the earlier one and 0 if it lasted less. A Rust

daemon derives and conditions the bits; a C kernel module hands them to Fortuna

so that GitHub Downtime appears in kern.random.random_sources.

$ sysctl kern.random.random_sources

kern.random.random_sources: 'GitHub Downtime','Intel Secure Key Seed'

$ sysctl kern.random.ghrandom.seconds_to_next_seed

kern.random.ghrandom.seconds_to_next_seed: 1877014 # 21.7 days, for 32 bits

Incident durations are public. Anyone polling githubstatus.com derives the

identical bitstream, so this has no secrecy value whatsoever.

The module therefore discards everything written to it until you set

kern.random.ghrandom.claim_entropy=1 by hand, which prints a console warning.

Feeding known data to Fortuna cannot subtract entropy -- the pools are hash

accumulators -- but crediting it would inflate the kernel's accounting for data

an adversary also has. Default off. Never use this as your only source.

Against 60 days of the live feed and a 230-incident, 9-month archive:

Bias is excellent either way (0.4934 over 227 bits; 0.5 is ideal). There are just almost none of them.

Comparing consecutive durations shares one duration between adjacent bits. For any i.i.d. source:

P(up, up) = P(d1 < d2 < d3) = 1/6, but P(up)P(up) = 1/4

Cov = 1/6 - 1/4 = -1/12, Var = 1/4 => r = -1/3

Simulation over 400 trials of i.i.d. lognormal, exponential and uniform durations reproduces r = -0.335 exactly. So the -0.432 measured on GitHub's data is the method, not GitHub -- only the modest excess over -1/3 is attributable to the corpus (minute-resolution ties).

--disjoint compares non-overlapping pairs and removes it, at half the rate.

Note that --von-neumann corrects bias, not correlation, and does not

help here.

kmod/ ghrandom.c, ghrandom.h -- the kernel module

ghentropyd/ Rust daemon (22 tests)

rc.d/ service script

The module links against kernel internals, so it must be built against source matching the running kernel, not merely a recent checkout:

sysctl -n kern.osreldate # e.g. 1600025

grep '__FreeBSD_version' /usr/src/sys/sys/param.h # must match

make -C kmod # add SYSDIR=/path/to/sys if /usr/src differs

cargo build --release --manifest-path ghentropyd/Cargo.tomlkldload ./kmod/ghrandom.ko

ghentropyd --once --dry-run --state /tmp/state.json # safe: writes nothing

# Replay the history archive instead of waiting ten months.

ghentropyd --backfill 3 --once --output /dev/ghrandom

sysctl kern.random.ghrandomUseful flags: --disjoint, --von-neumann, --interval, --width,

--output (works with /dev/random if the module is not loaded).

Push mode. The module registers with rs_read = NULL and harvests from

d_write instead. Fortuna polls rs_read with count == 8, four times per

100 ms once seeded and 256 times per 100 ms until seeded; a source that

produces one byte every ten days would return 0 to every one of those calls.

Pushing from the write path avoids that and can sleep, which rs_read cannot

(it runs inside an EPOCH_PREEMPT section).

Source enumerator. The module squats on RANDOM_PURE_RNDTEST (17), which

is unclaimed in-tree. A module cannot add an enumerator: healthtest[] and

hc_destination[] are sized [ENTROPYSOURCE] and the bounds checks are

KASSERTs, compiled out without INVARIANTS. rs_ident is a free string, so

kern.random.random_sources still reads GitHub Downtime while

kern.random.harvest.mask_symbolic reads PURE_RNDTEST.

The feed is mutable. 49 of 50 incidents in the live feed had updated_at

move more than an hour after resolved_at, one 7.1 days later. The daemon

dedups by incident id and warns if a re-observed duration ever changes rather

than re-harvesting it.

Backfill shape. history.json?page=N carries no timestamps, only incident

codes; api/v2/incidents/<code>.json has the millisecond record but wraps it

under an "incident" key (the bare /incidents/<code>.json path does not).

ivy.c is not the template to copy. It declares its sysctl_ctx_list as a

stack local in the modevent handler and never frees it, leaving an OID pointing

into an unloaded module. This uses a static SYSCTL_NODE, as rdseed.c does.

Module loads, registers, and unloads cleanly over 20 cycles with no M_ENTROPY

leak; unloading while the device is held open does not panic; the

claim_entropy gate provably stops crediting. Daemon: 22 tests, clippy and

cargo fmt clean; rc.d script passes ShellCheck.

Statistical batteries were run in Python; ent and dieharder are not

installed here. Note that any battery run on the conditioned output measures

SHA-256, not GitHub -- the source produces far too few raw bits to test.