As part of the background research on the U2F HID handshake information leak, I discovered through manual code analysis that the Yubico libu2f-host host-side C library contained an out of bounds write vulnerability which could be triggered by a malicious U2F client device.
Since the libu2f-host library is the basis for PAM over U2F login functionality that can be added to Unix-like operating systems such as Linux, BSD and macOS, this issue had the potential to affect a number of specially protected systems. MITRE assigned CVE-2018-20340 for this issue.
Note: part two describes CVE-2019-9578, a closely related second vulnerability.
I’m a freelance Security Consultant and currently available for new projects.
If you are looking for assistance to secure your projects or organization, contact me.
As described in depth in the U2F HID issue article, the U2F HID communication with an U2F client device is always initiated by the host side. The U2F client - usually a physical token connected via USB - then responds with a specific reply message to the initial request and the main U2F communication including the cryptographic challenges can be done over the arranged channel id.
The vulnerability is present due to faulty handling of the first reply message in the libu2f-host library.
The U2F handshake is initiated via the U2FHID_INIT message in the init_device function:
The actual communication happens in the u2fh_sendrecv function which is built to generically handle the send- and receive logic. Note that u2fh_sendrecv conveniently collects and returns the relevant bytes of the reply message(s) to the calling function. The generic nature of this utility function is part of the general problem, as I will show later.
In the beginning of init_device, the response buffer resp with the size 1024 bytes is allocated and resplen is set to value 1024, as shown in the previous code snippet. resplen is then handed to u2fh_sendrecv where it is used under the recvlen name as an upper limit on the number of U2F “payload” bytes that may be accepted during the receive stage:
However, resplen is also used as a return parameter to specify how many U2F message payload bytes were actually written to the resp buffer:
Since u2fh_sendrecv is a generic send/receive function, it is not designed to limit the type and length of the received message.
For the relevant U2F client response, the U2F specification defines a particular response message with a fixed number of 17 bytes of U2F payload. This data is normally contained in a single HID response packet.
The FIDO U2F standard mandates compatibility with larger responses:
An U2FHID host shall accept a response size that is longer than the anticipated size to allow for future extensions of the protocol, yet maintaining backwards compatibility.
However, u2fh_sendrecv imposes no meaningful checks at all and happily assembles multi-packet messages (via so-called continuation packets) which allows attackers to provide up to 1024 bytes of U2F payload data instead of the regular 17 bytes while still returning U2FH_OK and continuing in the program flow.
The actual vulnerability is triggered in init_device once the resp buffer of attacker-controlled size and with attacker-controlled contents is memcpy‘ed to the initresp struct that is at most 20 bytes in size, which leads to the out of bounds write:
Up to 1004 bytes of attacker-controlled data are written to the stack.
Attack scenario and security implications
The attack applies to host systems which use of libu2f-host through
an authentication module like pam_u2f.so (privileged execution)
other userspace applications that depend on libu2f-host such as yubikey-manager
the command-line tool bundled with libu2f-host
Once the library initiates a handshake with an U2F token, e.g. during an authentication attempt, the token can trigger the buffer overflow on the stack and potentially execute code with the relevant privileges or potentially skip the login authentication. Any malicious USB device which enumerates as a recognized U2F token class can perform this attack.
In my opinion, the CVSS Scope (S) metric should be Changed if the attack allows to move past authentication barriers and therefore past the vulnerable component, but I find their overall classification reasonable.
I find it plausible that drivers and low-level code for local, “safe” systems such as smartcards have less thoroughly analyzed or hardened interfaces since the involved developers are mainly focused on other development aspects or different attack scenarios. This is clearly an interesting area for further research.
I was surprised to find that the libu2f-host build system did not enable -fstack-protector-all or other protections such as -D_FORTIFY_SOURCE=2 when compiled from source:
No canary found
Particularly the missing stack canary is a serious problem in the context of this vulnerability as it reliably detects the attack and limits the impact to denial of service.
During discussion with Yubico, it turned out that most of the hardening flags are usually provided by the build environment.
This meant that most binary packages were in fact shipped with mitigations in place by major distributions like Debian, Ubuntu and others since their build environment includes the hardening flags:
However, it is possible that the library is used productively in other places and systems without these mitigations, e.g. as a self-compiled binary or as a code dependency.
In my opinion, these hardening flags should be included and active by default.
During research, I found that the host library always uses the identical “nonce” when initiating contact with the U2F client:
For the malicious client, this provides a simple and accurate way to detect that libu2f-host is used on the host side.
Through this detection, a malicious U2F token can behave functionally correct when used against unaffected host-side U2F code (e.g. browsers) and only trigger the exploit once used with the vulnerable library.
I’ve built a simple “malicious” U2F token on the basis of the Trezor One. The device detects the characteristic nonce value of the library (see above) and “attacks” with one packet full of AAAAA to demonstrate the issue.
For hardened libu2f-host binaries with stack canary, this leads to a crash, as expected:
I responsibly disclosed the vulnerability to Yubico in December 2018 and received quick and competent feedback during the holiday season.
They applied for CVE-2018-20340 and originally asked me for 90 days of disclosure embargo but managed to ship the release after about ~52 days.
A number of Linux distributions were given early warning to prepare security releases.
Overall, I am fairly satisfied with the disclosure process.