Check whether system clock is reasonable at startup.

This commit is contained in:
akwizgran
2021-06-23 16:32:32 +01:00
committed by Torsten Grote
parent 80749fec09
commit 802f64e309
5 changed files with 75 additions and 10 deletions

View File

@@ -22,6 +22,7 @@ public interface LifecycleManager {
*/
enum StartResult {
ALREADY_RUNNING,
CLOCK_ERROR,
DB_ERROR,
DATA_TOO_OLD_ERROR,
DATA_TOO_NEW_ERROR,
@@ -29,6 +30,26 @@ public interface LifecycleManager {
SUCCESS
}
/**
* The minimum reasonable value for the system clock, in milliseconds
* since the Unix epoch. {@link #startServices(SecretKey)} will return
* {@link StartResult#CLOCK_ERROR} if the system clock reports an earlier
* time.
* <p/>
* 1 Jan 2021, 00:00:00 UTC
*/
long MIN_REASONABLE_TIME_MS = 1_609_459_200_000L;
/**
* The maximum reasonable value for the system clock, in milliseconds
* since the Unix epoch. {@link #startServices(SecretKey)} will return
* {@link StartResult#CLOCK_ERROR} if the system clock reports a later
* time.
* <p/>
* 1 Jan 2121, 00:00:00 UTC
*/
long MAX_REASONABLE_TIME_MS = 4_765_132_800_000L;
/**
* The state the lifecycle can be in.
* Returned by {@link #getLifecycleState()}

View File

@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -34,6 +35,7 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
@@ -52,6 +54,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private final DatabaseComponent db;
private final EventBus eventBus;
private final Clock clock;
private final List<Service> services;
private final List<OpenDatabaseHook> openDatabaseHooks;
private final List<ExecutorService> executors;
@@ -63,9 +66,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private volatile LifecycleState state = STARTING;
@Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus) {
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
Clock clock) {
this.db = db;
this.eventBus = eventBus;
this.clock = clock;
services = new CopyOnWriteArrayList<>();
openDatabaseHooks = new CopyOnWriteArrayList<>();
executors = new CopyOnWriteArrayList<>();
@@ -99,6 +104,13 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
LOG.info("Already starting or stopping");
return ALREADY_RUNNING;
}
long now = clock.currentTimeMillis();
if (now < MIN_REASONABLE_TIME_MS || now > MAX_REASONABLE_TIME_MS) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("System clock is unreasonable: " + now);
}
return CLOCK_ERROR;
}
try {
LOG.info("Opening database");
long start = now();

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.junit.Before;
@@ -14,6 +15,9 @@ import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.MAX_REASONABLE_TIME_MS;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.MIN_REASONABLE_TIME_MS;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertEquals;
@@ -22,6 +26,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final EventBus eventBus = context.mock(EventBus.class);
private final Clock clock = context.mock(Clock.class);
private final SecretKey dbKey = getSecretKey();
@@ -29,16 +34,19 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
@Before
public void setUp() {
lifecycleManager = new LifecycleManagerImpl(db, eventBus);
lifecycleManager = new LifecycleManagerImpl(db, eventBus, clock);
}
@Test
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
long now = System.currentTimeMillis();
Transaction txn = new Transaction(null, false);
AtomicBoolean called = new AtomicBoolean(false);
OpenDatabaseHook hook = transaction -> called.set(true);
context.checking(new DbExpectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).open(dbKey, lifecycleManager);
will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn));
@@ -51,4 +59,26 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
assertTrue(called.get());
}
@Test
public void testStartupFailsIfClockIsUnreasonablyBehind() {
context.checking(new DbExpectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(MIN_REASONABLE_TIME_MS - 1));
}});
assertEquals(CLOCK_ERROR, lifecycleManager.startServices(dbKey));
}
@Test
public void testStartupFailsIfClockIsUnreasonablyAhead() {
context.checking(new DbExpectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(MAX_REASONABLE_TIME_MS + 1));
}});
assertEquals(CLOCK_ERROR, lifecycleManager.startServices(dbKey));
}
}

View File

@@ -51,26 +51,27 @@ public class StartupFailureActivity extends BaseActivity implements
}
// show proper error message
String errorMsg;
int errorRes;
switch (result) {
case CLOCK_ERROR:
errorRes = R.string.startup_failed_clock_error;
break;
case DATA_TOO_OLD_ERROR:
errorMsg =
getString(R.string.startup_failed_data_too_old_error);
errorRes = R.string.startup_failed_data_too_old_error;
break;
case DATA_TOO_NEW_ERROR:
errorMsg =
getString(R.string.startup_failed_data_too_new_error);
errorRes = R.string.startup_failed_data_too_new_error;
break;
case DB_ERROR:
errorMsg = getString(R.string.startup_failed_db_error);
errorRes = R.string.startup_failed_db_error;
break;
case SERVICE_ERROR:
errorMsg = getString(R.string.startup_failed_service_error);
errorRes = R.string.startup_failed_service_error;
break;
default:
throw new IllegalArgumentException();
}
showInitialFragment(ErrorFragment.newInstance(errorMsg));
showInitialFragment(ErrorFragment.newInstance(getString(errorRes)));
}
@Override

View File

@@ -49,6 +49,7 @@
<string name="startup_failed_notification_title">Briar could not start</string>
<string name="startup_failed_notification_text">Tap for more information.</string>
<string name="startup_failed_activity_title">Briar Startup Failure</string>
<string name="startup_failed_clock_error">Briar was unable to start because your device\'s clock is wrong. Please set your device\'s clock to the right time and try again.</string>
<string name="startup_failed_db_error">For some reason, your Briar database is corrupted beyond repair. Your account, your data and all your contacts are lost. Unfortunately, you need to reinstall Briar or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
<string name="startup_failed_data_too_old_error">Your account was created with an old version of this app and cannot be opened with this version. You must either reinstall the old version or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
<string name="startup_failed_data_too_new_error">This version of the app is too old. Please upgrade to the latest version and try again.</string>