Block Camera system — iOS
Firmware verified 16.5.1 and 16.6, devices that I tested:
- Affected: XS, SE 2022, 11
- Seems not affected: 13, 13 pro, 8, 7 plus, 12 mini
Intro
During the development of a RAW Camera project I encountered a couple of bugs that result in a black screen when using any Camera app on Apple devices. I submitted a detailed report to Apple, but unfortunately, it didn’t qualify for a reward. However, I strongly believe that these issues might have lower-level explanations and could potentially be eligible for a reward. I suspect they are related to how the kernel manages camera drivers during the synchronization of different events, i.e. potential race conditions.
Any assistance or contribution to this research is highly appreciated, github repo here.
I developed a simple app, proof-of-concept, to demonstrate the issues and execute their triggers, see the XCode project for technical details and the video at the end of the page.
Description
With the exception of the PHOTO
mode in the Camera app, it is possible to block the main back camera anywhere in the system by executing certain triggers. The only way to unlock it is by rebooting or by capturing one photo in the aforementioned mode.
Main issue
The issue is caused by a bug in the focus, specifically in the lensPosition
. When shooting in bayerRaw
mode with the flash on, if the focus is not completed correctly before the photo acquisition (e.g., when switching from a near object to a far object), the lensPosition
can become stuck and won't change anymore, resetted to default 1.0. Querying .isAdjustingFocus
will always return true, and setting .continuousAutoFocus
or a custom lensPosition
won't change anything, leaving it locked.
The expected behavior is that the focus and lensPosition
shouldn't get stuck after capturing, whether we're in .continuousAutoFocus
or if we're requesting a different lensPosition
through .setFocusModeLocked
.
This was the source of the issue, now how to black out the camera: After capturing the photo, in its delegate handler, the combination of the above bug with a “fast” switch to a different .sessionPreset
from the current .photo
(needed by bayerRAW), will make the camera full black and unavailable anywhere in the system, even uninstalling the app.
To unblock it, force a new capture without doing the session preset switch.
There may be an issue with the synchronization of events and how the code handles the camera driver when focusing, particularly when the flash is on, as it may be interrupted before it finishes and is not re-initialized.
Steps to reproduce:
- Write a basic camera app: set
.photo
preset,bayerRAW
format, and turn the flash on. - Execute
.capturePhoto
. - In the delegate of
.capturePhoto
, just afterdefer { didFinish?() }
, begin a session configuration, setting up a different preset from.photo
, like.high
. - Call
.commitConfiguration()
. - Do not put the camera on a black surface, and move the phone during the capturing process if possible.
Expected results:
- The time between point .3 and .4 to commit the session configuration should be a few ms.
- The
lensPosition
shouldn't be reset to the default 1.0, or if it is, it should be possible to unlock it.
Actual results:
- If the bug succeeds, the time between point .3 and .4 to commit the session configuration will be around 9 seconds.
- The
lensPosition
is 1.0 and is stuck/locked.
Issue #2
It’s still possible to .capturePhoto
even if the session is not running, but the output, added previously before going in background, hasn't been detached yet by the system (a possible race condition).
Steps to reproduce:
- Configure a capture session correctly and run it.
- Leave the app, going in background and return quickly (so that the system won’t have enough time to detach the previously added output).
- Execute
.capturePhoto
even if the session is not running.
Expected results:
An error message that the session is not running and it’s not possible to capture.
Actual results:
It’s capturing the photo
I have written a simple camera app, zip file enclosed, and there are comments with references to issue #1, which is demonstrated through the “Block” button, and issue #2, which is demonstrated through the “Un-block” button.