test-qxl-parsing.c 10.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
   Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
*/

19 20
/* Do some tests on memory parsing
 */
21 22 23
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
24 25 26 27 28

#undef NDEBUG
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
29
#include <unistd.h>
30 31 32 33

#include <spice/macros.h>
#include "memslot.h"
#include "red-parse-qxl.h"
34
#include "test-glib-compat.h"
35

36 37
static QXLPHYSICAL
to_physical(const void *ptr)
38
{
39
    return (uintptr_t) ptr;
40 41
}

42 43
static void *
from_physical(QXLPHYSICAL physical)
44
{
45
    return (void *)(uintptr_t) physical;
46 47
}

48 49 50
static void*
create_chunk(size_t prefix, uint32_t size, QXLDataChunk* prev, int fill)
{
51
    uint8_t *ptr = g_malloc0(prefix + sizeof(QXLDataChunk) + size);
52 53 54 55 56 57 58 59 60
    QXLDataChunk *qxl = (QXLDataChunk *) (ptr + prefix);
    memset(&qxl->data[0], fill, size);
    qxl->data_size = size;
    qxl->prev_chunk = to_physical(prev);
    if (prev)
        prev->next_chunk = to_physical(qxl);
    return ptr;
}

61
static void init_meminfo(RedMemSlotInfo *mem_info)
62
{
63 64 65 66 67 68 69
    memslot_info_init(mem_info, 1 /* groups */, 1 /* slots */, 1, 1, 0);
    memslot_info_add_slot(mem_info, 0, 0, 0 /* delta */, 0 /* start */, ~0ul /* end */, 0 /* generation */);
}

static void init_qxl_surface(QXLSurfaceCmd *qxl)
{
    void *surface_mem;
70

71 72 73 74 75 76 77 78
    memset(qxl, 0, sizeof(*qxl));

    qxl->surface_id = 123;

    qxl->u.surface_create.format = SPICE_SURFACE_FMT_32_xRGB;
    qxl->u.surface_create.width = 128;
    qxl->u.surface_create.stride = 512;
    qxl->u.surface_create.height = 128;
79
    surface_mem = g_malloc(0x10000);
80 81 82 83 84
    qxl->u.surface_create.data = to_physical(surface_mem);
}

static void deinit_qxl_surface(QXLSurfaceCmd *qxl)
{
85
    g_free(from_physical(qxl->u.surface_create.data));
86 87
}

88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
static void test_memslot_invalid_group_id(void)
{
    RedMemSlotInfo mem_info;
    init_meminfo(&mem_info);

    memslot_get_virt(&mem_info, 0, 16, 1);
}

static void test_memslot_invalid_slot_id(void)
{
    RedMemSlotInfo mem_info;
    init_meminfo(&mem_info);

    memslot_get_virt(&mem_info, 1 << mem_info.memslot_id_shift, 16, 0);
}

static void test_memslot_invalid_addresses(void)
{
    g_test_trap_subprocess("/server/memslot-invalid-addresses/subprocess/group_id", 0, 0);
    g_test_trap_assert_stderr("*group_id too big*");

    g_test_trap_subprocess("/server/memslot-invalid-addresses/subprocess/slot_id", 0, 0);
    g_test_trap_assert_stderr("*slot_id 1 too big*");
}

113 114 115
static void test_no_issues(void)
{
    RedMemSlotInfo mem_info;
116
    RedSurfaceCmd *cmd;
117 118
    QXLSurfaceCmd qxl;

119 120
    init_meminfo(&mem_info);
    init_qxl_surface(&qxl);
121

122
    /* try to create a surface with no issues, should succeed */
123 124 125
    cmd = red_surface_cmd_new(NULL, &mem_info, 0, to_physical(&qxl));
    g_assert_nonnull(cmd);
    red_surface_cmd_unref(cmd);
126

127 128 129
    deinit_qxl_surface(&qxl);
    memslot_info_destroy(&mem_info);
}
130

