/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2019 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see .
*/
/**
* Test Smartcard device and channel
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "test-display-base.h"
#include "test-glib-compat.h"
#include "reds.h"
#include "vmc-emu.h"
#include "red-client.h"
#include "net-utils.h"
#include "win-alarm.h"
static SpiceCoreInterface *core;
static Test *test;
static VmcEmu *vmc;
typedef int TestFixture;
static int client_socket = -1;
// buffer when data from channel are stored
static SpiceBuffer channel_buf;
// expected buffer in channel
static SpiceBuffer channel_expected;
// expected buffer in device
static SpiceBuffer device_expected;
static SpiceWatch *watch;
static void next_test(void);
static void test_smartcard_setup(TestFixture *fixture, gconstpointer user_data)
{
g_assert_null(core);
g_assert_null(test);
g_assert_null(vmc);
core = basic_event_loop_init();
g_assert_nonnull(core);
test = test_new(core);
g_assert_nonnull(test);
vmc = vmc_emu_new("smartcard", NULL);
g_assert_nonnull(vmc);
}
static void test_smartcard_teardown(TestFixture *fixture, gconstpointer user_data)
{
g_assert_nonnull(core);
g_assert_nonnull(test);
g_assert_nonnull(vmc);
vmc_emu_destroy(vmc);
vmc = NULL;
test_destroy(test);
test = NULL;
basic_event_loop_destroy();
core = NULL;
}
static RedStream *create_dummy_stream(SpiceServer *server, int *p_socket)
{
int sv[2];
g_assert_cmpint(socketpair(AF_LOCAL, SOCK_STREAM, 0, sv), ==, 0);
if (p_socket) {
*p_socket = sv[1];
}
red_socket_set_non_blocking(sv[0], true);
red_socket_set_non_blocking(sv[1], true);
RedStream * stream = red_stream_new(server, sv[0]);
g_assert_nonnull(stream);
return stream;
}
static void send_ack_sync(int socket, uint32_t generation)
{
struct {
uint16_t dummy;
uint16_t type;
uint32_t len;
uint32_t generation;
} msg;
SPICE_VERIFY(sizeof(msg) == 12);
msg.type = GUINT16_TO_LE(SPICE_MSGC_ACK_SYNC);
msg.len = GUINT32_TO_LE(sizeof(generation));
msg.generation = GUINT32_TO_LE(generation);
g_assert_cmpint(socket_write(socket, reinterpret_cast(&msg) + 2, 10), ==, 10);
}
static void send_data(int socket, uint32_t type, uint32_t reader_id)
{
struct {
uint16_t dummy;
uint16_t type;
uint32_t len;
VSCMsgHeader vheader;
char data[6];
} msg;
SPICE_VERIFY(sizeof(msg) == 8+12+8);
msg.type = GUINT16_TO_LE(SPICE_MSGC_SMARTCARD_DATA);
msg.len = GUINT32_TO_LE(sizeof(VSCMsgHeader)+6);
msg.vheader.type = GUINT32_TO_LE(type);
msg.vheader.reader_id = GUINT32_TO_LE(reader_id);
msg.vheader.length = GUINT32_TO_LE(6);
strcpy(msg.data, "hello");
g_assert_cmpint(socket_write(socket, reinterpret_cast(&msg) + 2,
sizeof(msg) - 4), ==, sizeof(msg) - 4);
}
static void check_data(VmcEmu *vmc_emu)
{
g_assert_cmpint(device_expected.offset, !=, 0);
if (vmc_emu->write_pos < device_expected.offset) {
return;
}
g_assert_cmpint(vmc_emu->write_pos, ==, device_expected.offset);
g_assert_true(memcmp(vmc_emu->write_buf, device_expected.buffer, device_expected.offset) == 0);
vmc_emu->write_pos = 0;
next_test();
}
static void data_from_channel(int fd, int event, void *opaque)
{
uint8_t buf[128];
ssize_t ret = socket_read(fd, buf, sizeof(buf));
if (ret <= 0) {
g_assert(ret == 0 || errno == EAGAIN || errno == EINTR);
if (ret == 0) {
g_warning("TEST: connection closed");
core->watch_remove(watch);
watch = NULL;
next_test();
}
return;
}
spice_buffer_append(&channel_buf, buf, ret);
g_assert_cmpint(channel_expected.offset, !=, 0);
if (channel_buf.offset < channel_expected.offset) {
return;
}
g_assert_true(memcmp(channel_buf.buffer, channel_expected.buffer, channel_expected.offset) == 0);
spice_buffer_remove(&channel_buf, channel_expected.offset);
next_test();
}
static void next_test(void)
{
static int test_num;
test_num++;
printf("Executing subtest %d\n", test_num);
spice_buffer_reset(&channel_expected);
spice_buffer_reset(&device_expected);
switch (test_num) {
// First test, send some message to channel expecting a reply
// for each message we are sending
case 1: {
static const char expected_buf[] =
// forwarded ReaderAdd message, note that payload is stripped
"\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00"
// forwarded APDU message
"\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x06\x68\x65\x6c\x6c\x6f\x00"
"\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00";
spice_buffer_append(&device_expected, expected_buf, sizeof(expected_buf) - 1);
send_data(client_socket, VSC_ReaderAdd, 0);
send_data(client_socket, VSC_APDU, 0);
send_data(client_socket, VSC_ReaderRemove, 0);
} break;
// Second test, send an init and remove a reader that is not present,
// we expect an error for the removal (the Init is ignored)
case 2: {
static const char expected_buf[] =
// forwarded Error message
"\x65\x00\x10\x00\x00\x00"
"\x02\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00";
spice_buffer_append(&channel_expected, expected_buf, sizeof(expected_buf) - 1);
// Init message, ignored
send_data(client_socket, VSC_Init, 0);
// remove again, this will trigger an error
send_data(client_socket, VSC_ReaderRemove, 0);
} break;
// Third test, APDU messages from device are forwarded to the channel.
// We split the header and payload of the first message to check device code can handle it.
// The second message is send inside a block with the end of the first to trigger
// an hard path in the device code
case 3: {
static const char expected_buf[] =
// forwarded APDU message
"\x65\x00\x12\x00\x00\x00"
"\x07\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00" "foobaz"
"\x65\x00\x12\x00\x00\x00"
"\x07\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00" "foobar";
spice_buffer_append(&channel_expected, expected_buf, sizeof(expected_buf) - 1);
vmc_emu_reset(vmc);
// data from device
uint8_t *p = vmc->message;
// add VSC_APDU message
memcpy(p, "\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x06" "foobaz", 18);
p += 18;
memcpy(p, "\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x06" "foobar", 18);
p += 18;
vmc_emu_add_read_till(vmc, vmc->message + 8);
vmc_emu_add_read_till(vmc, vmc->message + 14);
vmc_emu_add_read_till(vmc, p);
spice_server_char_device_wakeup(&vmc->instance);
} break;
// Fourth test, we should get back an error if client tried to remove
// a not existing reader
case 4: {
static const char expected_buf[] =
// forwarded Error message
"\x65\x00\x10\x00\x00\x00"
"\x02\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00";
spice_buffer_append(&channel_expected, expected_buf, sizeof(expected_buf) - 1);
// remove invalid, this will trigger an error
send_data(client_socket, VSC_ReaderRemove, 5);
} break;
// Fifth test, similar to previous but using an huge reader_id field to trigger
// possible buffer overflow
case 5: {
static const char expected_buf[] =
// forwarded Error message
"\x65\x00\x10\x00\x00\x00"
"\x02\x00\x00\x00\x05\x01\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00";
spice_buffer_append(&channel_expected, expected_buf, sizeof(expected_buf) - 1);
// remove invalid and huge, this will trigger an error, should not crash
send_data(client_socket, VSC_ReaderRemove, 261);
} break;
// Sixth test, send an invalid message from client, a log is triggered
// but channel continues to work
case 6: {
static const char expected_buf[] =
// forwarded APDU message
"\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x06\x68\x65\x6c\x6c\x6f\x00";
spice_buffer_append(&device_expected, expected_buf, sizeof(expected_buf) - 1);
g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"*ERROR: unexpected message on smartcard channel*");
// invalid message type, should log a warning
send_data(client_socket, 0xabcd, 0);
// APDU just to get an event
send_data(client_socket, VSC_APDU, 0);
} break;
// Seventh test, an Error message from device are forwarded to the channel.
// Note that the header is in big endian order while the error from device
// is in little endian order. This seems weird but it's correct with the
// current libcacard implementation which just send error as host order
case 7: {
g_test_assert_expected_messages();
static const char expected_buf[] =
// forwarded Error message
"\x65\x00\x10\x00\x00\x00"
"\x02\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x0a\x0b\x0c\x0d";
spice_buffer_append(&channel_expected, expected_buf, sizeof(expected_buf) - 1);
vmc_emu_reset(vmc);
// data from device
uint8_t *p = vmc->message;
// add Error message
memcpy(p, "\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x04\x0a\x0b\x0c\x0d", 16);
p += 16;
vmc_emu_add_read_till(vmc, p);
spice_server_char_device_wakeup(&vmc->instance);
} break;
// Eighth test, a message with invalid reader ID from device caused the channel
// to be closed
case 8: {
g_test_assert_expected_messages();
g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"*ERROR: received message for non existing reader*");
g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"*TEST: connection closed*");
send_data(client_socket, VSC_APDU, 0xabcd);
} break;
case 9:
g_test_assert_expected_messages();
basic_event_loop_quit();
break;
default:
abort();
}
}
static void test_smartcard(TestFixture *fixture, gconstpointer user_data)
{
SpiceServer *const server = test->server;
uint8_t *p = vmc->message;
spice_server_add_interface(server, &vmc->instance.base);
// add VSC_Init message
memcpy(p, "\x00\x00\x00\x01\x0a\x0b\x0c\x0d\x00\x00\x00\x00", 12);
p += 12;
vmc_emu_add_read_till(vmc, vmc->message + 2); // check header is decoded correctly when split
vmc_emu_add_read_till(vmc, p);
// find Smartcard channel to connect to
RedChannel *channel = reds_find_channel(server, SPICE_CHANNEL_SMARTCARD, 0);
g_assert_nonnull(channel);
// create dummy RedClient and MainChannelClient
RedChannelCapabilities caps;
memset(&caps, 0, sizeof(caps));
uint32_t common_caps = 1 << SPICE_COMMON_CAP_MINI_HEADER;
caps.num_common_caps = 1;
caps.common_caps = (uint32_t*) spice_memdup(&common_caps, sizeof(common_caps));
RedClient *client = red_client_new(server, FALSE);
g_assert_nonnull(client);
red::shared_ptr main_channel(main_channel_new(server));
g_assert(main_channel);
MainChannelClient *mcc;
mcc = main_channel_link(main_channel.get(), client, create_dummy_stream(server, NULL),
0, FALSE, &caps);
g_assert_nonnull(mcc);
// create our testing RedChannelClient
channel->connect(client, create_dummy_stream(server, &client_socket), FALSE, &caps);
red_channel_capabilities_reset(&caps);
// push data to device
spice_server_char_device_wakeup(&vmc->instance);
// push data into channel
send_ack_sync(client_socket, 1);
// check data are processed
watch = core->watch_add(client_socket, SPICE_WATCH_EVENT_READ, data_from_channel, NULL);
vmc->data_written_cb = check_data;
// start all test
alarm(10);
next_test();
basic_event_loop_mainloop();
alarm(0);
// cleanup
if (watch) {
core->watch_remove(watch);
}
client->destroy();
}
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add("/server/smartcard", TestFixture, NULL, test_smartcard_setup,
test_smartcard, test_smartcard_teardown);
return g_test_run();
}