
* Update to QEMU v9.0.0 --------- Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Fabiano Rosas <farosas@suse.de> Signed-off-by: Peter Xu <peterx@redhat.com> Signed-off-by: Thomas Huth <thuth@redhat.com> Signed-off-by: Cédric Le Goater <clg@redhat.com> Signed-off-by: Zheyu Ma <zheyuma97@gmail.com> Signed-off-by: Ido Plat <ido.plat@ibm.com> Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com> Signed-off-by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru> Signed-off-by: David Hildenbrand <david@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com> Signed-off-by: Fiona Ebner <f.ebner@proxmox.com> Signed-off-by: Gregory Price <gregory.price@memverge.com> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Lorenz Brun <lorenz@brun.one> Signed-off-by: Yao Xingtao <yaoxt.fnst@fujitsu.com> Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr> Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu> Signed-off-by: Igor Mammedov <imammedo@redhat.com> Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Sven Schnelle <svens@stackframe.org> Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com> Signed-off-by: Jason Wang <jasowang@redhat.com> Signed-off-by: Helge Deller <deller@gmx.de> Signed-off-by: Nicholas Piggin <npiggin@gmail.com> Signed-off-by: Benjamin Gray <bgray@linux.ibm.com> Signed-off-by: Avihai Horon <avihaih@nvidia.com> Signed-off-by: Michael Tokarev <mjt@tls.msk.ru> Signed-off-by: Joonas Kankaala <joonas.a.kankaala@gmail.com> Signed-off-by: Marcin Juszkiewicz <marcin.juszkiewicz@linaro.org> Signed-off-by: Stefan Weil <sw@weilnetz.de> Signed-off-by: Zhao Liu <zhao1.liu@intel.com> Signed-off-by: Glenn Miles <milesg@linux.ibm.com> Signed-off-by: Oleg Sviridov <oleg.sviridov@red-soft.ru> Signed-off-by: Artem Chernyshev <artem.chernyshev@red-soft.ru> Signed-off-by: Yajun Wu <yajunw@nvidia.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> Signed-off-by: Pierre-Clément Tosi <ptosi@google.com> Signed-off-by: Lei Wang <lei4.wang@intel.com> Signed-off-by: Wei Wang <wei.w.wang@intel.com> Signed-off-by: Martin Hundebøll <martin@geanix.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org> Signed-off-by: Wafer <wafer@jaguarmicro.com> Signed-off-by: Yuxue Liu <yuxue.liu@jaguarmicro.com> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com> Signed-off-by: Nguyen Dinh Phi <phind.uet@gmail.com> Signed-off-by: Zack Buhman <zack@buhman.org> Signed-off-by: Keith Packard <keithp@keithp.com> Signed-off-by: Yuquan Wang wangyuquan1236@phytium.com.cn Signed-off-by: Matheus Tavares Bernardino <quic_mathbern@quicinc.com> Signed-off-by: Cindy Lu <lulu@redhat.com> Co-authored-by: Peter Maydell <peter.maydell@linaro.org> Co-authored-by: Fabiano Rosas <farosas@suse.de> Co-authored-by: Peter Xu <peterx@redhat.com> Co-authored-by: Thomas Huth <thuth@redhat.com> Co-authored-by: Cédric Le Goater <clg@redhat.com> Co-authored-by: Zheyu Ma <zheyuma97@gmail.com> Co-authored-by: Ido Plat <ido.plat@ibm.com> Co-authored-by: Ilya Leoshkevich <iii@linux.ibm.com> Co-authored-by: Markus Armbruster <armbru@redhat.com> Co-authored-by: Marc-André Lureau <marcandre.lureau@redhat.com> Co-authored-by: Paolo Bonzini <pbonzini@redhat.com> Co-authored-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru> Co-authored-by: David Hildenbrand <david@redhat.com> Co-authored-by: Kevin Wolf <kwolf@redhat.com> Co-authored-by: Stefan Reiter <s.reiter@proxmox.com> Co-authored-by: Fiona Ebner <f.ebner@proxmox.com> Co-authored-by: Gregory Price <gregory.price@memverge.com> Co-authored-by: Lorenz Brun <lorenz@brun.one> Co-authored-by: Yao Xingtao <yaoxt.fnst@fujitsu.com> Co-authored-by: Philippe Mathieu-Daudé <philmd@linaro.org> Co-authored-by: Arnaud Minier <arnaud.minier@telecom-paris.fr> Co-authored-by: BALATON Zoltan <balaton@eik.bme.hu> Co-authored-by: Igor Mammedov <imammedo@redhat.com> Co-authored-by: Akihiko Odaki <akihiko.odaki@daynix.com> Co-authored-by: Richard Henderson <richard.henderson@linaro.org> Co-authored-by: Sven Schnelle <svens@stackframe.org> Co-authored-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Co-authored-by: Helge Deller <deller@kernel.org> Co-authored-by: Harsh Prateek Bora <harshpb@linux.ibm.com> Co-authored-by: Benjamin Gray <bgray@linux.ibm.com> Co-authored-by: Nicholas Piggin <npiggin@gmail.com> Co-authored-by: Avihai Horon <avihaih@nvidia.com> Co-authored-by: Michael Tokarev <mjt@tls.msk.ru> Co-authored-by: Joonas Kankaala <joonas.a.kankaala@gmail.com> Co-authored-by: Marcin Juszkiewicz <marcin.juszkiewicz@linaro.org> Co-authored-by: Stefan Weil <sw@weilnetz.de> Co-authored-by: Dayu Liu <liu.dayu@zte.com.cn> Co-authored-by: Zhao Liu <zhao1.liu@intel.com> Co-authored-by: Glenn Miles <milesg@linux.vnet.ibm.com> Co-authored-by: Artem Chernyshev <artem.chernyshev@red-soft.ru> Co-authored-by: Yajun Wu <yajunw@nvidia.com> Co-authored-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> Co-authored-by: Pierre-Clément Tosi <ptosi@google.com> Co-authored-by: Wei Wang <wei.w.wang@intel.com> Co-authored-by: Martin Hundebøll <martin@geanix.com> Co-authored-by: Michael S. Tsirkin <mst@redhat.com> Co-authored-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org> Co-authored-by: Wafer <wafer@jaguarmicro.com> Co-authored-by: lyx634449800 <yuxue.liu@jaguarmicro.com> Co-authored-by: Gerd Hoffmann <kraxel@redhat.com> Co-authored-by: Nguyen Dinh Phi <phind.uet@gmail.com> Co-authored-by: Zack Buhman <zack@buhman.org> Co-authored-by: Keith Packard <keithp@keithp.com> Co-authored-by: Yuquan Wang <wangyuquan1236@phytium.com.cn> Co-authored-by: Matheus Tavares Bernardino <quic_mathbern@quicinc.com> Co-authored-by: Cindy Lu <lulu@redhat.com>
553 lines
15 KiB
C
553 lines
15 KiB
C
/*
|
|
* U2F USB Passthru device.
|
|
*
|
|
* Copyright (c) 2020 César Belley <cesar.belley@lse.epita.fr>
|
|
* Written by César Belley <cesar.belley@lse.epita.fr>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/main-loop.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qapi/error.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/usb.h"
|
|
#include "migration/vmstate.h"
|
|
|
|
#include "u2f.h"
|
|
|
|
#ifdef CONFIG_LIBUDEV
|
|
#include <libudev.h>
|
|
#endif
|
|
#include <linux/hidraw.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#define NONCE_SIZE 8
|
|
#define BROADCAST_CID 0xFFFFFFFF
|
|
#define TRANSACTION_TIMEOUT 120000
|
|
|
|
struct transaction {
|
|
uint32_t cid;
|
|
uint16_t resp_bcnt;
|
|
uint16_t resp_size;
|
|
|
|
/* Nonce for broadcast isolation */
|
|
uint8_t nonce[NONCE_SIZE];
|
|
};
|
|
|
|
typedef struct U2FPassthruState U2FPassthruState;
|
|
|
|
#define CURRENT_TRANSACTIONS_NUM 4
|
|
|
|
struct U2FPassthruState {
|
|
U2FKeyState base;
|
|
|
|
/* Host device */
|
|
char *hidraw;
|
|
int hidraw_fd;
|
|
|
|
/* Current Transactions */
|
|
struct transaction current_transactions[CURRENT_TRANSACTIONS_NUM];
|
|
uint8_t current_transactions_start;
|
|
uint8_t current_transactions_end;
|
|
uint8_t current_transactions_num;
|
|
|
|
/* Transaction time checking */
|
|
int64_t last_transaction_time;
|
|
QEMUTimer timer;
|
|
};
|
|
|
|
#define TYPE_U2F_PASSTHRU "u2f-passthru"
|
|
#define PASSTHRU_U2F_KEY(obj) \
|
|
OBJECT_CHECK(U2FPassthruState, (obj), TYPE_U2F_PASSTHRU)
|
|
|
|
/* Init packet sizes */
|
|
#define PACKET_INIT_HEADER_SIZE 7
|
|
#define PACKET_INIT_DATA_SIZE (U2FHID_PACKET_SIZE - PACKET_INIT_HEADER_SIZE)
|
|
|
|
/* Cont packet sizes */
|
|
#define PACKET_CONT_HEADER_SIZE 5
|
|
#define PACKET_CONT_DATA_SIZE (U2FHID_PACKET_SIZE - PACKET_CONT_HEADER_SIZE)
|
|
|
|
struct packet_init {
|
|
uint32_t cid;
|
|
uint8_t cmd;
|
|
uint8_t bcnth;
|
|
uint8_t bcntl;
|
|
uint8_t data[PACKET_INIT_DATA_SIZE];
|
|
} QEMU_PACKED;
|
|
|
|
static inline uint32_t packet_get_cid(const void *packet)
|
|
{
|
|
return *((uint32_t *)packet);
|
|
}
|
|
|
|
static inline bool packet_is_init(const void *packet)
|
|
{
|
|
return ((uint8_t *)packet)[4] & (1 << 7);
|
|
}
|
|
|
|
static inline uint16_t packet_init_get_bcnt(
|
|
const struct packet_init *packet_init)
|
|
{
|
|
uint16_t bcnt = 0;
|
|
bcnt |= packet_init->bcnth << 8;
|
|
bcnt |= packet_init->bcntl;
|
|
|
|
return bcnt;
|
|
}
|
|
|
|
static void u2f_passthru_reset(U2FPassthruState *key)
|
|
{
|
|
timer_del(&key->timer);
|
|
qemu_set_fd_handler(key->hidraw_fd, NULL, NULL, key);
|
|
key->last_transaction_time = 0;
|
|
key->current_transactions_start = 0;
|
|
key->current_transactions_end = 0;
|
|
key->current_transactions_num = 0;
|
|
}
|
|
|
|
static void u2f_timeout_check(void *opaque)
|
|
{
|
|
U2FPassthruState *key = opaque;
|
|
int64_t time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
|
|
|
|
if (time > key->last_transaction_time + TRANSACTION_TIMEOUT) {
|
|
u2f_passthru_reset(key);
|
|
} else {
|
|
timer_mod(&key->timer, time + TRANSACTION_TIMEOUT / 4);
|
|
}
|
|
}
|
|
|
|
static int u2f_transaction_get_index(U2FPassthruState *key, uint32_t cid)
|
|
{
|
|
for (int i = 0; i < key->current_transactions_num; ++i) {
|
|
int index = (key->current_transactions_start + i)
|
|
% CURRENT_TRANSACTIONS_NUM;
|
|
if (cid == key->current_transactions[index].cid) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static struct transaction *u2f_transaction_get(U2FPassthruState *key,
|
|
uint32_t cid)
|
|
{
|
|
int index = u2f_transaction_get_index(key, cid);
|
|
if (index < 0) {
|
|
return NULL;
|
|
}
|
|
return &key->current_transactions[index];
|
|
}
|
|
|
|
static struct transaction *u2f_transaction_get_from_nonce(U2FPassthruState *key,
|
|
const uint8_t nonce[NONCE_SIZE])
|
|
{
|
|
for (int i = 0; i < key->current_transactions_num; ++i) {
|
|
int index = (key->current_transactions_start + i)
|
|
% CURRENT_TRANSACTIONS_NUM;
|
|
if (key->current_transactions[index].cid == BROADCAST_CID
|
|
&& memcmp(nonce, key->current_transactions[index].nonce,
|
|
NONCE_SIZE) == 0) {
|
|
return &key->current_transactions[index];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void u2f_transaction_close(U2FPassthruState *key, uint32_t cid)
|
|
{
|
|
int index, next_index;
|
|
index = u2f_transaction_get_index(key, cid);
|
|
if (index < 0) {
|
|
return;
|
|
}
|
|
next_index = (index + 1) % CURRENT_TRANSACTIONS_NUM;
|
|
|
|
/* Rearrange to ensure the oldest is at the start position */
|
|
while (next_index != key->current_transactions_end) {
|
|
memcpy(&key->current_transactions[index],
|
|
&key->current_transactions[next_index],
|
|
sizeof(struct transaction));
|
|
|
|
index = next_index;
|
|
next_index = (index + 1) % CURRENT_TRANSACTIONS_NUM;
|
|
}
|
|
|
|
key->current_transactions_end = index;
|
|
--key->current_transactions_num;
|
|
|
|
if (key->current_transactions_num == 0) {
|
|
u2f_passthru_reset(key);
|
|
}
|
|
}
|
|
|
|
static void u2f_transaction_add(U2FPassthruState *key, uint32_t cid,
|
|
const uint8_t nonce[NONCE_SIZE])
|
|
{
|
|
uint8_t index;
|
|
struct transaction *transaction;
|
|
|
|
if (key->current_transactions_num >= CURRENT_TRANSACTIONS_NUM) {
|
|
/* Close the oldest transaction */
|
|
index = key->current_transactions_start;
|
|
transaction = &key->current_transactions[index];
|
|
u2f_transaction_close(key, transaction->cid);
|
|
}
|
|
|
|
/* Index */
|
|
index = key->current_transactions_end;
|
|
key->current_transactions_end = (index + 1) % CURRENT_TRANSACTIONS_NUM;
|
|
++key->current_transactions_num;
|
|
|
|
/* Transaction */
|
|
transaction = &key->current_transactions[index];
|
|
transaction->cid = cid;
|
|
transaction->resp_bcnt = 0;
|
|
transaction->resp_size = 0;
|
|
|
|
/* Nonce */
|
|
if (nonce != NULL) {
|
|
memcpy(transaction->nonce, nonce, NONCE_SIZE);
|
|
}
|
|
}
|
|
|
|
static void u2f_passthru_read(void *opaque);
|
|
|
|
static void u2f_transaction_start(U2FPassthruState *key,
|
|
const struct packet_init *packet_init)
|
|
{
|
|
int64_t time;
|
|
|
|
/* Transaction */
|
|
if (packet_init->cid == BROADCAST_CID) {
|
|
u2f_transaction_add(key, packet_init->cid, packet_init->data);
|
|
} else {
|
|
u2f_transaction_add(key, packet_init->cid, NULL);
|
|
}
|
|
|
|
/* Time */
|
|
time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
|
|
if (key->last_transaction_time == 0) {
|
|
qemu_set_fd_handler(key->hidraw_fd, u2f_passthru_read, NULL, key);
|
|
timer_init_ms(&key->timer, QEMU_CLOCK_VIRTUAL, u2f_timeout_check, key);
|
|
timer_mod(&key->timer, time + TRANSACTION_TIMEOUT / 4);
|
|
}
|
|
key->last_transaction_time = time;
|
|
}
|
|
|
|
static void u2f_passthru_recv_from_host(U2FPassthruState *key,
|
|
const uint8_t packet[U2FHID_PACKET_SIZE])
|
|
{
|
|
struct transaction *transaction;
|
|
uint32_t cid;
|
|
|
|
/* Retrieve transaction */
|
|
cid = packet_get_cid(packet);
|
|
if (cid == BROADCAST_CID) {
|
|
struct packet_init *packet_init;
|
|
if (!packet_is_init(packet)) {
|
|
return;
|
|
}
|
|
packet_init = (struct packet_init *)packet;
|
|
transaction = u2f_transaction_get_from_nonce(key, packet_init->data);
|
|
} else {
|
|
transaction = u2f_transaction_get(key, cid);
|
|
}
|
|
|
|
/* Ignore no started transaction */
|
|
if (transaction == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (packet_is_init(packet)) {
|
|
struct packet_init *packet_init = (struct packet_init *)packet;
|
|
transaction->resp_bcnt = packet_init_get_bcnt(packet_init);
|
|
transaction->resp_size = PACKET_INIT_DATA_SIZE;
|
|
|
|
if (packet_init->cid == BROADCAST_CID) {
|
|
/* Nonce checking for legitimate response */
|
|
if (memcmp(transaction->nonce, packet_init->data, NONCE_SIZE)
|
|
!= 0) {
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
transaction->resp_size += PACKET_CONT_DATA_SIZE;
|
|
}
|
|
|
|
/* Transaction end check */
|
|
if (transaction->resp_size >= transaction->resp_bcnt) {
|
|
u2f_transaction_close(key, cid);
|
|
}
|
|
u2f_send_to_guest(&key->base, packet);
|
|
}
|
|
|
|
static void u2f_passthru_read(void *opaque)
|
|
{
|
|
U2FPassthruState *key = opaque;
|
|
U2FKeyState *base = &key->base;
|
|
uint8_t packet[2 * U2FHID_PACKET_SIZE];
|
|
int ret;
|
|
|
|
/* Full size base queue check */
|
|
if (base->pending_in_num >= U2FHID_PENDING_IN_NUM) {
|
|
return;
|
|
}
|
|
|
|
ret = read(key->hidraw_fd, packet, sizeof(packet));
|
|
if (ret < 0) {
|
|
/* Detach */
|
|
if (base->dev.attached) {
|
|
usb_device_detach(&base->dev);
|
|
u2f_passthru_reset(key);
|
|
}
|
|
return;
|
|
}
|
|
if (ret != U2FHID_PACKET_SIZE) {
|
|
return;
|
|
}
|
|
u2f_passthru_recv_from_host(key, packet);
|
|
}
|
|
|
|
static void u2f_passthru_recv_from_guest(U2FKeyState *base,
|
|
const uint8_t packet[U2FHID_PACKET_SIZE])
|
|
{
|
|
U2FPassthruState *key = PASSTHRU_U2F_KEY(base);
|
|
uint8_t host_packet[U2FHID_PACKET_SIZE + 1];
|
|
ssize_t written;
|
|
|
|
if (packet_is_init(packet)) {
|
|
u2f_transaction_start(key, (struct packet_init *)packet);
|
|
}
|
|
|
|
host_packet[0] = 0;
|
|
memcpy(host_packet + 1, packet, U2FHID_PACKET_SIZE);
|
|
|
|
written = write(key->hidraw_fd, host_packet, sizeof(host_packet));
|
|
if (written != sizeof(host_packet)) {
|
|
error_report("%s: Bad written size (req 0x%zu, val 0x%zd)",
|
|
TYPE_U2F_PASSTHRU, sizeof(host_packet), written);
|
|
}
|
|
}
|
|
|
|
static bool u2f_passthru_is_u2f_device(int fd)
|
|
{
|
|
int ret, rdesc_size;
|
|
struct hidraw_report_descriptor rdesc;
|
|
const uint8_t u2f_hid_report_desc_header[] = {
|
|
0x06, 0xd0, 0xf1, /* Usage Page (FIDO) */
|
|
0x09, 0x01, /* Usage (FIDO) */
|
|
};
|
|
|
|
/* Get report descriptor size */
|
|
ret = ioctl(fd, HIDIOCGRDESCSIZE, &rdesc_size);
|
|
if (ret < 0 || rdesc_size < sizeof(u2f_hid_report_desc_header)) {
|
|
return false;
|
|
}
|
|
|
|
/* Get report descriptor */
|
|
memset(&rdesc, 0x0, sizeof(rdesc));
|
|
rdesc.size = rdesc_size;
|
|
ret = ioctl(fd, HIDIOCGRDESC, &rdesc);
|
|
if (ret < 0) {
|
|
return false;
|
|
}
|
|
|
|
/* Header bytes cover specific U2F rdesc values */
|
|
return memcmp(u2f_hid_report_desc_header, rdesc.value,
|
|
sizeof(u2f_hid_report_desc_header)) == 0;
|
|
}
|
|
|
|
#ifdef CONFIG_LIBUDEV
|
|
static int u2f_passthru_open_from_device(struct udev_device *device)
|
|
{
|
|
const char *devnode = udev_device_get_devnode(device);
|
|
|
|
int fd = qemu_open_old(devnode, O_RDWR);
|
|
if (fd < 0) {
|
|
return -1;
|
|
} else if (!u2f_passthru_is_u2f_device(fd)) {
|
|
qemu_close(fd);
|
|
return -1;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
static int u2f_passthru_open_from_enumerate(struct udev *udev,
|
|
struct udev_enumerate *enumerate)
|
|
{
|
|
struct udev_list_entry *devices, *entry;
|
|
int ret, fd;
|
|
|
|
ret = udev_enumerate_scan_devices(enumerate);
|
|
if (ret < 0) {
|
|
return -1;
|
|
}
|
|
|
|
devices = udev_enumerate_get_list_entry(enumerate);
|
|
udev_list_entry_foreach(entry, devices) {
|
|
struct udev_device *device;
|
|
const char *syspath = udev_list_entry_get_name(entry);
|
|
|
|
if (syspath == NULL) {
|
|
continue;
|
|
}
|
|
|
|
device = udev_device_new_from_syspath(udev, syspath);
|
|
if (device == NULL) {
|
|
continue;
|
|
}
|
|
|
|
fd = u2f_passthru_open_from_device(device);
|
|
udev_device_unref(device);
|
|
if (fd >= 0) {
|
|
return fd;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int u2f_passthru_open_from_scan(void)
|
|
{
|
|
struct udev *udev;
|
|
struct udev_enumerate *enumerate;
|
|
int ret, fd = -1;
|
|
|
|
udev = udev_new();
|
|
if (udev == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
enumerate = udev_enumerate_new(udev);
|
|
if (enumerate == NULL) {
|
|
udev_unref(udev);
|
|
return -1;
|
|
}
|
|
|
|
ret = udev_enumerate_add_match_subsystem(enumerate, "hidraw");
|
|
if (ret >= 0) {
|
|
fd = u2f_passthru_open_from_enumerate(udev, enumerate);
|
|
}
|
|
|
|
udev_enumerate_unref(enumerate);
|
|
udev_unref(udev);
|
|
|
|
return fd;
|
|
}
|
|
#endif
|
|
|
|
static void u2f_passthru_unrealize(U2FKeyState *base)
|
|
{
|
|
U2FPassthruState *key = PASSTHRU_U2F_KEY(base);
|
|
|
|
u2f_passthru_reset(key);
|
|
qemu_close(key->hidraw_fd);
|
|
}
|
|
|
|
static void u2f_passthru_realize(U2FKeyState *base, Error **errp)
|
|
{
|
|
U2FPassthruState *key = PASSTHRU_U2F_KEY(base);
|
|
int fd;
|
|
|
|
if (key->hidraw == NULL) {
|
|
#ifdef CONFIG_LIBUDEV
|
|
fd = u2f_passthru_open_from_scan();
|
|
if (fd < 0) {
|
|
error_setg(errp, "%s: Failed to find a U2F USB device",
|
|
TYPE_U2F_PASSTHRU);
|
|
return;
|
|
}
|
|
#else
|
|
error_setg(errp, "%s: Missing hidraw", TYPE_U2F_PASSTHRU);
|
|
return;
|
|
#endif
|
|
} else {
|
|
fd = qemu_open_old(key->hidraw, O_RDWR);
|
|
if (fd < 0) {
|
|
error_setg(errp, "%s: Failed to open %s", TYPE_U2F_PASSTHRU,
|
|
key->hidraw);
|
|
return;
|
|
}
|
|
|
|
if (!u2f_passthru_is_u2f_device(fd)) {
|
|
qemu_close(fd);
|
|
error_setg(errp, "%s: Passed hidraw does not represent "
|
|
"a U2F HID device", TYPE_U2F_PASSTHRU);
|
|
return;
|
|
}
|
|
}
|
|
key->hidraw_fd = fd;
|
|
u2f_passthru_reset(key);
|
|
}
|
|
|
|
static int u2f_passthru_post_load(void *opaque, int version_id)
|
|
{
|
|
U2FPassthruState *key = opaque;
|
|
u2f_passthru_reset(key);
|
|
return 0;
|
|
}
|
|
|
|
static const VMStateDescription u2f_passthru_vmstate = {
|
|
.name = "u2f-key-passthru",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.post_load = u2f_passthru_post_load,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_U2F_KEY(base, U2FPassthruState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static Property u2f_passthru_properties[] = {
|
|
DEFINE_PROP_STRING("hidraw", U2FPassthruState, hidraw),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void u2f_passthru_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
U2FKeyClass *kc = U2F_KEY_CLASS(klass);
|
|
|
|
kc->realize = u2f_passthru_realize;
|
|
kc->unrealize = u2f_passthru_unrealize;
|
|
kc->recv_from_guest = u2f_passthru_recv_from_guest;
|
|
dc->desc = "QEMU U2F passthrough key";
|
|
dc->vmsd = &u2f_passthru_vmstate;
|
|
device_class_set_props(dc, u2f_passthru_properties);
|
|
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
|
}
|
|
|
|
static const TypeInfo u2f_key_passthru_info = {
|
|
.name = TYPE_U2F_PASSTHRU,
|
|
.parent = TYPE_U2F_KEY,
|
|
.instance_size = sizeof(U2FPassthruState),
|
|
.class_init = u2f_passthru_class_init
|
|
};
|
|
|
|
static void u2f_key_passthru_register_types(void)
|
|
{
|
|
type_register_static(&u2f_key_passthru_info);
|
|
}
|
|
|
|
type_init(u2f_key_passthru_register_types)
|