forked from I2P_Developers/i2p.i2p
Clock: Add sanity checks to detect invalid system clock
This commit is contained in:
157
core/java/src/net/i2p/time/BuildTime.java
Normal file
157
core/java/src/net/i2p/time/BuildTime.java
Normal file
@ -0,0 +1,157 @@
|
||||
package net.i2p.time;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
* Get the build date as set in i2p.jar,
|
||||
* and reasonable min and max values for the current time,
|
||||
* to be used as sanity checks.
|
||||
*
|
||||
* Idea taken from Chrome, which assumes any clock more than
|
||||
* 2 days before or 1 year after the build date is bad.
|
||||
*
|
||||
* Not maintained as a public API, not for use by plugins or applications.
|
||||
*
|
||||
* @since 0.9.25 modded from FileDumpHelper
|
||||
*/
|
||||
public class BuildTime {
|
||||
|
||||
private static final long _buildTime;
|
||||
private static final long _earliestTime;
|
||||
private static final long _latestTime;
|
||||
private static final long YEARS_25 = 25L*365*24*60*60*1000;
|
||||
/** update this periodically */
|
||||
private static final String EARLIEST = "2016-02-19 12:00:00 UTC";
|
||||
|
||||
static {
|
||||
// this is the standard format of build.timestamp as set in the top-level build.xml
|
||||
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z", Locale.US);
|
||||
TimeZone utc = TimeZone.getTimeZone("GMT");
|
||||
fmt.setTimeZone(utc);
|
||||
long min;
|
||||
try {
|
||||
Date date = fmt.parse(EARLIEST);
|
||||
if (date == null)
|
||||
throw new RuntimeException("BuildTime FAIL");
|
||||
min = date.getTime();
|
||||
} catch (ParseException pe) {
|
||||
System.out.println("BuildTime FAIL");
|
||||
pe.printStackTrace();
|
||||
throw new RuntimeException("BuildTime FAIL", pe);
|
||||
}
|
||||
long max = min + YEARS_25;
|
||||
long build = getBuildTime(fmt, "i2p.jar");
|
||||
if (build > max) {
|
||||
System.out.println("Warning: Strange build time, contact packager: " + new Date(build));
|
||||
build = max;
|
||||
} else if (build < min) {
|
||||
if (build > 0)
|
||||
System.out.println("Warning: Strange build time, contact packager: " + new Date(build));
|
||||
build = min;
|
||||
} else {
|
||||
// build time looks reasonable
|
||||
// allow 24h skew on build machine
|
||||
min = build - 24*60*60*1000L;
|
||||
}
|
||||
_earliestTime = min;
|
||||
_latestTime = max;
|
||||
_buildTime = build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the build date for i2p.jar.
|
||||
*
|
||||
* @return the earliest possible time if actual build date is unknown
|
||||
*/
|
||||
public static long getBuildTime() {
|
||||
return _buildTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the earliest it could possibly be right now.
|
||||
* Latest of the build time minus a day, or a hardcoded time.
|
||||
*
|
||||
* @return the time
|
||||
*/
|
||||
public static long getEarliestTime() {
|
||||
return _earliestTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest it could possibly be right now.
|
||||
* Hardcoded.
|
||||
*
|
||||
* @return the time
|
||||
*/
|
||||
public static long getLatestTime() {
|
||||
return _latestTime;
|
||||
}
|
||||
|
||||
private BuildTime() {}
|
||||
|
||||
/**
|
||||
* Won't be available on Android or on any builds not using our build.xml.
|
||||
*
|
||||
* @return 0 if unknown
|
||||
*/
|
||||
private static long getBuildTime(SimpleDateFormat fmt, String jar) {
|
||||
if (SystemVersion.isAndroid())
|
||||
return 0;
|
||||
File f = new File(I2PAppContext.getGlobalContext().getBaseDir(), "lib");
|
||||
f = new File(f, jar);
|
||||
Attributes atts = attributes(f);
|
||||
if (atts == null)
|
||||
return 0;
|
||||
String s = atts.getValue("Build-Date");
|
||||
if (s == null)
|
||||
return 0;
|
||||
try {
|
||||
Date date = fmt.parse(s);
|
||||
if (date != null) {
|
||||
return date.getTime();
|
||||
}
|
||||
} catch (ParseException pe) {}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static Attributes attributes(File f) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = (new URL("jar:file:" + f.getAbsolutePath() + "!/META-INF/MANIFEST.MF")).openStream();
|
||||
Manifest man = new Manifest(in);
|
||||
return man.getMainAttributes();
|
||||
} catch (IOException ioe) {
|
||||
return null;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String[] args) {
|
||||
long date = getEarliestTime();
|
||||
System.out.println("Earliest date: " + new Date(date));
|
||||
date = getBuildTime();
|
||||
System.out.println("Build date: " + new Date(date));
|
||||
date = System.currentTimeMillis();
|
||||
System.out.println("System time: " + new Date(date));
|
||||
date = I2PAppContext.getGlobalContext().clock().now();
|
||||
System.out.println("I2P time: " + new Date(date));
|
||||
date = getLatestTime();
|
||||
System.out.println("Latest date: " + new Date(date));
|
||||
}
|
||||
****/
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
package net.i2p.util;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.time.BuildTime;
|
||||
import net.i2p.time.Timestamper;
|
||||
|
||||
/**
|
||||
@ -19,6 +21,7 @@ import net.i2p.time.Timestamper;
|
||||
*/
|
||||
public class Clock implements Timestamper.UpdateListener {
|
||||
protected final I2PAppContext _context;
|
||||
protected final boolean _isSystemClockBad;
|
||||
protected long _startedOn;
|
||||
protected boolean _statCreated;
|
||||
protected volatile long _offset;
|
||||
@ -28,7 +31,28 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
public Clock(I2PAppContext context) {
|
||||
_context = context;
|
||||
_listeners = new CopyOnWriteArraySet<ClockUpdateListener>();
|
||||
_startedOn = System.currentTimeMillis();
|
||||
long now = System.currentTimeMillis();
|
||||
long min = BuildTime.getEarliestTime();
|
||||
long max = BuildTime.getLatestTime();
|
||||
// If the system clock is obviously bad, set our offset so our time is something "close"
|
||||
// We do not call setOffset() here as it sets _alreadyChanged.
|
||||
// Don't use Log here.
|
||||
if (now < min) {
|
||||
// positive offset
|
||||
_offset = min - now;
|
||||
System.out.println("ERROR: System clock is invalid: " + new Date(now));
|
||||
now = min;
|
||||
_isSystemClockBad = true;
|
||||
} else if (now > max) {
|
||||
// negative offset
|
||||
_offset = max - now;
|
||||
System.out.println("ERROR: System clock is invalid: " + new Date(now));
|
||||
now = max;
|
||||
_isSystemClockBad = true;
|
||||
} else {
|
||||
_isSystemClockBad = false;
|
||||
}
|
||||
_startedOn = now;
|
||||
}
|
||||
|
||||
public static Clock getInstance() {
|
||||
@ -63,6 +87,7 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
/**
|
||||
* Specify how far away from the "correct" time the computer is - a positive
|
||||
* value means that the system time is slow, while a negative value means the system time is fast.
|
||||
*
|
||||
* Warning - overridden in RouterClock
|
||||
*
|
||||
* @param offsetMs the delta from System.currentTimeMillis() (NOT the delta from now())
|
||||
@ -70,7 +95,7 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
public synchronized void setOffset(long offsetMs, boolean force) {
|
||||
long delta = offsetMs - _offset;
|
||||
if (!force) {
|
||||
if ((offsetMs > MAX_OFFSET) || (offsetMs < 0 - MAX_OFFSET)) {
|
||||
if (!_isSystemClockBad && (offsetMs > MAX_OFFSET || offsetMs < 0 - MAX_OFFSET)) {
|
||||
Log log = getLog();
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Maximum offset shift exceeded [" + offsetMs + "], NOT HONORING IT");
|
||||
@ -128,17 +153,27 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
|
||||
|
||||
public void setNow(long realTime) {
|
||||
if (realTime < BuildTime.getEarliestTime() || realTime > BuildTime.getLatestTime()) {
|
||||
Log log = getLog();
|
||||
String msg = "Invalid time received: " + new Date(realTime);
|
||||
if (log.shouldWarn())
|
||||
log.warn(msg, new Exception());
|
||||
else
|
||||
log.logAlways(Log.WARN, msg);
|
||||
return;
|
||||
}
|
||||
long diff = realTime - System.currentTimeMillis();
|
||||
setOffset(diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning - overridden in RouterClock
|
||||
*
|
||||
* @param stratum ignored
|
||||
* @since 0.7.12
|
||||
*/
|
||||
public void setNow(long realTime, int stratum) {
|
||||
long diff = realTime - System.currentTimeMillis();
|
||||
setOffset(diff);
|
||||
setNow(realTime);
|
||||
}
|
||||
|
||||
/**
|
||||
|
14
history.txt
14
history.txt
@ -1,3 +1,17 @@
|
||||
2016-02-19 zzz
|
||||
* Clock: Add sanity checks to detect invalid system clock
|
||||
|
||||
2016-02-18 zzz
|
||||
* Console: Clean up display and form handling
|
||||
for specifying a fixed host name or IP on /confignet
|
||||
* Crypto: Add utilities for loading CRLs from disk;
|
||||
check for revocation when reading in certificates
|
||||
* Transport:
|
||||
- Implement mayDisconnect() for outbound connections also,
|
||||
use when publishing RI directly to floodfill
|
||||
- Run SSU idle disconnect check faster if floodfill or near connection limit
|
||||
* NetDB: Fix bug publishing router info too often
|
||||
|
||||
2016-02-17 zzz
|
||||
* i2psnark: Increase max files per torrent to 2000
|
||||
* I2PTunnel: Improve layout of blacklist radio buttons
|
||||
|
@ -1,10 +1,12 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.time.RouterTimestamper;
|
||||
import net.i2p.time.BuildTime;
|
||||
import net.i2p.time.Timestamper;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
@ -108,7 +110,7 @@ public class RouterClock extends Clock {
|
||||
private synchronized void setOffset(long offsetMs, boolean force, int stratum) {
|
||||
long delta = offsetMs - _offset;
|
||||
if (!force) {
|
||||
if ((offsetMs > MAX_OFFSET) || (offsetMs < 0 - MAX_OFFSET)) {
|
||||
if (!_isSystemClockBad && (offsetMs > MAX_OFFSET || offsetMs < 0 - MAX_OFFSET)) {
|
||||
Log log = getLog();
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Maximum offset shift exceeded [" + offsetMs + "], NOT HONORING IT");
|
||||
@ -218,6 +220,15 @@ public class RouterClock extends Clock {
|
||||
*/
|
||||
@Override
|
||||
public void setNow(long realTime, int stratum) {
|
||||
if (realTime < BuildTime.getEarliestTime() || realTime > BuildTime.getLatestTime()) {
|
||||
Log log = getLog();
|
||||
String msg = "Invalid time received: " + new Date(realTime);
|
||||
if (log.shouldWarn())
|
||||
log.warn(msg, new Exception());
|
||||
else
|
||||
log.logAlways(Log.WARN, msg);
|
||||
return;
|
||||
}
|
||||
long diff = realTime - System.currentTimeMillis();
|
||||
setOffset(diff, stratum);
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
||||
/** deprecated */
|
||||
public final static String ID = "Monotone";
|
||||
public final static String VERSION = CoreVersion.VERSION;
|
||||
public final static long BUILD = 6;
|
||||
public final static long BUILD = 7;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
|
Reference in New Issue
Block a user