qemu-e2k/hw/usb/ccid-card-emulated.c
Markus Armbruster b69c3c21a5 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-15 07:08:14 +02:00

622 lines
19 KiB
C

/*
* 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/main-loop.h"
#include "qemu/module.h"
#include "ccid.h"
#include "hw/qdev-properties.h"
#include "qapi/error.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"
#define EMULATED_CCID_CARD(obj) \
OBJECT_CHECK(EmulatedState, (obj), 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
typedef struct EmulatedState EmulatedState;
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 = (EmulEvent *)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;
}
qemu_mutex_lock(&card->vreader_mutex);
while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) {
event = QSIMPLEQ_FIRST(&card->guest_apdu_list);
assert((unsigned long)event > 1000);
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);
}
qemu_mutex_unlock(&card->vreader_mutex);
}
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: quiting 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;
break;
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_mutex_lock(&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);
qemu_mutex_unlock(&card->event_list_mutex);
}
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);
}
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,
};
static void ccid_card_emulated_register_types(void)
{
type_register_static(&emulated_card_info);
}
type_init(ccid_card_emulated_register_types)