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

Crash when device is locked #1260

Closed
melbyruarus opened this issue Dec 21, 2014 · 13 comments
Closed

Crash when device is locked #1260

melbyruarus opened this issue Dec 21, 2014 · 13 comments
Labels

Comments

@melbyruarus
Copy link

Goal

Use Realm while the device is locked

Expected results

Realm will behave the same as it does when the device is not locked, or will fail gracefully if this is not possible.

Actual results

Calling any Realm method that will read/write data to the disk while the device is locked will cause the app to crash.

Steps to reproduce

Will require:

  • An iOS 8 device with PIN set and auto lock set to immediate
  1. Create a new Swift Xcode project linking against Realm 0.89
  2. Add code to the project which will execute in the background by using location services, audio, app refresh etc. Easiest option is to use background location services. This requires selecting the appropriate option in the Background Modes of the capability tab and starting to listen for updates before the app is backgrounded.
  3. Add code to the background callback code which will trigger a read/write of the database.
  4. Install on a device
  5. Open the app
  6. Give permissions to use whatever background modes were selected
  7. Lock the device

The app will then crash sometime later when iOS stops allowing access to encrypted data.

Code sample

https://github.com/melbyruarus/Realm-Background-Crash

The crash is triggered in the LocationHelper.log method

Discussion

I believe this occurs because if a PIN is set and the device is locked then shortly thereafter iOS throws away the decryption keys for files which are only accessible while the device is unlocked (the default?). This means that the Realm database file becomes inaccessible and gets a read/write/open error when accessed, and this error is not correctly handled.

Ideally there would either be a way to customise the creation of the database file so that it can be specified as not needing to be protected on lock, and additionally any read/write/opens should fail gracefully rather than exploding.

This may well be a duplicate of #773

Version info

iOS 8, Realm 0.89, Xcode 6.1.1

@tgoyne
Copy link
Member

tgoyne commented Dec 22, 2014

There isn't really any way to gracefully fail when the file isn't readable. Since nearly every method in our API could fail, all we could really do is throw an exception instead, which would still be a fatal error. Making every single method take an NSError out parameter would not really be practical.

You can control whether or not files get locked when the device is locked by changing the value of NSFileProtection with [NSFileManager setAttributes:ofItemAtPath:error:].

@melbyruarus
Copy link
Author

While I agree that it is difficult to deal with the fact that all of these operations are fundamentally failable—and with the current API dealing with this would be a challenge—I feel it is still not appropriate that something as common as a read or write error is a fatal and unrecoverable error which will bring down an entire application.

I've actually implemented your suggestion of changing the NSFileProtection attribute for the default Realm in the sample project I linked to earlier (data-protection-disabled branch). I've changed this to NSFileProtectionCompleteUntilFirstUserAuthentication and I still observe a crash.

Stack trace

frame #0: 0x0000000193e393f0 libc++abi.dylib`__cxa_throw
frame #1: 0x00000001000a6db4 Crash`tightdb::util::File::open_internal(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, tightdb::util::File::AccessMode, tightdb::util::File::CreateMode, int, bool*) + 1376
frame #2: 0x000000010020639c Crash`tightdb::util::File::open(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, tightdb::util::File::AccessMode, tightdb::util::File::CreateMode, int) + 52
frame #3: 0x0000000100202b28 Crash`tightdb::SharedGroup::open(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, tightdb::SharedGroup::DurabilityLevel, bool, char const*) + 876
frame #4: 0x00000001002041d4 Crash`tightdb::SharedGroup::open(tightdb::Replication&, tightdb::SharedGroup::DurabilityLevel, char const*) + 144
frame #5: 0x000000010008dac0 Crash`tightdb::SharedGroup::SharedGroup(this=0x0000000126828000, repl=0x0000000126518af0, dlevel=durability_Full, key=0x0000000000000000) + 340 at group_shared.hpp:510
frame #6: 0x000000010008d954 Crash`tightdb::SharedGroup::SharedGroup(this=0x0000000126828000, repl=0x0000000126518af0, dlevel=durability_Full, key=0x0000000000000000) + 44 at group_shared.hpp:511
frame #7: 0x0000000100086768 Crash`-[RLMRealm initWithPath:key:readOnly:inMemory:error:] [inlined] std::__1::__unique_if<tightdb::SharedGroup>::__unique_single std::__1::make_unique<tightdb::SharedGroup, tightdb::Replication&, tightdb::SharedGroup::DurabilityLevel&, char const*>(__args=0x0000000126518af0, __args=0x000000016fde12fc, __args=0x000000016fde12e8) + 60 at memory:3044
frame #8: 0x000000010008672c Crash`-[RLMRealm initWithPath:key:readOnly:inMemory:error:](self=0x00000001740b3da0, _cmd=0x00000001002e6eb4, path=0x00000001701a46e0, key=0x0000000000000000, readonly=false, inMemory=false, error=0x000000016fde1b98) + 2664 at RLMRealm.mm:244
frame #9: 0x00000001000880a0 Crash`+[RLMRealm realmWithPath:key:readOnly:inMemory:dynamic:schema:error:](self=0x000000010032be70, _cmd=0x00000001002e6e46, path=0x00000001701a46e0, key=0x0000000000000000, readonly=false, inMemory=false, dynamic=false, customSchema=0x0000000000000000, outError=0x0000000000000000) + 2072 at RLMRealm.mm:396
frame #10: 0x0000000100087558 Crash`+[RLMRealm realmWithPath:readOnly:error:](self=0x000000010032be70, _cmd=0x00000001002e6e28, path=0x00000001701a46e0, readonly=false, outError=0x0000000000000000) + 136 at RLMRealm.mm:327
frame #11: 0x00000001000873cc Crash`+[RLMRealm defaultRealm](self=0x000000010032be70, _cmd=0x00000001002e523f) + 124 at RLMRealm.mm:315
frame #12: 0x0000000100022874 Crash`Crash.LocationHelper.log (message=Swift.String at 0x000000016fde22d8, atTime=None, self=0x000000017004f990)(Swift.String, atTime : Swift.Optional<ObjectiveC.NSDate>) -> () + 2144 at LocationHelper.swift:53
frame #13: 0x0000000100023400 Crash`Crash.LocationHelper.locationManager (manager=Some, locations=Some, self=0x000000017004f990)(Swift.ImplicitlyUnwrappedOptional<ObjectiveC.CLLocationManager>, didUpdateLocations : Swift.ImplicitlyUnwrappedOptional<Swift.Array<Swift.AnyObject>>) -> () + 388 at LocationHelper.swift:72
frame #14: 0x0000000100023a80 Crash`@objc Crash.LocationHelper.locationManager (Crash.LocationHelper)(Swift.ImplicitlyUnwrappedOptional<ObjectiveC.CLLocationManager>, didUpdateLocations : Swift.ImplicitlyUnwrappedOptional<Swift.Array<Swift.AnyObject>>) -> () + 344 at LocationHelper.swift:0
frame #15: 0x0000000184647848 CoreLocation`___lldb_unnamed_function233$$CoreLocation + 1664
frame #16: 0x00000001846441ac CoreLocation`___lldb_unnamed_function133$$CoreLocation + 76
frame #17: 0x000000018463ee5c CoreLocation`___lldb_unnamed_function16$$CoreLocation + 104
frame #18: 0x0000000183eaea28 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 20
frame #19: 0x0000000183eadb30 CoreFoundation`__CFRunLoopDoBlocks + 312
frame #20: 0x0000000183eac154 CoreFoundation`__CFRunLoopRun + 1756
frame #21: 0x0000000183dd90a4 CoreFoundation`CFRunLoopRunSpecific + 396
frame #22: 0x000000018cf7b5a4 GraphicsServices`GSEventRunModal + 168
frame #23: 0x000000018870e3c0 UIKit`UIApplicationMain + 1488
frame #24: 0x0000000100029390 Crash`top_level_code + 76 at AppDelegate.swift:13
frame #25: 0x00000001000293d0 Crash`main + 48 at AppDelegate.swift:0
frame #26: 0x0000000194cbaa08 libdyld.dylib`start + 4

