DominoTree/ghrandom

GitHub's downtime as a FreeBSD entropy source.

Rust

0

1 commits

updated Sep 22, 2026

See the code

See what people are saying (1)

README

ghrandom

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

This is not a real entropy source

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.

Measured behaviour

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

rate256-bit seedlag-1 autocorrruns test
overlapping pairs (default)0.83 bits/day10.2 months-0.432FAIL (z=6.46)
--disjoint0.42 bits/day20.2 months+0.148pass (z=1.67)
--von-neumann0.27 bits/day2.6 yearsunchanged--

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

The overlapping-pair correlation is structural

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.

Layout

kmod/        ghrandom.c, ghrandom.h  -- the kernel module
ghentropyd/  Rust daemon (22 tests)
rc.d/        service script

Build

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.toml

Run

kldload ./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.ghrandom

Useful flags: --disjoint, --von-neumann, --interval, --width, --output (works with /dev/random if the module is not loaded).

Design notes

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.

Verified

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.

Contributors

DominoTree

1 commits

DominoTree/ghrandom

GitHub's downtime as a FreeBSD entropy source.

Rust

0

1 commits

updated Sep 22, 2026

See the code

See what people are saying (1)

README

ghrandom

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

This is not a real entropy source

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.

Measured behaviour

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

rate256-bit seedlag-1 autocorrruns test
overlapping pairs (default)0.83 bits/day10.2 months-0.432FAIL (z=6.46)
--disjoint0.42 bits/day20.2 months+0.148pass (z=1.67)
--von-neumann0.27 bits/day2.6 yearsunchanged--

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

The overlapping-pair correlation is structural

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.

Layout

kmod/        ghrandom.c, ghrandom.h  -- the kernel module
ghentropyd/  Rust daemon (22 tests)
rc.d/        service script

Build

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.toml

Run

kldload ./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.ghrandom

Useful flags: --disjoint, --von-neumann, --interval, --width, --output (works with /dev/random if the module is not loaded).

Design notes

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.

Verified

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.

Contributors

DominoTree

1 commits

Languages

Rust

81.4%

C

13.7%

Shell

3.4%

Makefile

1.5%