@@ -16,6 +16,7 @@
1616
1717import QtQuick 2.3
1818import AccountsService 0.1
19+import GSettings 1.0
1920import Ubuntu.Components 1.1
2021import Ubuntu.SystemImage 0.1
2122import Unity.Launcher 0.1
@@ -181,6 +182,11 @@ Showable {
181182 }
182183 }
183184
185+ GSettings {
186+ id: greeterSettings
187+ schema.id: "com.canonical.Unity8.Greeter"
188+ }
189+
184190 Timer {
185191 id: forcedDelayTimer
186192
@@ -189,14 +195,24 @@ Showable {
189195 // for a few minutes. When we wake up, we want to quickly be correct.
190196 interval: 500
191197
192− property var delayTarget;
193− property int delayMinutes;
194−
195− function forceDelay(delay /* in minutes */) {
196− delayTarget = new Date();
197− delayTarget.setTime(delayTarget.getTime() + delay * 60000);
198− delayMinutes = Math.ceil(delay);
199− start();
198+ property var delayTarget
199+ property int delayMinutes
200+
201+ function forceDelay() {
202+ // Store the beginning time for a lockout in GSettings, so that
203+ // we still lock the user out if they reboot. And we store
204+ // starting time rather than end-time or how-long because:
205+ // - If storing end-time and on boot we have a problem with NTP,
206+ // we might get locked out for a lot longer than we thought.
207+ // - If storing how-long, and user turns their phone off for an
208+ // hour rather than wait, they wouldn't expect to still be locked
209+ // out.
210+ // - A malicious actor could manipulate either of the above
211+ // settings to keep the user out longer. But by storing
212+ // start-time, we never make the user wait longer than the full
213+ // lock out time.
214+ greeterSettings.lockedOutTime = new Date().getTime();
215+ checkForForcedDelay();
200216 }
201217
202218 onTriggered: {
@@ -208,6 +224,29 @@ Showable {
208224 delayMinutes = 0;
209225 }
210226 }
227+
228+ function checkForForcedDelay() {
229+ var now = new Date();
230+ delayTarget = now;
231+ delayTarget.setTime(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
232+
233+ // If tooEarly is true, something went very wrong. Bug or NTP
234+ // misconfiguration maybe?
235+ var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
236+ var tooLate = now > delayTarget;
237+
238+ // Compare stored time to system time. If a malicious actor is
239+ // able to manipulate time to avoid our lockout, they already have
240+ // enough access to cause damage. So we choose to trust this check.
241+ if (tooEarly || tooLate) {
242+ stop();
243+ delayMinutes = 0;
244+ } else {
245+ triggered();
246+ }
247+ }
248+
249+ Component.onCompleted: checkForForcedDelay()
211250 }
212251
213252 // event eater
@@ -380,7 +419,7 @@ Showable {
380419 if (failedLoginsDelayAttempts > 0
381420 && AccountsService.failedLogins > 0
382421 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
383− forcedDelayTimer.forceDelay(failedLoginsDelayMinutes);
422+ forcedDelayTimer.forceDelay();
384423 }
385424
386425 loader.item.notifyAuthenticationFailed();