@alazier
Copy link
Contributor

alazier commented Dec 23, 2014

In this stack trace we are throwing a c++ exception rather than returning an error when opening the Realm. In this case we should definitely be catching this exception and returning an NSError.

@alazier alazier assigned alazier and unassigned alazier Dec 23, 2014
@alazier alazier added the T-Bug label Dec 23, 2014
@kronik
Copy link

kronik commented Jan 6, 2015

I had the same issue in obj-c code: #1077

@alazier
Copy link
Contributor

alazier commented Jan 6, 2015

@kronik this does seem to be the same issue.

I think we can make this work for the case @melbyruarus provided. If make things fail gracefully by properly returning an error when opening a Realm using the initializer with an output error, then users can at least detect and avoid crashing by getting a new Realm instance and checking the error before performing any potentially unsafe background operations.

The ideal long term solution though would be to actually make things work in the background.

@segiddins
Copy link
Contributor

It appears to me that we already catch any C++ exceptions inside -[RLMRealm initWithPath:key:readOnly:inMemory:error:] (https://github.com/realm/realm-cocoa/blob/master/Realm/RLMRealm.mm#L262).

@alazier
Copy link
Contributor

alazier commented Jan 8, 2015

@segiddins there are other core operations in the factory method which are not wrapped with an exception handler - these cases should also return an error from the factory methods. There is also the possibility that we are not catching the type of exception being thrown in this case as we may be missing some handers.

@segiddins segiddins self-assigned this Jan 29, 2015
@segiddins
Copy link
Contributor

Realm actually will fail gracefully as long as you use on of the factory methods that takes an error parameter (and a pointer to an NSError is passed in). The answer in situations like this is either to disable NSFIleProtection entirely (and optionally use Realm's built-in encryption support), or to recognize that opening the Realm when the app is in the background can fail, and using the appropriate factory method so you can handle the error (and thus the exception won't be thrown by Realm).

@melbyruarus I hope this helps.

@frankcjw
Copy link

frankcjw commented Apr 2, 2015

Is there any way to read/write while device was locked ?

@jpsim
Copy link
Contributor

jpsim commented Apr 2, 2015

You'll have to set NSFileProtectionAttributes on the realm files to allow reading when locked. See #1571 (comment)

@rex-remind101
Copy link

@jpsim it seems that the parent directory advised to change NSFileProtectionAttributes of in the documentation may contain files not pertaining to realm. Is there a way to set the realm config to store all realm files and auxiliary files into a specific directly, separate from the rest of the application documents directory?

@rex-remind101
Copy link

rex-remind101 commented Jun 5, 2016

Figured it out:

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.fileURL = [[[[config.fileURL URLByDeletingLastPathComponent]
                        URLByAppendingPathComponent:kRealmDirectory]
                       URLByAppendingPathComponent:kRealmDBName]
                      URLByAppendingPathExtension:kRealmExtension];
[RLMRealmConfiguration setDefaultConfiguration:config];

^ fyi, if you don't explicitly set it as the default realm, you may end up with the default.realm in addition to the realm in your new directory. This code also doesn't include creating the directory if it doesn't already exist.

@rex-remind101
Copy link

Also, fyi, using a subdirectory doesn't seem to work with config.deleteRealmIfMigrationNeeded = YES;. Have to @try{} @catch{} manually.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 16, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

8 participants