@@ -196,13 +196,25 @@ Showable {
196196 // for a few minutes. When we wake up, we want to quickly be correct.
197197 interval: 500
198198
199− property var delayTarget;
199+ property Date delayTarget;
200200 property int delayMinutes;
201201
202− function forceDelay(delay /* in minutes */) {
203− var when = new Date().getTime() + delay * 60000;
204− greeterSettings.lockedOutUntil = when;
205− // code below that watches GSettings will pick up the change
202+ function forceDelay() {
203+ // Store the beginning time for a lockout in GSettings, so that
204+ // we still lock the user out if they reboot. And we store
205+ // starting time rather than end-time or how-long because:
206+ // - If storing end-time and on boot we have a problem with NTP,
207+ // we might get locked out for a lot longer than we thought.
208+ // - If storing how-long, and user turns their phone off for an
209+ // hour rather than wait, they wouldn't expect to still be locked
210+ // out.
211+ // - A malicious actor could manipulate either of the above
212+ // settings to keep the user out longer. But by storing
213+ // start-time, we never make the user wait longer than the full
214+ // lock out time.
215+ // The code below that watches GSettings will pick up the change
216+ // for us.
217+ greeterSettings.lockedOutTime = new Date().getTime();
206218 }
207219
208220 onTriggered: {
@@ -212,17 +224,27 @@ Showable {
212224 start(); // go again
213225 } else {
214226 delayMinutes = 0;
215−
216− // clean up, to avoid accidentally locking user out if phone
217− // gets time wrong for whatever reason in the future.
218− greeterSettings.lockedOutUntil = 0;
219227 }
220228 }
221229
222230 function checkForForcedDelay() {
223− delayTarget = new Date();
224− delayTarget.setTime(greeterSettings.lockedOutUntil);
225− triggered();
231+ var now = new Date();
232+ delayTarget.setTime(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
233+
234+ // If tooEarly is true, something went very wrong. Bug or NTP
235+ // misconfiguration maybe?
236+ var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
237+ var tooLate = now > delayTarget;
238+
239+ // Compare stored time to system time. If a malicious actor is
240+ // able to manipulate time to avoid our lockout, they already have
241+ // enough access to cause damage. So we choose to trust this check.
242+ if (tooEarly || tooLate) {
243+ stop();
244+ delayMinutes = 0;
245+ } else {
246+ triggered();
247+ }
226248 }
227249
228250 Component.onCompleted: checkForForcedDelay()
@@ -398,7 +420,7 @@ Showable {
398420 if (failedLoginsDelayAttempts > 0
399421 && AccountsService.failedLogins > 0
400422 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
401− forcedDelayTimer.forceDelay(failedLoginsDelayMinutes);
423+ forcedDelayTimer.forceDelay();
402424 }
403425
404426 loader.item.notifyAuthenticationFailed();