Repository navigation
GPS: optional anti-spoofing guard for position and time (GPS_GUARD) - #4
Conversation
A spoofed GPS signal can move a fixed node anywhere and set its clock to any time; the clock only moves forward, so a time pushed into the future breaks timestamps and replay protection until fixed by hand. With GPS_GUARD, MicroNMEALocationProvider asks a GpsGuard before a fix is stored as the last fix (satellites, HDOP, optional region box, fence around the configured position, jump speed, N stable fixes) and before GPS time is written to the clock (trusted fix current, not before the build date, small steps once the clock is valid, drift-sized after the first sync). Without a trusted fix no live position is reported. CLI in CommonGpsPrefs: 'gps guard', 'gps guard accept', 'gps fence', 'gps ref'; CommonCLI keeps the reference on set lat/lon and gps setloc, resets the time anchor on manual clock sets and adds 'time force'. Ported from meshcore-dev#3736 onto the GpsDriver/LocationProvider refactor.
| uint32_t now = getRTCClock()->getCurrentTime(); | ||
| DateTime dt = DateTime(now); | ||
| sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); | ||
| } else if (memcmp(command, "time force ", 11) == 0) { // set time, allowing it to go backwards |
There was a problem hiding this comment.
should be on a separate PR, won't accept this in this context
There was a problem hiding this comment.
Removed time force from this PR (eea64ff), it will go to a separate PR against dev. The guard does not depend on it.
Also added a live test to the description: the node got a spoofed position about 12,000 km away for several minutes, the region check rejected every fix and GPS time was not written to the clock.
There was a problem hiding this comment.
Correction to my reply above: instead of a new PR for time force, I added the use case to meshcore-dev#1349, which already allows setting the clock backwards.
|
|
||
| // GPS time is only written to the clock while a trusted fix is current, it is not | ||
| // older than this firmware, and (once the clock is valid) it only nudges the clock. | ||
| bool acceptTime(uint32_t gps_time, uint32_t clock_time) { |
There was a problem hiding this comment.
if the node has a totally bogus time at boot (and I had several nodes in that situation in the past, like dates in 2080), the gps can not correct their time if the guard is active ...
There was a problem hiding this comment.
Good point, fixed in fbd20ac: a clock outside the plausible window (build date to +10 years) is now treated like an unset one, so the first trusted fix corrects it. A clock inside the window but far off still goes through the alert and gps guard accept.
| return true; | ||
| } | ||
| #ifdef GPS_GUARD | ||
| if (memcmp(command, "gps guard", 9) == 0 || memcmp(command, "gps fence", 9) == 0 || |
There was a problem hiding this comment.
you don't seem to save the values of that commands ?
I say that because I see there is no modification in CommonCLI.h where markDirty command should be called to trigger saving of the values (for the companion, values are saved in NodePrefs.h)
There was a problem hiding this comment.
Right, the fence was not saved. Fixed in d72f67d: gps fence <m> now goes through setGuardFence() → markDirty(), stored as fence in the GPS prefs (only with GPS_GUARD, -1 = build default), and applied at boot. Tested: gps fence 500, reboot, gps fence → 500. The other guard commands are status or one-shot actions (gps guard, gps guard accept, gps ref), nothing to save there.
There was a problem hiding this comment.
good ...
was thinking
could the whole mechanism be enabled/disabled (with state saved) ?
There was a problem hiding this comment.
Done in 8c7e1a8: gps guard on / gps guard off, saved in the GPS prefs (guard, default on). While off, every fix and GPS time passes and gps guard shows off; turning it back on starts clean (new stable fixes needed, no old alert). Tested: guard off, reboot → off, then on again.
|
Thanks for the PR |
|
Thanks! Saving is in d72f67d: |
|
merged ! |
|
Thank you for the quick and careful review! The time window, saved prefs and on/off switch made the guard much better. Great working with you on this, and happy to help test the refactor before it goes to |
@fdlamotte port of meshcore-dev#3736 onto
gps_refactor, as discussed in meshcore-dev#3753. The guard now sits in the location provider: it decides what becomes_lastFixand whether GPS time may set the clock. The CLI is inCommonGpsPrefs.Problem
A spoofed or jammed GPS signal can move a fixed node anywhere (position in adverts and telemetry) and set its clock to any time. The clock only moves forward, so a time pushed into the future breaks message timestamps, advert replay protection and logins until someone fixes it by hand.
Change
New header-only
GpsGuard(src/helpers/sensors/GpsGuard.h), compiled only withGPS_GUARD.Fixes.
MicroNMEALocationProvider::saveFix()asks the guard before storing a fix. A fix becomes_lastFixonly if it passes these checks:GPS_GUARD_MIN_SATSsatellites and HDOP ≤GPS_GUARD_MAX_HDOP10/10;GPS_GUARD_LAT_MIN/MAX,GPS_GUARD_LON_MIN/MAX);gps fence, default 1000 m) of the node's configuredlat/lon, if they are set;GPS_GUARD_MAX_SPEED_KMHfrom the last trusted fix;GPS_GUARD_STABLE_FIXESconsecutive consistent fixes, one per GPS second (GGA and RMC of the same second count once).With the guard, the provider no longer falls back to the live parser position before the first stored fix.
hasValidFix()means a trusted fix, soEnvironmentSensorManagerand telemetry never see an unverified position.Time.
updateTime()asks the guard beforesetCurrentTime(). GPS time is accepted only under these conditions:GPS_GUARD_TIME_MAX_STEP(600 s) for the first sync;GPS_GUARD_TIME_DRIFT_BASE+GPS_GUARD_TIME_DRIFT_PPM), so the clock cannot be walked away in small steps.Setting the clock by hand (
time,clock sync) resets that anchor. A clock outside the window (e.g. a bogus date in 2080 at boot) is corrected by the first trusted fix.If a trusted fix keeps disagreeing with the clock by the same offset for
GPS_GUARD_ALERT_MINminutes, the guard raises an alert. Either the clock is wrong or the spoofing is sustained. The operator checks and confirms withgps guard accept. While the alert is active, the offset in hours is also reported in telemetry on the next free channel.CLI (in
CommonGpsPrefs, so repeater, room server, sensor and companion):gps guard: status, e.g.> ok 7sat time ok +1s,> rej fence 12.3km time rej nofix, or... ALERT gps-clk +3600s 75m.gps guard on/gps guard off: turn the whole guard on or off, saved in the GPS prefs (guard, default on). While off, every fix and GPS time is accepted andgps guardshowsoff.gps guard accept: apply the alerted GPS time.gps fence [metres]: fence radius,0= off. Saved in the GPS prefs (fence, only withGPS_GUARD); unset means the build defaultGPS_GUARD_FENCE_M.gps ref: the reference position.CommonCLI'sGPSPrefsprovidesnode_lat/node_lon, kept in sync onset lat,set lonandgps setloc. The companion has no reference, so its fence is inactive.Other changes:
LocationProvidergetsgetGuard(),getClockOffset()andapplyTimeNow(), which are no-ops by default.RakLocationProviderdelegates them to its NMEA provider on the serial path. The RAK12500 I2C path does not use the guard.docs/cli_commands.md.Without
GPS_GUARDthe behaviour is unchanged.Testing
GPS_GUARD(and a region box):ProMicro_sensor,ProMicro_repeater,ProMicro_companion_radio_ble,heltec_tracker_v2_sensor,RAK_4631_sensor. Builds without it:ProMicro_repeater,RAK_4631_sensor.GPS_GUARD, node location set viaset lat/set lon, real fix about 0.3 km from it:gps guard->no fix 0sat time -;ok 6sat time ok +3s,gps refshows the node location,gps fence->1000;gps fence 100: every fix is dropped (GPS fix rejected (fence)in the debug log),gps guard->rej fence 0.3km time ok +3s;gps fence 1000again:settle 2/5, thenok 7sat.GPS fix rejected (region), some(hdop)when HDOP went up), one log line per GPS second.gps guard->rej region time rej nofix: the position stayed at the last trusted fix and GPS time was not written to the clock. This continued across a reboot.gps guardno longer shows an oldtime okwhile there is no current trusted fix; a clock outside the plausible window is corrected by GPS.gps fence 500, reboot,gps fence->500(then set back to 1000).gps off,gps guard off, reboot:gps guard->off;gps guard on->no fix time -, thensettle 2/5aftergps on.gps guard accept), RAK12500 over I2C.