qemu-e2k/hw/usb/ccid-card-emulated.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

624 lines
19 KiB
C
Raw Normal View History

/*
* CCID Card Device. Emulated card.
*
* Copyright (c) 2011 Red Hat.
* Written by Alon Levy.
*
* This code is licensed under the GNU LGPL, version 2 or later.
*/
/*
* It can be used to provide access to the local hardware in a non exclusive
* way, or it can use certificates. It requires the usb-ccid bus.
*
* Usage 1: standard, mirror hardware reader+card:
* qemu .. -usb -device usb-ccid -device ccid-card-emulated
*
* Usage 2: use certificates, no hardware required
* one time: create the certificates:
* for i in 1 2 3; do
* certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i
* done
* qemu .. -usb -device usb-ccid \
* -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3
*
* If you use a non default db for the certificates you can specify it using
* the db parameter.
*/
#include "qemu/osdep.h"
#include <libcacard.h>
#include "qemu/thread.h"
#include "qemu/lockable.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
#include "ccid.h"
#include "hw/qdev-properties.h"
#include "qapi/error.h"
#include "qom/object.h"
#define DPRINTF(card, lvl, fmt, ...) \
do {\
if (lvl <= card->debug) {\
printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__);\
} \
} while (0)
#define TYPE_EMULATED_CCID "ccid-card-emulated"
typedef struct EmulatedState EmulatedState;
DECLARE_INSTANCE_CHECKER(EmulatedState, EMULATED_CCID_CARD,
TYPE_EMULATED_CCID)
#define BACKEND_NSS_EMULATED_NAME "nss-emulated"
#define BACKEND_CERTIFICATES_NAME "certificates"
enum {
BACKEND_NSS_EMULATED = 1,
BACKEND_CERTIFICATES
};
#define DEFAULT_BACKEND BACKEND_NSS_EMULATED
enum {
EMUL_READER_INSERT = 0,
EMUL_READER_REMOVE,
EMUL_CARD_INSERT,
EMUL_CARD_REMOVE,
EMUL_GUEST_APDU,
EMUL_RESPONSE_APDU,
EMUL_ERROR,
};
static const char *emul_event_to_string(uint32_t emul_event)
{
switch (emul_event) {
case EMUL_READER_INSERT:
return "EMUL_READER_INSERT";
case EMUL_READER_REMOVE:
return "EMUL_READER_REMOVE";
case EMUL_CARD_INSERT:
return "EMUL_CARD_INSERT";
case EMUL_CARD_REMOVE:
return "EMUL_CARD_REMOVE";
case EMUL_GUEST_APDU:
return "EMUL_GUEST_APDU";
case EMUL_RESPONSE_APDU:
return "EMUL_RESPONSE_APDU";
case EMUL_ERROR:
return "EMUL_ERROR";
}
return "UNKNOWN";
}
typedef struct EmulEvent {
QSIMPLEQ_ENTRY(EmulEvent) entry;
union {
struct {
uint32_t type;
} gen;
struct {
uint32_t type;
uint64_t code;
} error;
struct {
uint32_t type;
uint32_t len;
uint8_t data[];
} data;
} p;
} EmulEvent;
#define MAX_ATR_SIZE 40
struct EmulatedState {
CCIDCardState base;
uint8_t debug;
char *backend_str;
uint32_t backend;
char *cert1;
char *cert2;
char *cert3;
char *db;
uint8_t atr[MAX_ATR_SIZE];
uint8_t atr_length;
QSIMPLEQ_HEAD(, EmulEvent) event_list;
QemuMutex event_list_mutex;
QemuThread event_thread_id;
VReader *reader;
QSIMPLEQ_HEAD(, EmulEvent) guest_apdu_list;
QemuMutex vreader_mutex; /* and guest_apdu_list mutex */
QemuMutex handle_apdu_mutex;
QemuCond handle_apdu_cond;
EventNotifier notifier;
int quit_apdu_thread;
QemuThread apdu_thread_id;
};
static void emulated_apdu_from_guest(CCIDCardState *base,
const uint8_t *apdu, uint32_t len)
{
EmulatedState *card = EMULATED_CCID_CARD(base);
EmulEvent *event = g_malloc(sizeof(EmulEvent) + len);
assert(event);
event->p.data.type = EMUL_GUEST_APDU;
event->p.data.len = len;
memcpy(event->p.data.data, apdu, len);
qemu_mutex_lock(&card->vreader_mutex);
QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry);
qemu_mutex_unlock(&card->vreader_mutex);
qemu_mutex_lock(&card->handle_apdu_mutex);
qemu_cond_signal(&card->handle_apdu_cond);
qemu_mutex_unlock(&card->handle_apdu_mutex);
}
static const uint8_t *emulated_get_atr(CCIDCardState *base, uint32_t *len)
{
EmulatedState *card = EMULATED_CCID_CARD(base);
*len = card->atr_length;
return card->atr;
}
static void emulated_push_event(EmulatedState *card, EmulEvent *event)
{
qemu_mutex_lock(&card->event_list_mutex);
QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry);
qemu_mutex_unlock(&card->event_list_mutex);
event_notifier_set(&card->notifier);
}
static void emulated_push_type(EmulatedState *card, uint32_t type)
{
EmulEvent *event = g_new(EmulEvent, 1);
assert(event);
event->p.gen.type = type;
emulated_push_event(card, event);
}
static void emulated_push_error(EmulatedState *card, uint64_t code)
{
EmulEvent *event = g_new(EmulEvent, 1);
assert(event);
event->p.error.type = EMUL_ERROR;
event->p.error.code = code;
emulated_push_event(card, event);
}
static void emulated_push_data_type(EmulatedState *card, uint32_t type,
const uint8_t *data, uint32_t len)
{
EmulEvent *event = (EmulEvent *)g_malloc(sizeof(EmulEvent) + len);
assert(event);
event->p.data.type = type;
event->p.data.len = len;
memcpy(event->p.data.data, data, len);
emulated_push_event(card, event);
}
static void emulated_push_reader_insert(EmulatedState *card)
{
emulated_push_type(card, EMUL_READER_INSERT);
}
static void emulated_push_reader_remove(EmulatedState *card)
{
emulated_push_type(card, EMUL_READER_REMOVE);
}
static void emulated_push_card_insert(EmulatedState *card,
const uint8_t *atr, uint32_t len)
{
emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len);
}
static void emulated_push_card_remove(EmulatedState *card)
{
emulated_push_type(card, EMUL_CARD_REMOVE);
}
static void emulated_push_response_apdu(EmulatedState *card,
const uint8_t *apdu, uint32_t len)
{
emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len);
}
#define APDU_BUF_SIZE 270
static void *handle_apdu_thread(void* arg)
{
EmulatedState *card = arg;
uint8_t recv_data[APDU_BUF_SIZE];
int recv_len;
VReaderStatus reader_status;
EmulEvent *event;
while (1) {
qemu_mutex_lock(&card->handle_apdu_mutex);
qemu_cond_wait(&card->handle_apdu_cond, &card->handle_apdu_mutex);
qemu_mutex_unlock(&card->handle_apdu_mutex);
if (card->quit_apdu_thread) {
card->quit_apdu_thread = 0; /* debugging */
break;
}
WITH_QEMU_LOCK_GUARD(&card->vreader_mutex) {
while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) {
event = QSIMPLEQ_FIRST(&card->guest_apdu_list);
assert(event != NULL);
QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry);
if (event->p.data.type != EMUL_GUEST_APDU) {
DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n");
g_free(event);
continue;
}
if (card->reader == NULL) {
DPRINTF(card, 1, "reader is NULL\n");
g_free(event);
continue;
}
recv_len = sizeof(recv_data);
reader_status = vreader_xfr_bytes(card->reader,
event->p.data.data, event->p.data.len,
recv_data, &recv_len);
DPRINTF(card, 2, "got back apdu of length %d\n", recv_len);
if (reader_status == VREADER_OK) {
emulated_push_response_apdu(card, recv_data, recv_len);
} else {
emulated_push_error(card, reader_status);
}
g_free(event);
}
}
}
return NULL;
}
static void *event_thread(void *arg)
{
int atr_len = MAX_ATR_SIZE;
uint8_t atr[MAX_ATR_SIZE];
VEvent *event = NULL;
EmulatedState *card = arg;
while (1) {
const char *reader_name;
event = vevent_wait_next_vevent();
if (event == NULL || event->type == VEVENT_LAST) {
break;
}
if (event->type != VEVENT_READER_INSERT) {
if (card->reader == NULL && event->reader != NULL) {
/* Happens after device_add followed by card remove or insert.
* XXX: create synthetic add_reader events if vcard_emul_init
* already called, which happens if device_del and device_add
* are called */
card->reader = vreader_reference(event->reader);
} else {
if (event->reader != card->reader) {
fprintf(stderr,
"ERROR: wrong reader: quitting event_thread\n");
break;
}
}
}
switch (event->type) {
case VEVENT_READER_INSERT:
/* TODO: take a specific reader. i.e. track which reader
* we are seeing here, check it is the one we want (the first,
* or by a particular name), and ignore if we don't want it.
*/
reader_name = vreader_get_name(event->reader);
if (card->reader != NULL) {
DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n",
vreader_get_name(card->reader), reader_name);
qemu_mutex_lock(&card->vreader_mutex);
vreader_free(card->reader);
qemu_mutex_unlock(&card->vreader_mutex);
emulated_push_reader_remove(card);
}
qemu_mutex_lock(&card->vreader_mutex);
DPRINTF(card, 2, "READER INSERT %s\n", reader_name);
card->reader = vreader_reference(event->reader);
qemu_mutex_unlock(&card->vreader_mutex);
emulated_push_reader_insert(card);
break;
case VEVENT_READER_REMOVE:
DPRINTF(card, 2, " READER REMOVE: %s\n",
vreader_get_name(event->reader));
qemu_mutex_lock(&card->vreader_mutex);
vreader_free(card->reader);
card->reader = NULL;
qemu_mutex_unlock(&card->vreader_mutex);
emulated_push_reader_remove(card);
break;
case VEVENT_CARD_INSERT:
/* get the ATR (intended as a response to a power on from the
* reader */
atr_len = MAX_ATR_SIZE;
vreader_power_on(event->reader, atr, &atr_len);
card->atr_length = (uint8_t)atr_len;
DPRINTF(card, 2, " CARD INSERT\n");
emulated_push_card_insert(card, atr, atr_len);
break;
case VEVENT_CARD_REMOVE:
DPRINTF(card, 2, " CARD REMOVE\n");
emulated_push_card_remove(card);
break;
case VEVENT_LAST: /* quit */
vevent_delete(event);
return NULL;
default:
break;
}
vevent_delete(event);
}
return NULL;
}
static void card_event_handler(EventNotifier *notifier)
{
EmulatedState *card = container_of(notifier, EmulatedState, notifier);
EmulEvent *event, *next;
event_notifier_test_and_clear(&card->notifier);
QEMU_LOCK_GUARD(&card->event_list_mutex);
QSIMPLEQ_FOREACH_SAFE(event, &card->event_list, entry, next) {
DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type));
switch (event->p.gen.type) {
case EMUL_RESPONSE_APDU:
ccid_card_send_apdu_to_guest(&card->base, event->p.data.data,
event->p.data.len);
break;
case EMUL_READER_INSERT:
ccid_card_ccid_attach(&card->base);
break;
case EMUL_READER_REMOVE:
ccid_card_ccid_detach(&card->base);
break;
case EMUL_CARD_INSERT:
assert(event->p.data.len <= MAX_ATR_SIZE);
card->atr_length = event->p.data.len;
memcpy(card->atr, event->p.data.data, card->atr_length);
ccid_card_card_inserted(&card->base);
break;
case EMUL_CARD_REMOVE:
ccid_card_card_removed(&card->base);
break;
case EMUL_ERROR:
ccid_card_card_error(&card->base, event->p.error.code);
break;
default:
DPRINTF(card, 2, "unexpected event\n");
break;
}
g_free(event);
}
QSIMPLEQ_INIT(&card->event_list);
}
static int init_event_notifier(EmulatedState *card, Error **errp)
{
if (event_notifier_init(&card->notifier, false) < 0) {
error_setg(errp, "ccid-card-emul: event notifier creation failed");
return -1;
}
event_notifier_set_handler(&card->notifier, card_event_handler);
return 0;
}
static void clean_event_notifier(EmulatedState *card)
{
event_notifier_set_handler(&card->notifier, NULL);
event_notifier_cleanup(&card->notifier);
}
#define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb"
#define CERTIFICATES_ARGS_TEMPLATE\
"db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)"
static int wrap_vcard_emul_init(VCardEmulOptions *options)
{
static int called;
static int options_was_null;
if (called) {
if ((options == NULL) != options_was_null) {
printf("%s: warning: running emulated with certificates"
" and emulated side by side is not supported\n",
__func__);
return VCARD_EMUL_FAIL;
}
vcard_emul_replay_insertion_events();
return VCARD_EMUL_OK;
}
options_was_null = (options == NULL);
called = 1;
return vcard_emul_init(options);
}
static int emulated_initialize_vcard_from_certificates(EmulatedState *card)
{
char emul_args[200];
VCardEmulOptions *options = NULL;
snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE,
card->db ? card->db : CERTIFICATES_DEFAULT_DB,
card->cert1, card->cert2, card->cert3);
options = vcard_emul_options(emul_args);
if (options == NULL) {
printf("%s: warning: not using certificates due to"
" initialization error\n", __func__);
}
return wrap_vcard_emul_init(options);
}
typedef struct EnumTable {
const char *name;
uint32_t value;
} EnumTable;
static const EnumTable backend_enum_table[] = {
{BACKEND_NSS_EMULATED_NAME, BACKEND_NSS_EMULATED},
{BACKEND_CERTIFICATES_NAME, BACKEND_CERTIFICATES},
{NULL, 0},
};
static uint32_t parse_enumeration(char *str,
const EnumTable *table, uint32_t not_found_value)
{
uint32_t ret = not_found_value;
if (str == NULL)
return 0;
while (table->name != NULL) {
if (strcmp(table->name, str) == 0) {
ret = table->value;
break;
}
table++;
}
return ret;
}
static void emulated_realize(CCIDCardState *base, Error **errp)
{
EmulatedState *card = EMULATED_CCID_CARD(base);
VCardEmulError ret;
const EnumTable *ptable;
QSIMPLEQ_INIT(&card->event_list);
QSIMPLEQ_INIT(&card->guest_apdu_list);
qemu_mutex_init(&card->event_list_mutex);
qemu_mutex_init(&card->vreader_mutex);
qemu_mutex_init(&card->handle_apdu_mutex);
qemu_cond_init(&card->handle_apdu_cond);
card->reader = NULL;
card->quit_apdu_thread = 0;
if (init_event_notifier(card, errp) < 0) {
goto out1;
}
card->backend = 0;
if (card->backend_str) {
card->backend = parse_enumeration(card->backend_str,
backend_enum_table, 0);
}
if (card->backend == 0) {
error_setg(errp, "backend must be one of:");
for (ptable = backend_enum_table; ptable->name != NULL; ++ptable) {
error_append_hint(errp, "%s\n", ptable->name);
}
goto out2;
}
/* TODO: a passthru backened that works on local machine. third card type?*/
if (card->backend == BACKEND_CERTIFICATES) {
if (card->cert1 != NULL && card->cert2 != NULL && card->cert3 != NULL) {
ret = emulated_initialize_vcard_from_certificates(card);
} else {
error_setg(errp, "%s: you must provide all three certs for"
" certificates backend", TYPE_EMULATED_CCID);
goto out2;
}
} else {
if (card->backend != BACKEND_NSS_EMULATED) {
error_setg(errp, "%s: bad backend specified. The options are:%s"
" (default), %s.", TYPE_EMULATED_CCID,
BACKEND_NSS_EMULATED_NAME, BACKEND_CERTIFICATES_NAME);
goto out2;
}
if (card->cert1 != NULL || card->cert2 != NULL || card->cert3 != NULL) {
error_setg(errp, "%s: unexpected cert parameters to nss emulated "
"backend", TYPE_EMULATED_CCID);
goto out2;
}
/* default to mirroring the local hardware readers */
ret = wrap_vcard_emul_init(NULL);
}
if (ret != VCARD_EMUL_OK) {
error_setg(errp, "%s: failed to initialize vcard", TYPE_EMULATED_CCID);
goto out2;
}
qemu_thread_create(&card->event_thread_id, "ccid/event", event_thread,
card, QEMU_THREAD_JOINABLE);
qemu_thread_create(&card->apdu_thread_id, "ccid/apdu", handle_apdu_thread,
card, QEMU_THREAD_JOINABLE);
return;
out2:
clean_event_notifier(card);
out1:
qemu_cond_destroy(&card->handle_apdu_cond);
qemu_mutex_destroy(&card->handle_apdu_mutex);
qemu_mutex_destroy(&card->vreader_mutex);
qemu_mutex_destroy(&card->event_list_mutex);
}
qdev: Unrealize must not fail Devices may have component devices and buses. Device realization may fail. Realization is recursive: a device's realize() method realizes its components, and device_set_realized() realizes its buses (which should in turn realize the devices on that bus, except bus_set_realized() doesn't implement that, yet). When realization of a component or bus fails, we need to roll back: unrealize everything we realized so far. If any of these unrealizes failed, the device would be left in an inconsistent state. Must not happen. device_set_realized() lets it happen: it ignores errors in the roll back code starting at label child_realize_fail. Since realization is recursive, unrealization must be recursive, too. But how could a partly failed unrealize be rolled back? We'd have to re-realize, which can fail. This design is fundamentally broken. device_set_realized() does not roll back at all. Instead, it keeps unrealizing, ignoring further errors. It can screw up even for a device with no buses: if the lone dc->unrealize() fails, it still unregisters vmstate, and calls listeners' unrealize() callback. bus_set_realized() does not roll back either. Instead, it stops unrealizing. Fortunately, no unrealize method can fail, as we'll see below. To fix the design error, drop parameter @errp from all the unrealize methods. Any unrealize method that uses @errp now needs an update. This leads us to unrealize() methods that can fail. Merely passing it to another unrealize method cannot cause failure, though. Here are the ones that do other things with @errp: * virtio_serial_device_unrealize() Fails when qbus_set_hotplug_handler() fails, but still does all the other work. On failure, the device would stay realized with its resources completely gone. Oops. Can't happen, because qbus_set_hotplug_handler() can't actually fail here. Pass &error_abort to qbus_set_hotplug_handler() instead. * hw/ppc/spapr_drc.c's unrealize() Fails when object_property_del() fails, but all the other work is already done. On failure, the device would stay realized with its vmstate registration gone. Oops. Can't happen, because object_property_del() can't actually fail here. Pass &error_abort to object_property_del() instead. * spapr_phb_unrealize() Fails and bails out when remove_drcs() fails, but other work is already done. On failure, the device would stay realized with some of its resources gone. Oops. remove_drcs() fails only when chassis_from_bus()'s object_property_get_uint() fails, and it can't here. Pass &error_abort to remove_drcs() instead. Therefore, no unrealize method can fail before this patch. device_set_realized()'s recursive unrealization via bus uses object_property_set_bool(). Can't drop @errp there, so pass &error_abort. We similarly unrealize with object_property_set_bool() elsewhere, always ignoring errors. Pass &error_abort instead. Several unrealize methods no longer handle errors from other unrealize methods: virtio_9p_device_unrealize(), virtio_input_device_unrealize(), scsi_qdev_unrealize(), ... Much of the deleted error handling looks wrong anyway. One unrealize methods no longer ignore such errors: usb_ehci_pci_exit(). Several realize methods no longer ignore errors when rolling back: v9fs_device_realize_common(), pci_qdev_unrealize(), spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(), virtio_device_realize(). Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Reviewed-by: Paolo Bonzini <pbonzini@redhat.com> Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-05 17:29:24 +02:00
static void emulated_unrealize(CCIDCardState *base)
{
EmulatedState *card = EMULATED_CCID_CARD(base);
VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL);
vevent_queue_vevent(vevent); /* stop vevent thread */
qemu_thread_join(&card->event_thread_id);
card->quit_apdu_thread = 1; /* stop handle_apdu thread */
qemu_cond_signal(&card->handle_apdu_cond);
qemu_thread_join(&card->apdu_thread_id);
clean_event_notifier(card);
/* threads exited, can destroy all condvars/mutexes */
qemu_cond_destroy(&card->handle_apdu_cond);
qemu_mutex_destroy(&card->handle_apdu_mutex);
qemu_mutex_destroy(&card->vreader_mutex);
qemu_mutex_destroy(&card->event_list_mutex);
}
static Property emulated_card_properties[] = {
DEFINE_PROP_STRING("backend", EmulatedState, backend_str),
DEFINE_PROP_STRING("cert1", EmulatedState, cert1),
DEFINE_PROP_STRING("cert2", EmulatedState, cert2),
DEFINE_PROP_STRING("cert3", EmulatedState, cert3),
DEFINE_PROP_STRING("db", EmulatedState, db),
DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0),
DEFINE_PROP_END_OF_LIST(),
};
static void emulated_class_initfn(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
CCIDCardClass *cc = CCID_CARD_CLASS(klass);
cc->realize = emulated_realize;
cc->unrealize = emulated_unrealize;
cc->get_atr = emulated_get_atr;
cc->apdu_from_guest = emulated_apdu_from_guest;
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
dc->desc = "emulated smartcard";
device_class_set_props(dc, emulated_card_properties);
}
static const TypeInfo emulated_card_info = {
.name = TYPE_EMULATED_CCID,
.parent = TYPE_CCID_CARD,
.instance_size = sizeof(EmulatedState),
.class_init = emulated_class_initfn,
};
module_obj(TYPE_EMULATED_CCID);
module_kconfig(USB);
static void ccid_card_emulated_register_types(void)
{
type_register_static(&emulated_card_info);
}
type_init(ccid_card_emulated_register_types)