We engaged Doyensec to perform a security assessment of our firmware, v3.0.1 at the time of testing. During a 10 person/days project, Doyensec discovered and reported 3 vulnerabilities in our firmware. While two of the issues are considered informational, one issue has been rated as high severity and fixed in v3.1.0. The full report is available with all details, while in this post we'd like to give a high level summary of the engagement and findings.
Why a Security Analysis, Why Now?
One of the first requests we received after Solo's Kickstarter was to run an independent security audit. At the time we didn't have resources to run it and towards the end of 2019 I even closed the ticket as won't fix, causing a series of complaints from the community.
Recently, we shared that we're building a new model of Solo based on a new microcontroller, the NXP LPC55S69, and a new firmware rewritten in Rust (a blog post on the firmware is coming soon). As most of our energies will be spent on the new firmware, we didn't want the current STM32-based firmware to be abandoned. We'll keep supporting it, fixing bugs and vulnerabilities, but it's likely it will receive less attention from the wider community.
Therefore we thought this was a good time for a security analysis.
We asked Doyensec to detail not just their findings but also their process, so that we can re-validate the new firmware in Rust when released. We expect to run another analysis on the new firmware, although there's no concrete plan yet.
The Major Finding: Downgrade Attack
The security review consisted of a manual source code review and fuzzing of the firmware. One researcher performed the review for 2 weeks from Jan 21 to Jan 31, 2020.
In short, he found a downgrade attack where he was able to "upgrade" a firmware to a previous version, exploiting the ability to upload the firmware in multiple, unordered chunks. Downgrade attacks are generally very sensitive because they allow an attacker to downgrade to a previous version of the firmware and then take advantage of older known vulnerabilities.
Practically speaking, however, running such an attack against a Solo key requires either physical access to the key or -if attempted on a malicious site- an explicit user acknowledgement on the WebAuthn window.
This means that your key is almost certainly safe. In addition, we always recommend upgrading the firmware with our official tools.
Also note that our firmware is digitally signed and this downgrade attack couldn't bypass our signature verification. Therefore a possible attacker can only install one of our twenty-ish previous releases.
Needless to say, we took the vulnerability very seriously and fixed it immediately.
Anatomy of the Downgrade Attack
This was the incriminated code. And this is the patch, that should help understand what happened.
Solo firmware updates are a binary blob where the last 4 bytes represent the version. When a new firmware is installed on the keys, these bytes are checked to ensure that its version is greater than the currently installed one. The firmware digital signature is also verified, but this is irrelevant as this attack only allows to install older signed releases.
The new firmware is written to the keys in chunks. At every write, a pointer to the last written address is updated, so that eventually it will point to the new version at the end of the firmware. You might see the issue: we were assuming that chunks are written only once and in order, but this was not enforced. The patch fixes the issue by requiring that the chunks are written strictly in ascending order.
As an example, think of running v3.0.1, and take an old firmware - say v3.0.0. Search four bytes in it which, when interpreted as a version number, appear to be greater than v3.0.1. First, send the whole 3.0.0 firmware to the key. The last_written_app_address pointer now correctly points to the end of the firmware, encoding version 3.0.0.
Then, write again the four chosen bytes at their original location. Now last_written_app_address points somewhere in the middle of the firmware, and those 4 bytes are interpreted as a "random" version. It turns out firmware v3.0.0 contains some bytes which can be interpreted as v3.0.37 -- boom! Here is a fully working proof-of-concept.
Fuzzing with AFL
Update: in a previous version of this blog post we attributed fuzzing errors to tinycbor, but further analysis showed the issues are actually in the firmware.
The researcher also integrated AFL (American Fuzzy Lop) and started fuzzing our firmware. Our firmware depends on an external library, tinycbor, for parsing CBOR data. In about 24 hours of execution, the researcher exercised the code with over 100M inputs and found over 4k bogus inputs that are misinterpreted by tinycbor and cause a crash of our firmware. Interestingly, the initial inputs were generated by our FIDO2 testing framework.
The fuzzer will be integrated in our testing toolchain soon. If anyone in the community is interested in fuzzing and would like to contribute by fixing bugs in tinycbor we would be happy to share details and examples.
In summary, we engaged a security engineering company (Doyensec) to perform a security review of our firmware. You can read the full report for details on the process and the downgrade attack they found. For any additional question or for helping with fuzzing of tinycbor feel free to reach out on Twitter @SoloKeysSec or at [email protected].
We would like to thank Doyensec for their help in securing the SoloKeys platform. Please make sure to check their website, and oh, they're also launching a game soon. Yes, a mobile game with a hacking theme!