1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-14 15:23:35 +00:00

Use monotonic clock for vault timeout (#1175)

* Use monotonic clock for vault timeout

* free memory

* removed vault timeout timers and added crash logging to iOS clock hack
This commit is contained in:
Matt Portune
2020-12-14 15:29:30 -05:00
committed by GitHub
parent 3227daddaf
commit acf2e4360f
11 changed files with 98 additions and 96 deletions

View File

@@ -38,7 +38,6 @@ namespace Bit.Droid
private IAppIdService _appIdService; private IAppIdService _appIdService;
private IStorageService _storageService; private IStorageService _storageService;
private IEventService _eventService; private IEventService _eventService;
private PendingIntent _vaultTimeoutAlarmPendingIntent;
private PendingIntent _clearClipboardPendingIntent; private PendingIntent _clearClipboardPendingIntent;
private PendingIntent _eventUploadPendingIntent; private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions; private AppOptions _appOptions;
@@ -51,9 +50,6 @@ namespace Bit.Droid
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver)); var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent, _eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
PendingIntentFlags.UpdateCurrent); PendingIntentFlags.UpdateCurrent);
var alarmIntent = new Intent(this, typeof(LockAlarmReceiver));
_vaultTimeoutAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
PendingIntentFlags.UpdateCurrent);
var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver)); var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver));
_clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent, _clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent,
PendingIntentFlags.UpdateCurrent); PendingIntentFlags.UpdateCurrent);
@@ -91,20 +87,7 @@ namespace Bit.Droid
_broadcasterService.Subscribe(_activityKey, (message) => _broadcasterService.Subscribe(_activityKey, (message) =>
{ {
if (message.Command == "scheduleVaultTimeoutTimer") if (message.Command == "startEventTimer")
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
var vaultTimeoutMinutes = (int)message.Data;
var vaultTimeoutMs = vaultTimeoutMinutes * 60000;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + vaultTimeoutMs + 10;
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _vaultTimeoutAlarmPendingIntent);
}
else if (message.Command == "cancelVaultTimeoutTimer")
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Cancel(_vaultTimeoutAlarmPendingIntent);
}
else if (message.Command == "startEventTimer")
{ {
StartEventAlarm(); StartEventAlarm();
} }

View File

@@ -752,6 +752,15 @@ namespace Bit.Droid.Services
return false; return false;
} }
public long GetActiveTime()
{
// Returns milliseconds since the system was booted, and includes deep sleep. This clock is guaranteed to
// be monotonic, and continues to tick even when the CPU is in power saving modes, so is the recommend
// basis for general purpose interval timing.
// ref: https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime()
return SystemClock.ElapsedRealtime() / 1000;
}
private bool DeleteDir(Java.IO.File dir) private bool DeleteDir(Java.IO.File dir)
{ {
if (dir != null && dir.IsDirectory) if (dir != null && dir.IsDirectory)

View File

@@ -43,5 +43,6 @@ namespace Bit.App.Abstractions
void OpenAccessibilityOverlayPermissionSettings(); void OpenAccessibilityOverlayPermissionSettings();
void OpenAutofillSettings(); void OpenAutofillSettings();
bool UsingDarkTheme(); bool UsingDarkTheme();
long GetActiveTime();
} }
} }

View File

@@ -185,7 +185,7 @@ namespace Bit.App
var isLocked = await _vaultTimeoutService.IsLockedAsync(); var isLocked = await _vaultTimeoutService.IsLockedAsync();
if (!isLocked) if (!isLocked)
{ {
await _storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow); await _storageService.SaveAsync(Constants.LastActiveKey, _deviceActionService.GetActiveTime());
} }
SetTabsPageFromAutofill(isLocked); SetTabsPageFromAutofill(isLocked);
await SleptAsync(); await SleptAsync();
@@ -210,7 +210,7 @@ namespace Bit.App
private async void ResumedAsync() private async void ResumedAsync()
{ {
_messagingService.Send("cancelVaultTimeoutTimer"); await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("startEventTimer"); _messagingService.Send("startEventTimer");
await ClearCacheIfNeededAsync(); await ClearCacheIfNeededAsync();
Prime(); Prime();
@@ -302,11 +302,7 @@ namespace Bit.App
vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey); vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
} }
vaultTimeout = vaultTimeout.GetValueOrDefault(-1); vaultTimeout = vaultTimeout.GetValueOrDefault(-1);
if (vaultTimeout > 0) if (vaultTimeout == 0)
{
_messagingService.Send("scheduleVaultTimeoutTimer", vaultTimeout.Value);
}
else if (vaultTimeout == 0)
{ {
var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey); var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey);
if (action == "logOut") if (action == "logOut")