131 132 133
static void test_stride_too_small(void)
{
    RedMemSlotInfo mem_info;
134
    RedSurfaceCmd *cmd;
135
    QXLSurfaceCmd qxl;
136

137 138
    init_meminfo(&mem_info);
    init_qxl_surface(&qxl);
139 140 141 142 143 144

    /* try to create a surface with a stride too small to fit
     * the entire width.
     * This can be used to cause buffer overflows so refuse it.
     */
    qxl.u.surface_create.stride = 256;
145 146
    cmd = red_surface_cmd_new(NULL, &mem_info, 0, to_physical(&qxl));
    g_assert_null(cmd);
147 148 149 150 151 152 153 154

    deinit_qxl_surface(&qxl);
    memslot_info_destroy(&mem_info);
}

static void test_too_big_image(void)
{
    RedMemSlotInfo mem_info;
155
    RedSurfaceCmd *cmd;
156 157 158 159
    QXLSurfaceCmd qxl;

    init_meminfo(&mem_info);
    init_qxl_surface(&qxl);
160 161 162 163 164 165 166 167 168 169 170

    /* try to create a surface quite large.
     * The sizes (width and height) were chosen so the multiplication
     * using 32 bit values gives a very small value.
     * These kind of values should be refused as they will cause
     * overflows. Also the total memory for the card is not enough to
     * hold the surface so surely can't be accepted.
     */
    qxl.u.surface_create.stride = 0x08000004 * 4;
    qxl.u.surface_create.width = 0x08000004;
    qxl.u.surface_create.height = 0x40000020;
171 172
    cmd = red_surface_cmd_new(NULL, &mem_info, 0, to_physical(&qxl));
    g_assert_null(cmd);
173 174 175 176 177 178 179 180

    deinit_qxl_surface(&qxl);
    memslot_info_destroy(&mem_info);
}

static void test_cursor_command(void)
{
    RedMemSlotInfo mem_info;
181
    RedCursorCmd *red_cursor_cmd;
182 183 184 185
    QXLCursorCmd cursor_cmd;
    QXLCursor *cursor;

    init_meminfo(&mem_info);
186

187 188 189 190 191 192 193 194 195 196 197 198
    /* test base cursor with no problems */
    memset(&cursor_cmd, 0, sizeof(cursor_cmd));
    cursor_cmd.type = QXL_CURSOR_SET;

    cursor = create_chunk(SPICE_OFFSETOF(QXLCursor, chunk), 128 * 128 * 4, NULL, 0xaa);
    cursor->header.unique = 1;
    cursor->header.width = 128;
    cursor->header.height = 128;
    cursor->data_size = 128 * 128 * 4;

    cursor_cmd.u.set.shape = to_physical(cursor);

199 200 201
    red_cursor_cmd = red_cursor_cmd_new(NULL, &mem_info, 0, to_physical(&cursor_cmd));
    g_assert_nonnull(red_cursor_cmd);
    red_cursor_cmd_unref(red_cursor_cmd);
202
    g_free(cursor);
203 204 205 206 207 208
    memslot_info_destroy(&mem_info);
}

static void test_circular_empty_chunks(void)
{
    RedMemSlotInfo mem_info;
209
    RedCursorCmd *red_cursor_cmd;
210 211 212 213 214 215 216
    QXLCursorCmd cursor_cmd;
    QXLCursor *cursor;
    QXLDataChunk *chunks[2];

    init_meminfo(&mem_info);
    g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
                          "*red_get_data_chunks_ptr: data split in too many chunks, avoiding DoS*");
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

    /* a circular list of empty chunks should not be a problems */
    memset(&cursor_cmd, 0, sizeof(cursor_cmd));
    cursor_cmd.type = QXL_CURSOR_SET;

    cursor = create_chunk(SPICE_OFFSETOF(QXLCursor, chunk), 0, NULL, 0xaa);
    cursor->header.unique = 1;
    cursor->header.width = 128;
    cursor->header.height = 128;
    cursor->data_size = 128 * 128 * 4;

    chunks[0] = create_chunk(0, 0, &cursor->chunk, 0xaa);
    chunks[0]->next_chunk = to_physical(&cursor->chunk);

    cursor_cmd.u.set.shape = to_physical(cursor);

233 234
    red_cursor_cmd = red_cursor_cmd_new(NULL, &mem_info, 0, to_physical(&cursor_cmd));
    if (red_cursor_cmd != NULL) {
235
        /* function does not return errors so there should be no data */
236 237 238 239 240
        g_assert_cmpuint(red_cursor_cmd->type, ==, QXL_CURSOR_SET);
        g_assert_cmpuint(red_cursor_cmd->u.set.position.x, ==, 0);
        g_assert_cmpuint(red_cursor_cmd->u.set.position.y, ==, 0);
        g_assert_cmpuint(red_cursor_cmd->u.set.shape.data_size, ==, 0);
        red_cursor_cmd_unref(red_cursor_cmd);
241
    }
