Merge lp:~unity-team/unity8/lockout-time-travel into lp:unity8

Proposed by Michael Terry
Status: Merged
Approved by: Michael Zanetti
Approved revision: 2085
Merged at revision: 2116
Proposed branch: lp:~unity-team/unity8/lockout-time-travel
Merge into: lp:unity8
Diff against target: 49 lines (+20/-3)
2 files modified
qml/Greeter/Greeter.qml (+6/-3)
tests/qmltests/Greeter/tst_Greeter.qml (+14/-0)
To merge this branch: bzr merge lp:~unity-team/unity8/lockout-time-travel
Reviewer Review Type Date Requested Status
Michael Zanetti (community) Approve
PS Jenkins bot (community) continuous-integration Needs Fixing
Review via email: mp+280206@code.launchpad.net

Commit message

Guard against a couple odd timing scenarios for the too-many-failed-login-attempts lockout screen.

Description of the change

The issue was reported here: https://lists.launchpad.net/ubuntu-phone/msg17217.html

To reproduce this, start with a phone you don't care about (so you can wipe it afterwards to get it working again):

- Set your phone time to 'manual' instead of 'automatic' in System Settings
- Run 'gsettings set com.canonical.Unity8.Greeter locked-out-time 1450125324000' to simulate having gotten locked out in the past.
- Run 'sudo hwclock --set --date @0' to simulate resetting your RTC clock.
- adb reboot
- Now you're locked out for years and years.

Here's why this happened. The old code (briefly and with pseudocode):

===
var now = new Date();
delayTarget = now;
delayTarget.setTime(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);

// If tooEarly is true, something went very wrong. Bug or NTP
// misconfiguration maybe?
var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
var tooLate = now > delayTarget;

if (!tooEarly && !tooLate)
    startLockout();
===

The big mistake is that we re-use the now pointer with "delayTarget = now". And this was my mistake -- this is my code. :( In Javascript, that's just a pointer assignment rather than a value/copy assignment.

So that bug means that 'now' and 'delayTarget' are equal. So tooEarly and tooLate will always be false. Which means when we start up, we always start a lockout. It's just that usually when we tick down during the lockout, we notice that the current time is later than delayTarget and stop the lockout.

But in this case, time is years and years before delayTarget. So we keep the lockout going.

There are other bugs in this code that I'm fixing with this branch:

- We shouldn't even run this code if greeterSettings.lockedOutTime is 0 (never set)
- tooLate should probably be "now >= delayTarget"

To post a comment you must log in.
Revision history for this message
Michael Zanetti (mzanetti) wrote :

+ // - And we trust this check because if a malicious actor is able
+ // to manipulate time to avoid our lockout, there's not much we
+ // can do to stop them.

Hmm... not so sure about this. AFAIU, one could fake ntp replies with relatively low efforts and with that escape the time limit. Brute forcing a 4 digit pin without the time limit is not too hard. Even if the risk of someone doing this is quite low, it might just be enough to get us bad press. I would feel better if someone from security has seen this.

review: Needs Information
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
2083. By Michael Terry

Condense code

2084. By Michael Terry

Add some more tests

Revision history for this message
Michael Terry (mterry) wrote :

@mzanetti, so there is one cheap thing we could do to mitigate that: don't change the lockout timer if the system time changes (and this MP introduces that logic, so I could just strip that out). That way, the attacker would have to reboot between attacks.

But, even with that logic in there, the attacker would have to turn networking on and off to trigger ntpdate checks (which happens in an ifupdown.d script). Which is a sight faster than rebooting or even 5 minute intervals, granted.

But I have no idea how to not trust system time. We could store "seconds left for lock out" but then we would still have locked you out if you turned your phone off for an hour. To cater to that case (turning off your phone while locked out) -- which doesn't seem SUPER unreasonable if can't use your phone for 5 minutes -- I think we have to trust system time somewhat.

Given that, maybe just the cheap fix above is a decent compromise. But will ask jdstrand his opinion.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:2084
http://jenkins.qa.ubuntu.com/job/unity8-ci/6942/
Executed test runs:
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-vivid-touch/5777
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-xenial-touch/357/console
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/unity-phablet-qmluitests-vivid/1653
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/unity8-qmluitest-xenial-amd64/356
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-vivid-amd64-ci/1548
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-vivid-i386-ci/1548
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-xenial-amd64-ci/355
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-xenial-i386-ci/354
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-vivid-touch/4472
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/5790
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/5790/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26045
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-xenial-touch/132/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-xenial-armhf/356
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-xenial-armhf/356/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26046

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/unity8-ci/6942/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote :

Talked to jdstrand and tyhicks. End result is that we'll avoid relying on ntp by not caring about the reboot or power-off cases. Which admittedly are relatively corner cases anyway.

So I'll update this branch to not add the "watch clock changes during lockout" feature. But I'll propose the larger fix of not relying on ntp as a separate branch building on this. It's a separate change and I don't want to slow down this bug fix, since the bug is quite bad.

2085. By Michael Terry

Drop check-clock-every-tick logic, we don't need it

Revision history for this message
Michael Terry (mterry) wrote :

OK updated to simplified version. Will work on separate MP to drop wall clock watching.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:2085
http://jenkins.qa.ubuntu.com/job/unity8-ci/6960/
Executed test runs:
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-vivid-touch/5804
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-xenial-touch/375/console
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/unity-phablet-qmluitests-vivid/1671
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/unity8-qmluitest-xenial-amd64/374
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-vivid-amd64-ci/1566
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-vivid-i386-ci/1566
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-xenial-amd64-ci/373
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-xenial-i386-ci/372
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-vivid-touch/4490
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/5817
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/5817/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26094
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-xenial-touch/143/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-xenial-armhf/374
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-xenial-armhf/374/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26093

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/unity8-ci/6960/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote :

(I'm also trying to get Design to comment on that proposed security change... didn't want to slow this fix down on that either.)

Revision history for this message
Michael Zanetti (mzanetti) wrote :

 * Did you perform an exploratory manual test run of the code change and any related functionality?

yes

 * Did CI run pass? If not, please explain why.

unrelated failures that are being addressed in another branch.

 * Did you make sure that the branch does not contain spurious tags?

yes

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'qml/Greeter/Greeter.qml'
2--- qml/Greeter/Greeter.qml 2015-11-04 14:57:33 +0000
3+++ qml/Greeter/Greeter.qml 2015-12-15 19:09:13 +0000
4@@ -226,14 +226,17 @@
5 }
6
7 function checkForForcedDelay() {
8+ if (greeterSettings.lockedOutTime === 0) {
9+ return;
10+ }
11+
12 var now = new Date();
13- delayTarget = now;
14- delayTarget.setTime(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
15+ delayTarget = new Date(greeterSettings.lockedOutTime + failedLoginsDelayMinutes * 60000);
16
17 // If tooEarly is true, something went very wrong. Bug or NTP
18 // misconfiguration maybe?
19 var tooEarly = now.getTime() < greeterSettings.lockedOutTime;
20- var tooLate = now > delayTarget;
21+ var tooLate = now >= delayTarget;
22
23 // Compare stored time to system time. If a malicious actor is
24 // able to manipulate time to avoid our lockout, they already have
25
26=== modified file 'tests/qmltests/Greeter/tst_Greeter.qml'
27--- tests/qmltests/Greeter/tst_Greeter.qml 2015-09-14 07:47:24 +0000
28+++ tests/qmltests/Greeter/tst_Greeter.qml 2015-12-15 19:09:13 +0000
29@@ -458,6 +458,20 @@
30 compare(view.delayMinutes, greeter.failedLoginsDelayMinutes);
31 }
32
33+ function test_forcedDelayOnConstructionIgnoredIfInFuture() {
34+ greeterSettings.lockedOutTime = new Date().getTime() + greeter.failedLoginsDelayMinutes * 60000 + 1;
35+ resetLoader();
36+ view = findChild(greeter, "testView");
37+ compare(view.delayMinutes, 0);
38+ }
39+
40+ function test_forcedDelayOnConstructionIgnoredIfInPast() {
41+ greeterSettings.lockedOutTime = new Date().getTime() - greeter.failedLoginsDelayMinutes * 60000 - 1;
42+ resetLoader();
43+ view = findChild(greeter, "testView");
44+ compare(view.delayMinutes, 0);
45+ }
46+
47 function test_forcedDelayRoundTrip() {
48 greeter.failedLoginsDelayAttempts = 1;
49 greeter.failedLoginsDelayMinutes = 0.001; // make delay very short

Subscribers

People subscribed via source and target branches