View File

@@ -3,6 +3,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration; using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific; using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
@@ -12,6 +13,7 @@ namespace Bit.App.Pages
public class BaseContentPage : ContentPage public class BaseContentPage : ContentPage
{ {
private IStorageService _storageService; private IStorageService _storageService;
private IDeviceActionService _deviceActionService;
protected int ShowModalAnimationDelay = 400; protected int ShowModalAnimationDelay = 400;
protected int ShowPageAnimationDelay = 100; protected int ShowPageAnimationDelay = 100;
@@ -101,18 +103,22 @@ namespace Bit.App.Pages
}); });
} }
private void SetStorageService() private void SetServices()
{ {
if (_storageService == null) if (_storageService == null)
{ {
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _storageService = ServiceContainer.Resolve<IStorageService>("storageService");
} }
if (_deviceActionService == null)
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
}
} }
private void SaveActivity() private void SaveActivity()
{ {
SetStorageService(); SetServices();
_storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow); _storageService.SaveAsync(Constants.LastActiveKey, _deviceActionService.GetActiveTime());
} }
} }
} }

View File

@@ -241,5 +241,10 @@ namespace Bit.App.Services
catch { } catch { }
return false; return false;
} }
public long GetActiveTime()
{
return _deviceActionService.GetActiveTime();
}
} }
} }

View File

@@ -28,5 +28,6 @@ namespace Bit.Core.Abstractions
bool SupportsDuo(); bool SupportsDuo();
Task<bool> SupportsBiometricAsync(); Task<bool> SupportsBiometricAsync();
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null); Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null);
long GetActiveTime();
} }
} }

View File

@@ -90,13 +90,13 @@ namespace Bit.Core.Services
{ {
return; return;
} }
var lastActive = await _storageService.GetAsync<DateTime?>(Constants.LastActiveKey); var lastActive = await _storageService.GetAsync<long?>(Constants.LastActiveKey);
if (lastActive == null) if (lastActive == null)
{ {
return; return;
} }
var diff = DateTime.UtcNow - lastActive.Value; var diff = _platformUtilsService.GetActiveTime() - lastActive;
if (diff.TotalSeconds >= vaultTimeout.Value) if (diff >= vaultTimeout * 60)
{ {
// Pivot based on saved action // Pivot based on saved action
var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey); var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey);

View File

@@ -430,6 +430,13 @@ namespace Bit.iOS.Core.Services
return false; return false;
} }
public long GetActiveTime()
{
// Fall back to UnixTimeSeconds in case this approach stops working. We'll lose clock-change protection but
// the lock functionality will continue to work.
return iOSHelpers.GetSystemUpTimeSeconds() ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e) private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
{ {
if (sender is UIImagePickerController picker) if (sender is UIImagePickerController picker)

View File

@@ -1,4 +1,7 @@
using Bit.App.Utilities; using System;
using System.Runtime.InteropServices;
using Bit.App.Utilities;
using Microsoft.AppCenter.Crashes;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; using Xamarin.Forms.Platform.iOS;
@@ -7,7 +10,52 @@ namespace Bit.iOS.Core.Utilities
{ {
public static class iOSHelpers public static class iOSHelpers
{ {
public static System.nfloat? GetAccessibleFont<T>(double size) [DllImport(ObjCRuntime.Constants.SystemLibrary)]
internal static extern int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string property, IntPtr output,
IntPtr oldLen, IntPtr newp, uint newLen);
// Returns the difference between when the system was booted and now in seconds, resulting in a duration that
// includes sleep time.
// ref: https://forums.xamarin.com/discussion/20006/access-to-sysctl-h
// ref: https://github.com/XLabs/Xamarin-Forms-Labs/blob/master/src/Platform/XLabs.Platform.iOS/Device/AppleDevice.cs
public static long? GetSystemUpTimeSeconds()
{
long? uptime = null;
IntPtr pLen = default, pStr = default;
try
{
var property = "kern.boottime";
pLen = Marshal.AllocHGlobal(sizeof(int));
sysctlbyname(property, IntPtr.Zero, pLen, IntPtr.Zero, 0);
var length = Marshal.ReadInt32(pLen);
pStr = Marshal.AllocHGlobal(length);
sysctlbyname(property, pStr, pLen, IntPtr.Zero, 0);
var timeVal = Marshal.PtrToStructure<TimeVal>(pStr);
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (timeVal.sec > 0 && now > 0)
{
uptime = now - timeVal.sec;
}
}
catch (Exception e)
{
Crashes.TrackError(e);
}
finally
{
if (pLen != default)
{
Marshal.FreeHGlobal(pLen);
}
if (pStr != default)
{
Marshal.FreeHGlobal(pStr);
}
}
return uptime;
}
public static nfloat? GetAccessibleFont<T>(double size)
{ {
var pointSize = UIFontDescriptor.PreferredBody.PointSize; var pointSize = UIFontDescriptor.PreferredBody.PointSize;
if (size == Device.GetNamedSize(NamedSize.Large, typeof(T))) if (size == Device.GetNamedSize(NamedSize.Large, typeof(T)))
@@ -60,5 +108,11 @@ namespace Bit.iOS.Core.Utilities
control, NSLayoutAttribute.Bottom, 1, 10f), control, NSLayoutAttribute.Bottom, 1, 10f),
}); });
} }
private struct TimeVal
{
public long sec;
public long usec;
}
} }
} }