242 243
    g_test_assert_expected_messages();

244 245
    g_free(cursor);
    g_free(chunks[0]);
246 247 248 249 250 251
    memslot_info_destroy(&mem_info);
}

static void test_circular_small_chunks(void)
{
    RedMemSlotInfo mem_info;
252
    RedCursorCmd *red_cursor_cmd;
253 254 255 256 257 258 259
    QXLCursorCmd cursor_cmd;
    QXLCursor *cursor;
    QXLDataChunk *chunks[2];

    init_meminfo(&mem_info);
    g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
                          "*red_get_data_chunks_ptr: data split in too many chunks, avoiding DoS*");
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275

    /* a circular list of small chunks should not be a problems */
    memset(&cursor_cmd, 0, sizeof(cursor_cmd));
    cursor_cmd.type = QXL_CURSOR_SET;

    cursor = create_chunk(SPICE_OFFSETOF(QXLCursor, chunk), 1, NULL, 0xaa);
    cursor->header.unique = 1;
    cursor->header.width = 128;
    cursor->header.height = 128;
    cursor->data_size = 128 * 128 * 4;

    chunks[0] = create_chunk(0, 1, &cursor->chunk, 0xaa);
    chunks[0]->next_chunk = to_physical(&cursor->chunk);

    cursor_cmd.u.set.shape = to_physical(cursor);

276 277
    red_cursor_cmd = red_cursor_cmd_new(NULL, &mem_info, 0, to_physical(&cursor_cmd));
    if (red_cursor_cmd != NULL) {
278
        /* function does not return errors so there should be no data */
279 280 281 282 283
        g_assert_cmpuint(red_cursor_cmd->type, ==, QXL_CURSOR_SET);
        g_assert_cmpuint(red_cursor_cmd->u.set.position.x, ==, 0);
        g_assert_cmpuint(red_cursor_cmd->u.set.position.y, ==, 0);
        g_assert_cmpuint(red_cursor_cmd->u.set.shape.data_size, ==, 0);
        red_cursor_cmd_unref(red_cursor_cmd);
284
    }
285 286
    g_test_assert_expected_messages();

287 288
    g_free(cursor);
    g_free(chunks[0]);
289
    memslot_info_destroy(&mem_info);
290 291 292 293 294 295 296
}


int main(int argc, char *argv[])
{
    g_test_init(&argc, &argv, NULL);

297 298 299 300 301
    /* try to use invalid memslot group/slot */
    g_test_add_func("/server/memslot-invalid-addresses", test_memslot_invalid_addresses);
    g_test_add_func("/server/memslot-invalid-addresses/subprocess/group_id", test_memslot_invalid_group_id);
    g_test_add_func("/server/memslot-invalid-addresses/subprocess/slot_id", test_memslot_invalid_slot_id);

302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
    /* try to create a surface with no issues, should succeed */
    g_test_add_func("/server/qxl-parsing-no-issues", test_no_issues);

    /* try to create a surface with a stride too small to fit
     * the entire width.
     * This can be used to cause buffer overflows so refuse it.
     */
    g_test_add_func("/server/qxl-parsing/stride-too-small", test_stride_too_small);

    /* try to create a surface quite large.
     * The sizes (width and height) were chosen so the multiplication
     * using 32 bit values gives a very small value.
     * These kind of values should be refused as they will cause
     * overflows. Also the total memory for the card is not enough to
     * hold the surface so surely can't be accepted.
     */
    g_test_add_func("/server/qxl-parsing/too-big-image", test_too_big_image);

    /* test base cursor with no problems */
    g_test_add_func("/server/qxl-parsing/base-cursor-command", test_cursor_command);

    /* a circular list of empty chunks should not be a problems */
    g_test_add_func("/server/qxl-parsing/circular-empty-chunks", test_circular_empty_chunks);

    /* a circular list of small chunks should not be a problems */
    g_test_add_func("/server/qxl-parsing/circular-small-chunks", test_circular_small_chunks);
328

329
    return g_test_run();
330
}