Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fix/bypass-lock-prevention] Bypass Passcode Prevention #1324

Closed
wants to merge 6 commits into from

Conversation

hosy
Copy link
Collaborator

@hosy hosy commented Feb 20, 2024

Description

Prevents bypass the passcode by changing the device system time.

Related Issue

https://github.com/owncloud/security-tracker/issues/413

Motivation and Context

How Has This Been Tested?

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • Added an issue with details about all relevant changes in the iOS documentation repository.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • Added changelog files for the fixed issues in folder changelog/unreleased

Copy link
Contributor

@felix-schwarz felix-schwarz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the code comments, please also take a look at:

  • variable naming: f.ex. lockSetTime, lockDuration and lockedUntilDate leave open what data is being used in each (clock time or system up time) , which makes the code harder to read. Using something like lockSetSystemUptime, systemUpTimeLockDuration (or just System instead of SystemUptime?) would clarify what is tracking what.
  • I found no mechanism resetting the countdown in case of a detected lock bypass

ownCloudAppShared/AppLock/AppLockManager.swift Outdated Show resolved Hide resolved
ownCloudAppShared/AppLock/AppLockManager.swift Outdated Show resolved Hide resolved
let lockDuration = currentTime - lockSetTime
let timeRemaining = lockedUntilDate.timeIntervalSinceNow

if lockDuration < timeRemaining {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • lockDuration computes the time since the lock has been set
  • timeRemaining computes the time until the lock expires

Comparing the two will not be able to tell if there was an attempt to bypass the lock. Image a 10 second delay, where 2 seconds have passed by and 8 seconds are remaining - without any bypass attempt. These numbers would fit the condition for a lock-bypass above even though it hasn't been bypassed.

To determine a bypass, it's necessary to also keep track of the date/time the lock has been put in place - and then use that date to compute the time passed since locking, to compare it against the time passed in system uptime.
That comparison should also leave a tiny bit of wiggle-room (f.ex. ~ 0.05 seconds), because the systemUptime and the date are not taken at the exact same moment and therefore will regularly differ slightly.

…user changes the system time to bypass the passcode lock. ProcessInfo.processInfo.systemUptime does not work, because system does not restart after setting the system date/time. lockUntilDate is also stored in user defaults than lockedUntilDate before.
@hosy
Copy link
Collaborator Author

hosy commented Mar 15, 2024

@felix-schwarz I decided to go another approach:

Converted lockUntilDate to lockDelay. This is a better prevention if user changes the system time to bypass the passcode lock. ProcessInfo.processInfo.systemUptime does not work, because system does not restart after setting the system date/time. lockUntilDate is also stored in user defaults than lockedUntilDate before.

Copy link
Contributor

@felix-schwarz felix-schwarz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that, to reliably detect the change of system time to circumvent the lock, these need to be tracked:

  • system uptime at the start of the lock (via ProcessInfo.processInfo.systemUptime)
  • the start date/time and duration of the lock countdown
  • the last known remaining time

If the system date is set to a future date to circumvent the lock, the app can catch this because the system uptime has not moved forward by the same amount of time.

If the device is rebooted, the system uptime will be lower than the uptime at the start of the lock.

In both cases, the app can use the last known remaining time to restart the lock.

Counting down the timeout time with a timer is not a viable solution IMO for the reasons outlined below.

let seconds = Int(lockDelay) % 60

if lockDelay > 0 {
self.lockDelay = (lockDelay - 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Counting down the lock delay with a timer has several issues:

  • the app needs to stay in the foreground for the entire duration of the countdown
  • additional instances of AppLockManager running in different processes (possibly even scenes) also reduce the lock delay every second, so that if the lock screen is shown in the app, in the Share Extension and the File Provider UI at the same time, it would run a 3x speed

@jesmrec
Copy link
Contributor

jesmrec commented Apr 8, 2024

Shouldn't this PR point to milestone/12.2 instead of master?

@hosy hosy changed the base branch from master to milestone/12.2 April 9, 2024 09:18
@hosy hosy requested a review from felix-schwarz April 16, 2024 08:43
@hosy
Copy link
Collaborator Author

hosy commented Apr 16, 2024

@felix-schwarz, I converted the code to use the system's significant time change notification to get informed about time changes while the app is locked. If the device is locked for a specific delay, the counter will start from the beginning if a time change occurs. Additionally, if the app was terminated and there is a lock delay, the counter will also reset. This prevents issues if a time change was not delivered.

@@ -153,11 +159,13 @@ public class AppLockManager: NSObject {
userDefaults = OCAppIdentity.shared.userDefaults!

super.init()
significantTimeChangeOccurred()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By handling every initialization of the AppLockManager as a significant time change and resetting the countdown, users may be hit without trying to bypass the lock timer, f.ex. during regular usage like:

  • invoking the share extension from a share sheet
  • iOS quits the app in the background to free memory for apps launched after it
  • the user closes the app

Adding UIApplication.significantTimeChangeNotification as a signal to the mix of detecting a bypass attempt is a good idea, but if the notification is not delivered to the app and/or app extensions when they aren't running at the time the significant time change occurs, it can't be relied on alone.

Resetting the timer every time the AppLockManager is initialized in a new process closes the gaps that a UIApplication.significantTimeChangeNotification can leave, but also has negative effects on legitimate usage, unfortunately, which I think we should avoid.

Why not use ProcessInfo.processInfo.systemUptime as outlined above?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felix-schwarz please go ahead if by implementing the prevention using ProcessInfo.processInfo.systemUptime if you think it is a better solution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I implemented a solution based on ProcessInfo.processInfo.systemUptime at #1347 . If it passes testing on a real device, I'd suggest to close this PR and proceed with #1347 instead.

@felix-schwarz
Copy link
Contributor

@hosy wrote in a comment above:

@felix-schwarz please go ahead if by implementing the prevention using ProcessInfo.processInfo.systemUptime if you think it is a better solution.

Ok, I implemented a solution based on ProcessInfo.processInfo.systemUptime at #1347 . Testing on a real iPad and changing its clock, time manipulations were reliably detected - even if the app was quit before and launched again after the clock change. I'm therefore closing this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants