The qemu_chr_fe_write method will return -1 on EAGAIN if the chardev backend write would block. Almost no callers of the qemu_chr_fe_write() method check the return value, instead blindly assuming data was successfully sent. In most cases this will lead to silent data loss on interactive consoles, but in some cases (eg RNG EGD) it'll just cause corruption of the protocol being spoken. We unfortunately can't fix the virtio-console code, due to a bug in the Linux guest drivers, which would cause the entire Linux kernel to hang if we delay processing of the incoming data in any way. Fixing this requires first fixing the guest driver to not hold spinlocks while writing to the hvc device backend. Fixes bug: https://bugs.launchpad.net/qemu/+bug/1586756 Signed-off-by: Daniel P. Berrange <berrange@redhat.com> Message-Id: <1473170165-540-4-git-send-email-berrange@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
420 lines
13 KiB
C
420 lines
13 KiB
C
/*
|
|
* CCID Passthru Card Device emulation
|
|
*
|
|
* Copyright (c) 2011 Red Hat.
|
|
* Written by Alon Levy.
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2.1 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "sysemu/char.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qemu/sockets.h"
|
|
#include "ccid.h"
|
|
#include "cacard/vscard_common.h"
|
|
|
|
#define DPRINTF(card, lvl, fmt, ...) \
|
|
do { \
|
|
if (lvl <= card->debug) { \
|
|
printf("ccid-card-passthru: " fmt , ## __VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define D_WARN 1
|
|
#define D_INFO 2
|
|
#define D_MORE_INFO 3
|
|
#define D_VERBOSE 4
|
|
|
|
/* TODO: do we still need this? */
|
|
static const uint8_t DEFAULT_ATR[] = {
|
|
/*
|
|
* From some example somewhere
|
|
* 0x3B, 0xB0, 0x18, 0x00, 0xD1, 0x81, 0x05, 0xB1, 0x40, 0x38, 0x1F, 0x03, 0x28
|
|
*/
|
|
|
|
/* From an Athena smart card */
|
|
0x3B, 0xD5, 0x18, 0xFF, 0x80, 0x91, 0xFE, 0x1F, 0xC3, 0x80, 0x73, 0xC8, 0x21,
|
|
0x13, 0x08
|
|
};
|
|
|
|
#define VSCARD_IN_SIZE 65536
|
|
|
|
/* maximum size of ATR - from 7816-3 */
|
|
#define MAX_ATR_SIZE 40
|
|
|
|
typedef struct PassthruState PassthruState;
|
|
|
|
struct PassthruState {
|
|
CCIDCardState base;
|
|
CharDriverState *cs;
|
|
uint8_t vscard_in_data[VSCARD_IN_SIZE];
|
|
uint32_t vscard_in_pos;
|
|
uint32_t vscard_in_hdr;
|
|
uint8_t atr[MAX_ATR_SIZE];
|
|
uint8_t atr_length;
|
|
uint8_t debug;
|
|
};
|
|
|
|
#define TYPE_CCID_PASSTHRU "ccid-card-passthru"
|
|
#define PASSTHRU_CCID_CARD(obj) \
|
|
OBJECT_CHECK(PassthruState, (obj), TYPE_CCID_PASSTHRU)
|
|
|
|
/*
|
|
* VSCard protocol over chardev
|
|
* This code should not depend on the card type.
|
|
*/
|
|
|
|
static void ccid_card_vscard_send_msg(PassthruState *s,
|
|
VSCMsgType type, uint32_t reader_id,
|
|
const uint8_t *payload, uint32_t length)
|
|
{
|
|
VSCMsgHeader scr_msg_header;
|
|
|
|
scr_msg_header.type = htonl(type);
|
|
scr_msg_header.reader_id = htonl(reader_id);
|
|
scr_msg_header.length = htonl(length);
|
|
/* XXX this blocks entire thread. Rewrite to use
|
|
* qemu_chr_fe_write and background I/O callbacks */
|
|
qemu_chr_fe_write_all(s->cs, (uint8_t *)&scr_msg_header,
|
|
sizeof(VSCMsgHeader));
|
|
qemu_chr_fe_write_all(s->cs, payload, length);
|
|
}
|
|
|
|
static void ccid_card_vscard_send_apdu(PassthruState *s,
|
|
const uint8_t *apdu, uint32_t length)
|
|
{
|
|
ccid_card_vscard_send_msg(
|
|
s, VSC_APDU, VSCARD_MINIMAL_READER_ID, apdu, length);
|
|
}
|
|
|
|
static void ccid_card_vscard_send_error(PassthruState *s,
|
|
uint32_t reader_id, VSCErrorCode code)
|
|
{
|
|
VSCMsgError msg = {.code = htonl(code)};
|
|
|
|
ccid_card_vscard_send_msg(
|
|
s, VSC_Error, reader_id, (uint8_t *)&msg, sizeof(msg));
|
|
}
|
|
|
|
static void ccid_card_vscard_send_init(PassthruState *s)
|
|
{
|
|
VSCMsgInit msg = {
|
|
.version = htonl(VSCARD_VERSION),
|
|
.magic = VSCARD_MAGIC,
|
|
.capabilities = {0}
|
|
};
|
|
|
|
ccid_card_vscard_send_msg(s, VSC_Init, VSCARD_UNDEFINED_READER_ID,
|
|
(uint8_t *)&msg, sizeof(msg));
|
|
}
|
|
|
|
static int ccid_card_vscard_can_read(void *opaque)
|
|
{
|
|
PassthruState *card = opaque;
|
|
|
|
return VSCARD_IN_SIZE >= card->vscard_in_pos ?
|
|
VSCARD_IN_SIZE - card->vscard_in_pos : 0;
|
|
}
|
|
|
|
static void ccid_card_vscard_handle_init(
|
|
PassthruState *card, VSCMsgHeader *hdr, VSCMsgInit *init)
|
|
{
|
|
uint32_t *capabilities;
|
|
int num_capabilities;
|
|
int i;
|
|
|
|
capabilities = init->capabilities;
|
|
num_capabilities =
|
|
1 + ((hdr->length - sizeof(VSCMsgInit)) / sizeof(uint32_t));
|
|
init->version = ntohl(init->version);
|
|
for (i = 0 ; i < num_capabilities; ++i) {
|
|
capabilities[i] = ntohl(capabilities[i]);
|
|
}
|
|
if (init->magic != VSCARD_MAGIC) {
|
|
error_report("wrong magic");
|
|
/* we can't disconnect the chardev */
|
|
}
|
|
if (init->version != VSCARD_VERSION) {
|
|
DPRINTF(card, D_WARN,
|
|
"got version %d, have %d", init->version, VSCARD_VERSION);
|
|
}
|
|
/* future handling of capabilities, none exist atm */
|
|
ccid_card_vscard_send_init(card);
|
|
}
|
|
|
|
static int check_atr(PassthruState *card, uint8_t *data, int len)
|
|
{
|
|
int historical_length, opt_bytes;
|
|
int td_count = 0;
|
|
int td;
|
|
|
|
if (len < 2) {
|
|
return 0;
|
|
}
|
|
historical_length = data[1] & 0xf;
|
|
opt_bytes = 0;
|
|
if (data[0] != 0x3b && data[0] != 0x3f) {
|
|
DPRINTF(card, D_WARN, "atr's T0 is 0x%X, not in {0x3b, 0x3f}\n",
|
|
data[0]);
|
|
return 0;
|
|
}
|
|
td_count = 0;
|
|
td = data[1] >> 4;
|
|
while (td && td_count < 2 && opt_bytes + historical_length + 2 < len) {
|
|
td_count++;
|
|
if (td & 0x1) {
|
|
opt_bytes++;
|
|
}
|
|
if (td & 0x2) {
|
|
opt_bytes++;
|
|
}
|
|
if (td & 0x4) {
|
|
opt_bytes++;
|
|
}
|
|
if (td & 0x8) {
|
|
opt_bytes++;
|
|
td = data[opt_bytes + 2] >> 4;
|
|
}
|
|
}
|
|
if (len < 2 + historical_length + opt_bytes) {
|
|
DPRINTF(card, D_WARN,
|
|
"atr too short: len %d, but historical_len %d, T1 0x%X\n",
|
|
len, historical_length, data[1]);
|
|
return 0;
|
|
}
|
|
if (len > 2 + historical_length + opt_bytes) {
|
|
DPRINTF(card, D_WARN,
|
|
"atr too long: len %d, but hist/opt %d/%d, T1 0x%X\n",
|
|
len, historical_length, opt_bytes, data[1]);
|
|
/* let it through */
|
|
}
|
|
DPRINTF(card, D_VERBOSE,
|
|
"atr passes check: %d total length, %d historical, %d optional\n",
|
|
len, historical_length, opt_bytes);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void ccid_card_vscard_handle_message(PassthruState *card,
|
|
VSCMsgHeader *scr_msg_header)
|
|
{
|
|
uint8_t *data = (uint8_t *)&scr_msg_header[1];
|
|
|
|
switch (scr_msg_header->type) {
|
|
case VSC_ATR:
|
|
DPRINTF(card, D_INFO, "VSC_ATR %d\n", scr_msg_header->length);
|
|
if (scr_msg_header->length > MAX_ATR_SIZE) {
|
|
error_report("ATR size exceeds spec, ignoring");
|
|
ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
|
|
VSC_GENERAL_ERROR);
|
|
break;
|
|
}
|
|
if (!check_atr(card, data, scr_msg_header->length)) {
|
|
error_report("ATR is inconsistent, ignoring");
|
|
ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
|
|
VSC_GENERAL_ERROR);
|
|
break;
|
|
}
|
|
memcpy(card->atr, data, scr_msg_header->length);
|
|
card->atr_length = scr_msg_header->length;
|
|
ccid_card_card_inserted(&card->base);
|
|
ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
|
|
VSC_SUCCESS);
|
|
break;
|
|
case VSC_APDU:
|
|
ccid_card_send_apdu_to_guest(
|
|
&card->base, data, scr_msg_header->length);
|
|
break;
|
|
case VSC_CardRemove:
|
|
DPRINTF(card, D_INFO, "VSC_CardRemove\n");
|
|
ccid_card_card_removed(&card->base);
|
|
ccid_card_vscard_send_error(card,
|
|
scr_msg_header->reader_id, VSC_SUCCESS);
|
|
break;
|
|
case VSC_Init:
|
|
ccid_card_vscard_handle_init(
|
|
card, scr_msg_header, (VSCMsgInit *)data);
|
|
break;
|
|
case VSC_Error:
|
|
ccid_card_card_error(&card->base, *(uint32_t *)data);
|
|
break;
|
|
case VSC_ReaderAdd:
|
|
if (ccid_card_ccid_attach(&card->base) < 0) {
|
|
ccid_card_vscard_send_error(card, VSCARD_UNDEFINED_READER_ID,
|
|
VSC_CANNOT_ADD_MORE_READERS);
|
|
} else {
|
|
ccid_card_vscard_send_error(card, VSCARD_MINIMAL_READER_ID,
|
|
VSC_SUCCESS);
|
|
}
|
|
break;
|
|
case VSC_ReaderRemove:
|
|
ccid_card_ccid_detach(&card->base);
|
|
ccid_card_vscard_send_error(card,
|
|
scr_msg_header->reader_id, VSC_SUCCESS);
|
|
break;
|
|
default:
|
|
printf("usb-ccid: chardev: unexpected message of type %X\n",
|
|
scr_msg_header->type);
|
|
ccid_card_vscard_send_error(card, scr_msg_header->reader_id,
|
|
VSC_GENERAL_ERROR);
|
|
}
|
|
}
|
|
|
|
static void ccid_card_vscard_drop_connection(PassthruState *card)
|
|
{
|
|
qemu_chr_delete(card->cs);
|
|
card->vscard_in_pos = card->vscard_in_hdr = 0;
|
|
}
|
|
|
|
static void ccid_card_vscard_read(void *opaque, const uint8_t *buf, int size)
|
|
{
|
|
PassthruState *card = opaque;
|
|
VSCMsgHeader *hdr;
|
|
|
|
if (card->vscard_in_pos + size > VSCARD_IN_SIZE) {
|
|
error_report(
|
|
"no room for data: pos %d + size %d > %d. dropping connection.",
|
|
card->vscard_in_pos, size, VSCARD_IN_SIZE);
|
|
ccid_card_vscard_drop_connection(card);
|
|
return;
|
|
}
|
|
assert(card->vscard_in_pos < VSCARD_IN_SIZE);
|
|
assert(card->vscard_in_hdr < VSCARD_IN_SIZE);
|
|
memcpy(card->vscard_in_data + card->vscard_in_pos, buf, size);
|
|
card->vscard_in_pos += size;
|
|
hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr);
|
|
|
|
while ((card->vscard_in_pos - card->vscard_in_hdr >= sizeof(VSCMsgHeader))
|
|
&&(card->vscard_in_pos - card->vscard_in_hdr >=
|
|
sizeof(VSCMsgHeader) + ntohl(hdr->length))) {
|
|
hdr->reader_id = ntohl(hdr->reader_id);
|
|
hdr->length = ntohl(hdr->length);
|
|
hdr->type = ntohl(hdr->type);
|
|
ccid_card_vscard_handle_message(card, hdr);
|
|
card->vscard_in_hdr += hdr->length + sizeof(VSCMsgHeader);
|
|
hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr);
|
|
}
|
|
if (card->vscard_in_hdr == card->vscard_in_pos) {
|
|
card->vscard_in_pos = card->vscard_in_hdr = 0;
|
|
}
|
|
}
|
|
|
|
static void ccid_card_vscard_event(void *opaque, int event)
|
|
{
|
|
PassthruState *card = opaque;
|
|
|
|
switch (event) {
|
|
case CHR_EVENT_BREAK:
|
|
card->vscard_in_pos = card->vscard_in_hdr = 0;
|
|
break;
|
|
case CHR_EVENT_FOCUS:
|
|
break;
|
|
case CHR_EVENT_OPENED:
|
|
DPRINTF(card, D_INFO, "%s: CHR_EVENT_OPENED\n", __func__);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* End VSCard handling */
|
|
|
|
static void passthru_apdu_from_guest(
|
|
CCIDCardState *base, const uint8_t *apdu, uint32_t len)
|
|
{
|
|
PassthruState *card = PASSTHRU_CCID_CARD(base);
|
|
|
|
if (!card->cs) {
|
|
printf("ccid-passthru: no chardev, discarding apdu length %d\n", len);
|
|
return;
|
|
}
|
|
ccid_card_vscard_send_apdu(card, apdu, len);
|
|
}
|
|
|
|
static const uint8_t *passthru_get_atr(CCIDCardState *base, uint32_t *len)
|
|
{
|
|
PassthruState *card = PASSTHRU_CCID_CARD(base);
|
|
|
|
*len = card->atr_length;
|
|
return card->atr;
|
|
}
|
|
|
|
static int passthru_initfn(CCIDCardState *base)
|
|
{
|
|
PassthruState *card = PASSTHRU_CCID_CARD(base);
|
|
|
|
card->vscard_in_pos = 0;
|
|
card->vscard_in_hdr = 0;
|
|
if (card->cs) {
|
|
DPRINTF(card, D_INFO, "initing chardev\n");
|
|
qemu_chr_add_handlers(card->cs,
|
|
ccid_card_vscard_can_read,
|
|
ccid_card_vscard_read,
|
|
ccid_card_vscard_event, card);
|
|
ccid_card_vscard_send_init(card);
|
|
} else {
|
|
error_report("missing chardev");
|
|
return -1;
|
|
}
|
|
card->debug = parse_debug_env("QEMU_CCID_PASSTHRU_DEBUG", D_VERBOSE,
|
|
card->debug);
|
|
assert(sizeof(DEFAULT_ATR) <= MAX_ATR_SIZE);
|
|
memcpy(card->atr, DEFAULT_ATR, sizeof(DEFAULT_ATR));
|
|
card->atr_length = sizeof(DEFAULT_ATR);
|
|
return 0;
|
|
}
|
|
|
|
static int passthru_exitfn(CCIDCardState *base)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static VMStateDescription passthru_vmstate = {
|
|
.name = "ccid-card-passthru",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_BUFFER(vscard_in_data, PassthruState),
|
|
VMSTATE_UINT32(vscard_in_pos, PassthruState),
|
|
VMSTATE_UINT32(vscard_in_hdr, PassthruState),
|
|
VMSTATE_BUFFER(atr, PassthruState),
|
|
VMSTATE_UINT8(atr_length, PassthruState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static Property passthru_card_properties[] = {
|
|
DEFINE_PROP_CHR("chardev", PassthruState, cs),
|
|
DEFINE_PROP_UINT8("debug", PassthruState, debug, 0),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void passthru_class_initfn(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
CCIDCardClass *cc = CCID_CARD_CLASS(klass);
|
|
|
|
cc->initfn = passthru_initfn;
|
|
cc->exitfn = passthru_exitfn;
|
|
cc->get_atr = passthru_get_atr;
|
|
cc->apdu_from_guest = passthru_apdu_from_guest;
|
|
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
|
|
dc->desc = "passthrough smartcard";
|
|
dc->vmsd = &passthru_vmstate;
|
|
dc->props = passthru_card_properties;
|
|
}
|
|
|
|
static const TypeInfo passthru_card_info = {
|
|
.name = TYPE_CCID_PASSTHRU,
|
|
.parent = TYPE_CCID_CARD,
|
|
.instance_size = sizeof(PassthruState),
|
|
.class_init = passthru_class_initfn,
|
|
};
|
|
|
|
static void ccid_card_passthru_register_types(void)
|
|
{
|
|
type_register_static(&passthru_card_info);
|
|
}
|
|
|
|
type_init(ccid_card_passthru_register_types)
|