View File

@@ -4,7 +4,6 @@ using System.Threading.Tasks;
using AuthenticationServices; using AuthenticationServices;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Resources;
using Bit.App.Services; using Bit.App.Services;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
@@ -29,8 +28,6 @@ namespace Bit.iOS
private Core.NFCReaderDelegate _nfcDelegate = null; private Core.NFCReaderDelegate _nfcDelegate = null;
private NSTimer _clipboardTimer = null; private NSTimer _clipboardTimer = null;
private nint _clipboardBackgroundTaskId; private nint _clipboardBackgroundTaskId;
private NSTimer _vaultTimeoutTimer = null;
private nint _lockBackgroundTaskId;
private NSTimer _eventTimer = null; private NSTimer _eventTimer = null;
private nint _eventBackgroundTaskId; private nint _eventBackgroundTaskId;
@@ -59,15 +56,7 @@ namespace Bit.iOS
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) => _broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
{ {
if (message.Command == "scheduleVaultTimeoutTimer") if (message.Command == "startEventTimer")
{
VaultTimeoutTimer((int)message.Data);
}
else if (message.Command == "cancelVaultTimeoutTimer")
{
CancelVaultTimeoutTimer();
}
else if (message.Command == "startEventTimer")
{ {
StartEventTimer(); StartEventTimer();
} }
@@ -212,7 +201,7 @@ namespace Bit.iOS
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view); UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view);
UIApplication.SharedApplication.KeyWindow.EndEditing(true); UIApplication.SharedApplication.KeyWindow.EndEditing(true);
UIApplication.SharedApplication.SetStatusBarHidden(true, false); UIApplication.SharedApplication.SetStatusBarHidden(true, false);
_storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow); _storageService.SaveAsync(Constants.LastActiveKey, _deviceActionService.GetActiveTime());
_messagingService.Send("slept"); _messagingService.Send("slept");
base.DidEnterBackground(uiApplication); base.DidEnterBackground(uiApplication);
} }
@@ -322,55 +311,6 @@ namespace Bit.iOS
"pushNotificationService", iosPushNotificationService); "pushNotificationService", iosPushNotificationService);
} }
private void VaultTimeoutTimer(int vaultTimeoutMinutes)
{
if (_lockBackgroundTaskId > 0)
{
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
_lockBackgroundTaskId = 0;
}
_lockBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
{
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
_lockBackgroundTaskId = 0;
});
var vaultTimeoutMs = vaultTimeoutMinutes * 60000;
_vaultTimeoutTimer?.Invalidate();
_vaultTimeoutTimer?.Dispose();
_vaultTimeoutTimer = null;
var vaultTimeoutMsSpan = TimeSpan.FromMilliseconds(vaultTimeoutMs + 10);
Device.BeginInvokeOnMainThread(() =>
{
_vaultTimeoutTimer = NSTimer.CreateScheduledTimer(vaultTimeoutMsSpan, timer =>
{
Device.BeginInvokeOnMainThread(() =>
{
_vaultTimeoutService.CheckVaultTimeoutAsync();
_vaultTimeoutTimer?.Invalidate();
_vaultTimeoutTimer?.Dispose();
_vaultTimeoutTimer = null;
if (_lockBackgroundTaskId > 0)
{
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
_lockBackgroundTaskId = 0;
}
});
});
});
}
private void CancelVaultTimeoutTimer()
{
_vaultTimeoutTimer?.Invalidate();
_vaultTimeoutTimer?.Dispose();
_vaultTimeoutTimer = null;
if (_lockBackgroundTaskId > 0)
{
UIApplication.SharedApplication.EndBackgroundTask(_lockBackgroundTaskId);
_lockBackgroundTaskId = 0;
}
}
private async Task ClearClipboardTimerAsync(Tuple<string, int?, bool> data) private async Task ClearClipboardTimerAsync(Tuple<string, int?, bool> data)
{ {
if (data.Item3) if (data.Item3)