red-parse-qxl.cpp 51.1 KB
Newer Older
Gerd Hoffmann's avatar
Gerd Hoffmann committed
1
2
3
4
/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
   Copyright (C) 2009,2010 Red Hat, Inc.

5
6
7
8
   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.
Gerd Hoffmann's avatar
Gerd Hoffmann committed
9

10
   This library is distributed in the hope that it will be useful,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
11
   but WITHOUT ANY WARRANTY; without even the implied warranty of
12
13
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
Gerd Hoffmann's avatar
Gerd Hoffmann committed
14

15
16
   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/>.
Gerd Hoffmann's avatar
Gerd Hoffmann committed
17
*/
18
#include <config.h>
Gerd Hoffmann's avatar
Gerd Hoffmann committed
19

20
#include <inttypes.h>
21
#include <glib.h>
22
#include <common/lz_common.h>
23
#include "spice-bitmap-utils.h"
Frediano Ziglio's avatar
Frediano Ziglio committed
24
#include "red-common.h"
25
#include "red-qxl.h"
Frediano Ziglio's avatar
Frediano Ziglio committed
26
27
#include "memslot.h"
#include "red-parse-qxl.h"
Gerd Hoffmann's avatar
Gerd Hoffmann committed
28

29
30
31
32
33
34
35
36
/* Max size in bytes for any data field used in a QXL command.
 * This will for example be useful to prevent the guest from saturating the
 * host memory if it tries to send overlapping chunks.
 * This value should be big enough for all requests but limited
 * to 32 bits. Even better if it fits on 31 bits to detect integer overflows.
 */
#define MAX_DATA_CHUNK 0x7ffffffflu

37
verify(MAX_DATA_CHUNK <= G_MAXINT32);
38

39
40
41
42
43
44
45
/* Limit number of chunks.
 * The guest can attempt to make host allocate too much memory
 * just with a large number of small chunks.
 * Prevent that the chunk list take more memory than the data itself.
 */
#define MAX_CHUNKS (MAX_DATA_CHUNK/1024u)

46
47
#define INVALID_SIZE ((size_t) -1)

48
49
50
51
52
53
54
struct RedDataChunk {
    uint32_t data_size;
    RedDataChunk *prev_chunk;
    RedDataChunk *next_chunk;
    uint8_t *data;
};

55
56
#if 0
static void hexdump_qxl(RedMemSlotInfo *slots, int group_id,
57
                        QXLPHYSICAL addr, uint8_t bytes)
58
59
60
61
{
    uint8_t *hex;
    int i;

62
    hex = (uint8_t*)memslot_get_virt(slots, addr, bytes, group_id);
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
    for (i = 0; i < bytes; i++) {
        if (0 == i % 16) {
            fprintf(stderr, "%lx: ", addr+i);
        }
        if (0 == i % 4) {
            fprintf(stderr, " ");
        }
        fprintf(stderr, " %02x", hex[i]);
        if (15 == i % 16) {
            fprintf(stderr, "\n");
        }
    }
}
#endif

78
79
80
81
82
83
84
85
86
87
88
static inline uint32_t color_16_to_32(uint32_t color)
{
    uint32_t ret;

    ret = ((color & 0x001f) << 3) | ((color & 0x001c) >> 2);
    ret |= ((color & 0x03e0) << 6) | ((color & 0x0380) << 1);
    ret |= ((color & 0x7c00) << 9) | ((color & 0x7000) << 4);

    return ret;
}

89
90
91
92
93
94
static uint8_t *red_linearize_chunk(RedDataChunk *head, size_t size, bool *free_chunk)
{
    uint8_t *data, *ptr;
    RedDataChunk *chunk;
    uint32_t copy;

Rosen Penev's avatar
Rosen Penev committed
95
    if (head->next_chunk == nullptr) {
96
        spice_assert(size <= head->data_size);
97
98
99
100
        *free_chunk = false;
        return head->data;
    }

Frediano Ziglio's avatar
Frediano Ziglio committed
101
    ptr = data = (uint8_t*) g_malloc(size);
102
    *free_chunk = true;
Rosen Penev's avatar
Rosen Penev committed
103
    for (chunk = head; chunk != nullptr && size > 0; chunk = chunk->next_chunk) {
104
105
106
107
108
        copy = MIN(chunk->data_size, size);
        memcpy(ptr, chunk->data, copy);
        ptr += copy;
        size -= copy;
    }
109
    spice_assert(size == 0);
110
111
112
113
114
115
    return data;
}

static size_t red_get_data_chunks_ptr(RedMemSlotInfo *slots, int group_id,
                                      int memslot_id,
                                      RedDataChunk *red, QXLDataChunk *qxl)
Gerd Hoffmann's avatar
Gerd Hoffmann committed
116
117
{
    RedDataChunk *red_prev;
118
119
    uint64_t data_size = 0;
    uint32_t chunk_data_size;
120
    QXLPHYSICAL next_chunk;
121
    unsigned num_chunks = 0;
Gerd Hoffmann's avatar
Gerd Hoffmann committed
122
123
124

    red->data_size = qxl->data_size;
    data_size += red->data_size;
125
    red->data = qxl->data;
Rosen Penev's avatar
Rosen Penev committed
126
    red->prev_chunk = red->next_chunk = nullptr;
127
    if (!memslot_validate_virt(slots, (intptr_t)red->data, memslot_id, red->data_size, group_id)) {
Rosen Penev's avatar
Rosen Penev committed
128
        red->data = nullptr;
129
        return INVALID_SIZE;
Alon Levy's avatar
Alon Levy committed
130
    }
Gerd Hoffmann's avatar
Gerd Hoffmann committed
131

132
    while ((next_chunk = qxl->next_chunk) != 0) {
133
134
135
136
        /* somebody is trying to use too much memory using a lot of chunks.
         * Or made a circular list of chunks
         */
        if (++num_chunks >= MAX_CHUNKS) {
137
            spice_warning("data split in too many chunks, avoiding DoS");
138
139
140
            goto error;
        }

141
        memslot_id = memslot_get_id(slots, next_chunk);
142
        qxl = (QXLDataChunk *)memslot_get_virt(slots, next_chunk, sizeof(*qxl), group_id);
Rosen Penev's avatar
Rosen Penev committed
143
        if (qxl == nullptr) {
144
            goto error;
145
        }
146
147
148
149
150
151
152
153
154
155
156

        /* do not waste space for empty chunks.
         * This could be just a driver issue or an attempt
         * to allocate too much memory or a circular list.
         * All above cases are handled by the check for number
         * of chunks.
         */
        chunk_data_size = qxl->data_size;
        if (chunk_data_size == 0)
            continue;

Gerd Hoffmann's avatar
Gerd Hoffmann committed
157
        red_prev = red;
158
        red = g_new0(RedDataChunk, 1);
159
        red->data_size = chunk_data_size;
160
        red->prev_chunk = red_prev;
161
        red->data = qxl->data;
162
163
        red_prev->next_chunk = red;

164
165
166
        data_size += chunk_data_size;
        /* this can happen if client is sending nested chunks */
        if (data_size > MAX_DATA_CHUNK) {
167
            spice_warning("too much data inside chunks, avoiding DoS");
168
            goto error;
169
        }
170
        if (!memslot_validate_virt(slots, (intptr_t)red->data, memslot_id, red->data_size, group_id))
171
            goto error;
Gerd Hoffmann's avatar
Gerd Hoffmann committed
172
173
    }

Rosen Penev's avatar
Rosen Penev committed
174
    red->next_chunk = nullptr;
Gerd Hoffmann's avatar
Gerd Hoffmann committed
175
    return data_size;
176
177
178
179

error:
    while (red->prev_chunk) {
        red_prev = red->prev_chunk;
180
        g_free(red);
181
182
183
        red = red_prev;
    }
    red->data_size = 0;
Rosen Penev's avatar
Rosen Penev committed
184
185
    red->next_chunk = nullptr;
    red->data = nullptr;
186
    return INVALID_SIZE;
Gerd Hoffmann's avatar
Gerd Hoffmann committed
187
188
}

189
static size_t red_get_data_chunks(RedMemSlotInfo *slots, int group_id,
190
                                  RedDataChunk *red, QXLPHYSICAL addr)
191
192
{
    QXLDataChunk *qxl;
193
    int memslot_id = memslot_get_id(slots, addr);
194

195
    qxl = (QXLDataChunk *)memslot_get_virt(slots, addr, sizeof(*qxl), group_id);
Rosen Penev's avatar
Rosen Penev committed
196
    if (qxl == nullptr) {
197
        return INVALID_SIZE;
Alon Levy's avatar
Alon Levy committed
198
    }
199
200
201
    return red_get_data_chunks_ptr(slots, group_id, memslot_id, red, qxl);
}

Gerd Hoffmann's avatar
Gerd Hoffmann committed
202
203
204
205
206
207
208
209
static void red_put_data_chunks(RedDataChunk *red)
{
    RedDataChunk *tmp;

    red = red->next_chunk;
    while (red) {
        tmp = red;
        red = red->next_chunk;
210
        g_free(tmp);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
211
212
213
    }
}

214
215
216
217
218
219
220
221
222
223
224
225
static void red_get_point_ptr(SpicePoint *red, QXLPoint *qxl)
{
    red->x = qxl->x;
    red->y = qxl->y;
}

static void red_get_point16_ptr(SpicePoint16 *red, QXLPoint16 *qxl)
{
    red->x = qxl->x;
    red->y = qxl->y;
}

Alon Levy's avatar
Alon Levy committed
226
void red_get_rect_ptr(SpiceRect *red, const QXLRect *qxl)
Gerd Hoffmann's avatar
Gerd Hoffmann committed
227
228
229
230
231
232
233
{
    red->top    = qxl->top;
    red->left   = qxl->left;
    red->bottom = qxl->bottom;
    red->right  = qxl->right;
}

234
static SpicePath *red_get_path(RedMemSlotInfo *slots, int group_id,
235
                               QXLPHYSICAL addr)
236
237
238
239
240
241
242
243
{
    RedDataChunk chunks;
    QXLPathSeg *start, *end;
    SpicePathSeg *seg;
    uint8_t *data;
    bool free_data;
    QXLPath *qxl;
    SpicePath *red;
244
245
    size_t size;
    uint64_t mem_size, mem_size2, segment_size;
246
    int n_segments;
247
    int i;
248
    uint32_t count;
249

250
    qxl = (QXLPath *)memslot_get_virt(slots, addr, sizeof(*qxl), group_id);
Rosen Penev's avatar
Rosen Penev committed
251
252
    if (qxl == nullptr) {
        return nullptr;
Alon Levy's avatar
Alon Levy committed
253
    }
254
    size = red_get_data_chunks_ptr(slots, group_id,
255
                                   memslot_get_id(slots, addr),
256
                                   &chunks, &qxl->chunk);
257
    if (size == INVALID_SIZE) {
Rosen Penev's avatar
Rosen Penev committed
258
        return nullptr;
259
    }
260
261
262
    data = red_linearize_chunk(&chunks, size, &free_data);
    red_put_data_chunks(&chunks);

263
264
265
266
267
    n_segments = 0;
    mem_size = sizeof(*red);

    start = (QXLPathSeg*)data;
    end = (QXLPathSeg*)(data + size);
268
    while (start+1 < end) {
269
270
        n_segments++;
        count = start->count;
271
        segment_size = sizeof(SpicePathSeg) + (uint64_t) count * sizeof(SpicePointFix);
272
        mem_size += sizeof(SpicePathSeg *) + SPICE_ALIGN(segment_size, 4);
273
274
275
        /* avoid going backward with 32 bit architectures */
        spice_assert((uint64_t) count * sizeof(QXLPointFix)
                     <= (char*) end - (char*) &start->points[0]);
276
277
278
        start = (QXLPathSeg*)(&start->points[count]);
    }

Frediano Ziglio's avatar
Frediano Ziglio committed
279
    red = (SpicePath*) g_malloc(mem_size);
280
    red->num_segments = n_segments;
281
282
283

    start = (QXLPathSeg*)data;
    end = (QXLPathSeg*)(data + size);
284
    seg = (SpicePathSeg*)&red->segments[n_segments];
285
286
    n_segments = 0;
    mem_size2 = sizeof(*red);
287
    while (start+1 < end && n_segments < red->num_segments) {
288
        red->segments[n_segments++] = seg;
289
290
291
292
293
        count = start->count;

        /* Protect against overflow in size calculations before
           writing to memory */
        /* Verify that we didn't overflow due to guest changing data */
294
        mem_size2 += sizeof(SpicePathSeg) + (uint64_t) count * sizeof(SpicePointFix);
295
        spice_assert(mem_size2 <= mem_size);
296

297
        seg->flags = start->flags;
298
        seg->count = count;
299
300
301
302
303
304
305
        for (i = 0; i < seg->count; i++) {
            seg->points[i].x = start->points[i].x;
            seg->points[i].y = start->points[i].y;
        }
        start = (QXLPathSeg*)(&start->points[i]);
        seg = (SpicePathSeg*)(&seg->points[i]);
    }
306
    /* Ensure guest didn't tamper with segment count */
307
    spice_assert(n_segments == red->num_segments);
308
309

    if (free_data) {
310
        g_free(data);
311
312
313
314
315
    }
    return red;
}

static SpiceClipRects *red_get_clip_rects(RedMemSlotInfo *slots, int group_id,
316
                                          QXLPHYSICAL addr)
317
318
319
320
{
    RedDataChunk chunks;
    QXLClipRects *qxl;
    SpiceClipRects *red;
321
    QXLRect *start;
322
323
324
325
    uint8_t *data;
    bool free_data;
    size_t size;
    int i;
326
    uint32_t num_rects;
327

328
    qxl = (QXLClipRects *)memslot_get_virt(slots, addr, sizeof(*qxl), group_id);
Rosen Penev's avatar
Rosen Penev committed
329
330
    if (qxl == nullptr) {
        return nullptr;
Alon Levy's avatar
Alon Levy committed
331
    }
332
    size = red_get_data_chunks_ptr(slots, group_id,
333
                                   memslot_get_id(slots, addr),
334
                                   &chunks, &qxl->chunk);
335
    if (size == INVALID_SIZE) {
Rosen Penev's avatar
Rosen Penev committed
336
        return nullptr;
337
    }
338
339
340
    data = red_linearize_chunk(&chunks, size, &free_data);
    red_put_data_chunks(&chunks);

341
    num_rects = qxl->num_rects;
342
343
344
345
346
    /* The cast is needed to prevent 32 bit integer overflows.
     * This check is enough as size is limited to 31 bit
     * by red_get_data_chunks_ptr checks.
     */
    spice_assert((uint64_t) num_rects * sizeof(QXLRect) == size);
347
    SPICE_VERIFY(sizeof(SpiceRect) == sizeof(QXLRect));
Frediano Ziglio's avatar
Frediano Ziglio committed
348
    red = (SpiceClipRects*) g_malloc(sizeof(*red) + num_rects * sizeof(SpiceRect));
349
    red->num_rects = num_rects;
350
351
352
353
354
355
356

    start = (QXLRect*)data;
    for (i = 0; i < red->num_rects; i++) {
        red_get_rect_ptr(red->rects + i, start++);
    }

    if (free_data) {
357
        g_free(data);
358
359
360
361
    }
    return red;
}

362
static SpiceChunks *red_get_image_data_flat(RedMemSlotInfo *slots, int group_id,
363
                                            QXLPHYSICAL addr, size_t size)
364
365
{
    SpiceChunks *data;
366
    void *bitmap_virt;
367

368
    bitmap_virt = memslot_get_virt(slots, addr, size, group_id);
Rosen Penev's avatar
Rosen Penev committed
369
370
    if (bitmap_virt == nullptr) {
        return nullptr;
Alon Levy's avatar
Alon Levy committed
371
    }
372
373
374

    data = spice_chunks_new(1);
    data->data_size      = size;
Frediano Ziglio's avatar
Frediano Ziglio committed
375
    data->chunk[0].data  = (uint8_t*) bitmap_virt;
376
377
378
379
380
381
382
383
384
385
386
    data->chunk[0].len   = size;
    return data;
}

static SpiceChunks *red_get_image_data_chunked(RedMemSlotInfo *slots, int group_id,
                                               RedDataChunk *head)
{
    SpiceChunks *data;
    RedDataChunk *chunk;
    int i;

Rosen Penev's avatar
Rosen Penev committed
387
    for (i = 0, chunk = head; chunk != nullptr; chunk = chunk->next_chunk) {
388
389
390
391
392
393
        i++;
    }

    data = spice_chunks_new(i);
    data->data_size = 0;
    for (i = 0, chunk = head;
Rosen Penev's avatar
Rosen Penev committed
394
         chunk != nullptr && i < data->num_chunks;
395
396
397
398
399
         chunk = chunk->next_chunk, i++) {
        data->chunk[i].data  = chunk->data;
        data->chunk[i].len   = chunk->data_size;
        data->data_size     += chunk->data_size;
    }
400
    spice_assert(i == data->num_chunks);
401
402
403
    return data;
}

404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
static const char *bitmap_format_to_string(int format)
{
    switch (format) {
    case SPICE_BITMAP_FMT_INVALID: return "SPICE_BITMAP_FMT_INVALID";
    case SPICE_BITMAP_FMT_1BIT_LE: return "SPICE_BITMAP_FMT_1BIT_LE";
    case SPICE_BITMAP_FMT_1BIT_BE: return "SPICE_BITMAP_FMT_1BIT_BE";
    case SPICE_BITMAP_FMT_4BIT_LE: return "SPICE_BITMAP_FMT_4BIT_LE";
    case SPICE_BITMAP_FMT_4BIT_BE: return "SPICE_BITMAP_FMT_4BIT_BE";
    case SPICE_BITMAP_FMT_8BIT: return "SPICE_BITMAP_FMT_8BIT";
    case SPICE_BITMAP_FMT_16BIT: return "SPICE_BITMAP_FMT_16BIT";
    case SPICE_BITMAP_FMT_24BIT: return "SPICE_BITMAP_FMT_24BIT";
    case SPICE_BITMAP_FMT_32BIT: return "SPICE_BITMAP_FMT_32BIT";
    case SPICE_BITMAP_FMT_RGBA: return "SPICE_BITMAP_FMT_RGBA";
    case SPICE_BITMAP_FMT_8BIT_A: return "SPICE_BITMAP_FMT_8BIT_A";
    }
    return "unknown";
}

422
423
static const unsigned int MAP_BITMAP_FMT_TO_BITS_PER_PIXEL[] =
    {0, 1, 1, 4, 4, 8, 16, 24, 32, 32, 8};
424

425
static bool bitmap_consistent(SpiceBitmap *bitmap)
426
{
427
    unsigned int bpp;
Frediano Ziglio's avatar
Frediano Ziglio committed
428
429

    if (bitmap->format >= SPICE_N_ELEMENTS(MAP_BITMAP_FMT_TO_BITS_PER_PIXEL)) {
430
        spice_warning("wrong format specified for image");
431
        return false;
Frediano Ziglio's avatar
Frediano Ziglio committed
432
433
434
    }

    bpp = MAP_BITMAP_FMT_TO_BITS_PER_PIXEL[bitmap->format];
435

436
    if (bitmap->stride < (((uint64_t) bitmap->x * bpp + 7u) / 8u)) {
437
        spice_warning("image stride too small for width: %d < ((%d * %d + 7) / 8) (%s=%d)",
438
439
440
                    bitmap->stride, bitmap->x, bpp,
                    bitmap_format_to_string(bitmap->format),
                    bitmap->format);
441
        return false;
442
    }
443
    return true;
444
445
}

446
static SpiceImage *red_get_image(RedMemSlotInfo *slots, int group_id,
447
                                 QXLPHYSICAL addr, uint32_t flags, bool is_mask)
448
449
450
{
    RedDataChunk chunks;
    QXLImage *qxl;
Rosen Penev's avatar
Rosen Penev committed
451
452
    SpiceImage *red = nullptr;
    SpicePalette *rp = nullptr;
453
    uint64_t bitmap_size, size;
454
    uint8_t qxl_flags;
Frediano Ziglio's avatar
Frediano Ziglio committed
455
    QXLPHYSICAL palette;
456
457

    if (addr == 0) {
Rosen Penev's avatar
Rosen Penev committed
458
        return nullptr;
459
460
    }

461
    qxl = (QXLImage *)memslot_get_virt(slots, addr, sizeof(*qxl), group_id);
Rosen Penev's avatar
Rosen Penev committed
462
463
    if (qxl == nullptr) {
        return nullptr;
Alon Levy's avatar
Alon Levy committed
464
    }
465
    red = g_new0(SpiceImage, 1);
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
    red->descriptor.id     = qxl->descriptor.id;
    red->descriptor.type   = qxl->descriptor.type;
    red->descriptor.flags = 0;
    if (qxl->descriptor.flags & QXL_IMAGE_HIGH_BITS_SET) {
        red->descriptor.flags |= SPICE_IMAGE_FLAGS_HIGH_BITS_SET;
    }
    if (qxl->descriptor.flags & QXL_IMAGE_CACHE) {
        red->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
    }
    red->descriptor.width  = qxl->descriptor.width;
    red->descriptor.height = qxl->descriptor.height;

    switch (red->descriptor.type) {
    case SPICE_IMAGE_TYPE_BITMAP:
        red->u.bitmap.format = qxl->bitmap.format;
Frediano Ziglio's avatar
Frediano Ziglio committed
481
482
483
484
485
        red->u.bitmap.x      = qxl->bitmap.x;
        red->u.bitmap.y      = qxl->bitmap.y;
        red->u.bitmap.stride = qxl->bitmap.stride;
        palette = qxl->bitmap.palette;
        if (!bitmap_fmt_is_rgb(red->u.bitmap.format) && !palette && !is_mask) {
486
            spice_warning("guest error: missing palette on bitmap format=%d",
487
                          red->u.bitmap.format);
488
            goto error;
489
        }
Frediano Ziglio's avatar
Frediano Ziglio committed
490
        if (red->u.bitmap.x == 0 || red->u.bitmap.y == 0) {
491
            spice_warning("guest error: zero area bitmap");
492
            goto error;
493
        }
494
495
496
497
        qxl_flags = qxl->bitmap.flags;
        if (qxl_flags & QXL_BITMAP_TOP_DOWN) {
            red->u.bitmap.flags = SPICE_BITMAP_FLAGS_TOP_DOWN;
        }
498
499
500
        if (!bitmap_consistent(&red->u.bitmap)) {
            goto error;
        }
Frediano Ziglio's avatar
Frediano Ziglio committed
501
        if (palette) {
502
503
            QXLPalette *qp;
            int i, num_ents;
504
            qp = (QXLPalette *)memslot_get_virt(slots, palette,
505
                                                sizeof(*qp), group_id);
Rosen Penev's avatar
Rosen Penev committed
506
            if (qp == nullptr) {
507
                goto error;
Alon Levy's avatar
Alon Levy committed
508
            }
509
            num_ents = qp->num_ents;
510
511
512
            if (!memslot_validate_virt(slots, (intptr_t)qp->ents,
                                       memslot_get_id(slots, palette),
                                       num_ents * sizeof(qp->ents[0]), group_id)) {
513
                goto error;
Alon Levy's avatar
Alon Levy committed
514
            }
Frediano Ziglio's avatar
Frediano Ziglio committed
515
            rp = (SpicePalette*) g_malloc(num_ents * sizeof(rp->ents[0]) + sizeof(*rp));
516
517
            rp->unique   = qp->unique;
            rp->num_ents = num_ents;
518
519
520
521
522
523
524
525
            if (flags & QXL_COMMAND_FLAG_COMPAT_16BPP) {
                for (i = 0; i < num_ents; i++) {
                    rp->ents[i] = color_16_to_32(qp->ents[i]);
                }
            } else {
                for (i = 0; i < num_ents; i++) {
                    rp->ents[i] = qp->ents[i];
                }
526
527
528
529
            }
            red->u.bitmap.palette = rp;
            red->u.bitmap.palette_id = rp->unique;
        }
530
531
532
533
        bitmap_size = (uint64_t) red->u.bitmap.y * red->u.bitmap.stride;
        if (bitmap_size > MAX_DATA_CHUNK) {
            goto error;
        }
534
535
536
537
        if (qxl_flags & QXL_BITMAP_DIRECT) {
            red->u.bitmap.data = red_get_image_data_flat(slots, group_id,
                                                         qxl->bitmap.data,
                                                         bitmap_size);
538
539
540
            if (red->u.bitmap.data == nullptr) {
                goto error;
            }
541
542
543
        } else {
            size = red_get_data_chunks(slots, group_id,
                                       &chunks, qxl->bitmap.data);
544
            if (size == INVALID_SIZE || size != bitmap_size) {
545
                red_put_data_chunks(&chunks);
546
                goto error;
Alon Levy's avatar
Alon Levy committed
547
            }
548
549
550
551
552
553
554
555
556
557
558
559
560
561
            red->u.bitmap.data = red_get_image_data_chunked(slots, group_id,
                                                            &chunks);
            red_put_data_chunks(&chunks);
        }
        if (qxl_flags & QXL_BITMAP_UNSTABLE) {
            red->u.bitmap.data->flags |= SPICE_CHUNKS_FLAGS_UNSTABLE;
        }
        break;
    case SPICE_IMAGE_TYPE_SURFACE:
        red->u.surface.surface_id = qxl->surface_image.surface_id;
        break;
    case SPICE_IMAGE_TYPE_QUIC:
        red->u.quic.data_size = qxl->quic.data_size;
        size = red_get_data_chunks_ptr(slots, group_id,
562
                                       memslot_get_id(slots, addr),
563
                                       &chunks, (QXLDataChunk *)qxl->quic.data);
564
        if (size == INVALID_SIZE || size != red->u.quic.data_size) {
565
            red_put_data_chunks(&chunks);
566
            goto error;
Alon Levy's avatar
Alon Levy committed
567
        }
568
569
570
571
572
        red->u.quic.data = red_get_image_data_chunked(slots, group_id,
                                                      &chunks);
        red_put_data_chunks(&chunks);
        break;
    default:
573
574
        spice_warning("unknown type %d", red->descriptor.type);
        goto error;
575
576
    }
    return red;
577
error:
578
579
    g_free(red);
    g_free(rp);
Rosen Penev's avatar
Rosen Penev committed
580
    return nullptr;
581
582
}

Frediano Ziglio's avatar
Frediano Ziglio committed
583
static void red_put_image(SpiceImage *red)
584
{
Rosen Penev's avatar
Rosen Penev committed
585
    if (red == nullptr)
586
587
588
589
        return;

    switch (red->descriptor.type) {
    case SPICE_IMAGE_TYPE_BITMAP:
590
        g_free(red->u.bitmap.palette);
591
592
        spice_chunks_destroy(red->u.bitmap.data);
        break;
593
594
595
    case SPICE_IMAGE_TYPE_QUIC:
        spice_chunks_destroy(red->u.quic.data);
        break;
596
    }
597
    g_free(red);
598
599
}

Gerd Hoffmann's avatar
Gerd Hoffmann committed
600
static void red_get_brush_ptr(RedMemSlotInfo *slots, int group_id,
601
                              SpiceBrush *red, QXLBrush *qxl, uint32_t flags)
Gerd Hoffmann's avatar
Gerd Hoffmann committed
602
603
604
605
{
    red->type = qxl->type;
    switch (red->type) {
    case SPICE_BRUSH_TYPE_SOLID:
606
607
608
609
610
        if (flags & QXL_COMMAND_FLAG_COMPAT_16BPP) {
            red->u.color = color_16_to_32(qxl->u.color);
        } else {
            red->u.color = qxl->u.color;
        }
Gerd Hoffmann's avatar
Gerd Hoffmann committed
611
612
        break;
    case SPICE_BRUSH_TYPE_PATTERN:
613
        red->u.pattern.pat = red_get_image(slots, group_id, qxl->u.pattern.pat, flags, false);
614
        red_get_point_ptr(&red->u.pattern.pos, &qxl->u.pattern.pos);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
615
616
617
618
        break;
    }
}

619
620
621
622
623
624
625
626
627
static void red_put_brush(SpiceBrush *red)
{
    switch (red->type) {
    case SPICE_BRUSH_TYPE_PATTERN:
        red_put_image(red->u.pattern.pat);
        break;
    }
}

Gerd Hoffmann's avatar
Gerd Hoffmann committed
628
static void red_get_qmask_ptr(RedMemSlotInfo *slots, int group_id,
629
                              SpiceQMask *red, QXLQMask *qxl, uint32_t flags)
Gerd Hoffmann's avatar
Gerd Hoffmann committed
630
{
631
    red->bitmap = red_get_image(slots, group_id, qxl->bitmap, flags, true);
632
633
634
635
636
637
638
639
    if (red->bitmap) {
        red->flags  = qxl->flags;
        red_get_point_ptr(&red->pos, &qxl->pos);
    } else {
        red->flags  = 0;
        red->pos.x = 0;
        red->pos.y = 0;
    }
640
641
642
643
644
}

static void red_put_qmask(SpiceQMask *red)
{
    red_put_image(red->bitmap);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
645
646
}

Gerd Hoffmann's avatar
Gerd Hoffmann committed
647
static void red_get_fill_ptr(RedMemSlotInfo *slots, int group_id,
648
                             SpiceFill *red, QXLFill *qxl, uint32_t flags)
Gerd Hoffmann's avatar
Gerd Hoffmann committed
649
{
650
    red_get_brush_ptr(slots, group_id, &red->brush, &qxl->brush, flags);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
651
    red->rop_descriptor = qxl->rop_descriptor;
652
    red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
653
654
}

655
656
657
658
659
660
static void red_put_fill(SpiceFill *red)
{
    red_put_brush(&red->brush);
    red_put_qmask(&red->mask);
}

Gerd Hoffmann's avatar
Gerd Hoffmann committed
661
static void red_get_opaque_ptr(RedMemSlotInfo *slots, int group_id,
662
                               SpiceOpaque *red, QXLOpaque *qxl, uint32_t flags)
Gerd Hoffmann's avatar
Gerd Hoffmann committed
663
{
664
   red->src_bitmap     = red_get_image(slots, group_id, qxl->src_bitmap, flags, false);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
665
   red_get_rect_ptr(&red->src_area, &qxl->src_area);
666
   red_get_brush_ptr(slots, group_id, &red->brush, &qxl->brush, flags);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
667
668
   red->rop_descriptor = qxl->rop_descriptor;
   red->scale_mode     = qxl->scale_mode;
669
   red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
670
671
}

672
673
674
675
676
677
678
static void red_put_opaque(SpiceOpaque *red)
{
    red_put_image(red->src_bitmap);
    red_put_brush(&red->brush);
    red_put_qmask(&red->mask);
}

679
static bool red_get_copy_ptr(RedMemSlotInfo *slots, int group_id,
680
                             RedDrawable *red_drawable, QXLCopy *qxl, uint32_t flags)
681
{
682
683
684
685
686
687
688
    /* there's no sense to have this true, this will just waste CPU and reduce optimizations
     * for this command. Due to some bugs however some driver set self_bitmap field for this
     * command so reset it. */
    red_drawable->self_bitmap = false;

    SpiceCopy *red = &red_drawable->u.copy;

689
    red->src_bitmap      = red_get_image(slots, group_id, qxl->src_bitmap, flags, false);
Alon Levy's avatar
Alon Levy committed
690
    if (!red->src_bitmap) {
Frediano Ziglio's avatar
Frediano Ziglio committed
691
        return false;
Alon Levy's avatar
Alon Levy committed
692
693
    }
    red_get_rect_ptr(&red->src_area, &qxl->src_area);
694
695
696
697
698
699
700
    /* The source area should not extend outside the source bitmap or have
     * swapped coordinates.
     */
    if (red->src_area.left < 0 ||
        red->src_area.left > red->src_area.right ||
        red->src_area.top < 0 ||
        red->src_area.top > red->src_area.bottom) {
Frediano Ziglio's avatar
Frediano Ziglio committed
701
        return false;
702
703
704
705
    }
    if (red->src_bitmap->descriptor.type == SPICE_IMAGE_TYPE_BITMAP &&
        (red->src_area.right > red->src_bitmap->u.bitmap.x ||
         red->src_area.bottom > red->src_bitmap->u.bitmap.y)) {
Frediano Ziglio's avatar
Frediano Ziglio committed
706
        return false;
707
    }
Alon Levy's avatar
Alon Levy committed
708
709
710
    red->rop_descriptor  = qxl->rop_descriptor;
    red->scale_mode      = qxl->scale_mode;
    red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags);
Frediano Ziglio's avatar
Frediano Ziglio committed
711
    return true;
712
713
}

714
715
716
717
718
719
static void red_put_copy(SpiceCopy *red)
{
    red_put_image(red->src_bitmap);
    red_put_qmask(&red->mask);
}

720
721
// these types are really the same thing
#define red_get_blend_ptr red_get_copy_ptr
722
#define red_put_blend red_put_copy
723

Gerd Hoffmann's avatar
Gerd Hoffmann committed
724
static void red_get_transparent_ptr(RedMemSlotInfo *slots, int group_id,
725
726
                                    SpiceTransparent *red, QXLTransparent *qxl,
                                    uint32_t flags)
Gerd Hoffmann's avatar
Gerd Hoffmann committed
727
{
728
    red->src_bitmap      = red_get_image(slots, group_id, qxl->src_bitmap, flags, false);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
729
   red_get_rect_ptr(&red->src_area, &qxl->src_area);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
730
731
732
733
   red->src_color       = qxl->src_color;
   red->true_color      = qxl->true_color;
}

734
735
736
737
738
static void red_put_transparent(SpiceTransparent *red)
{
    red_put_image(red->src_bitmap);
}

739
static void red_get_alpha_blend_ptr(RedMemSlotInfo *slots, int group_id,
740
741
                                    SpiceAlphaBlend *red, QXLAlphaBlend *qxl,
                                    uint32_t flags)
742
743
744
{
    red->alpha_flags = qxl->alpha_flags;
    red->alpha       = qxl->alpha;
745
    red->src_bitmap  = red_get_image(slots, group_id, qxl->src_bitmap, flags, false);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
746
    red_get_rect_ptr(&red->src_area, &qxl->src_area);
747
748
749
}

static void red_get_alpha_blend_ptr_compat(RedMemSlotInfo *slots, int group_id,
750
751
                                           SpiceAlphaBlend *red, QXLCompatAlphaBlend *qxl,
                                           uint32_t flags)
752
753
{
    red->alpha       = qxl->alpha;
754
    red->src_bitmap  = red_get_image(slots, group_id, qxl->src_bitmap, flags, false);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
755
    red_get_rect_ptr(&red->src_area, &qxl->src_area);
756
757
}

Alexander Larsson's avatar
Alexander Larsson committed
758
static void red_put_alpha_blend(SpiceAlphaBlend *red)
759
760
761
762
{
    red_put_image(red->src_bitmap);
}

763
764
765
766
767
static bool get_transform(RedMemSlotInfo *slots,
                          int group_id,
                          QXLPHYSICAL qxl_transform,
                          SpiceTransform *dst_transform)
{
Rosen Penev's avatar
Rosen Penev committed
768
    const uint32_t *t = nullptr;
769
770

    if (qxl_transform == 0)
771
        return false;
772

773
    t = (uint32_t *)memslot_get_virt(slots, qxl_transform, sizeof(*dst_transform), group_id);
774

Rosen Penev's avatar
Rosen Penev committed
775
    if (t == nullptr)
776
        return false;
777
778

    memcpy(dst_transform, t, sizeof(*dst_transform));
779
    return true;
780
781
782
783
784
785
786
}

static void red_get_composite_ptr(RedMemSlotInfo *slots, int group_id,
                                  SpiceComposite *red, QXLComposite *qxl, uint32_t flags)
{
    red->flags = qxl->flags;

787
    red->src_bitmap = red_get_image(slots, group_id, qxl->src, flags, false);
788
789
    if (get_transform(slots, group_id, qxl->src_transform, &red->src_transform))
        red->flags |= SPICE_COMPOSITE_HAS_SRC_TRANSFORM;
790

791
    if (qxl->mask) {
792
        red->mask_bitmap = red_get_image(slots, group_id, qxl->mask, flags, false);
793
794
795
796
        red->flags |= SPICE_COMPOSITE_HAS_MASK;
        if (get_transform(slots, group_id, qxl->mask_transform, &red->mask_transform))
            red->flags |= SPICE_COMPOSITE_HAS_MASK_TRANSFORM;
    } else {
Rosen Penev's avatar
Rosen Penev committed
797
        red->mask_bitmap = nullptr;
798
799
800
801
802
803
804
805
806
807
808
809
810
811
    }
    red->src_origin.x = qxl->src_origin.x;
    red->src_origin.y = qxl->src_origin.y;
    red->mask_origin.x = qxl->mask_origin.x;
    red->mask_origin.y = qxl->mask_origin.y;
}

static void red_put_composite(SpiceComposite *red)
{
    red_put_image(red->src_bitmap);
    if (red->mask_bitmap)
        red_put_image(red->mask_bitmap);
}

Gerd Hoffmann's avatar
Gerd Hoffmann committed
812
static void red_get_rop3_ptr(RedMemSlotInfo *slots, int group_id,
813
                             SpiceRop3 *red, QXLRop3 *qxl, uint32_t flags)
Gerd Hoffmann's avatar
Gerd Hoffmann committed
814
{
815
   red->src_bitmap = red_get_image(slots, group_id, qxl->src_bitmap, flags, false);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
816
   red_get_rect_ptr(&red->src_area, &qxl->src_area);
817
   red_get_brush_ptr(slots, group_id, &red->brush, &qxl->brush, flags);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
818
819
   red->rop3       = qxl->rop3;
   red->scale_mode = qxl->scale_mode;
820
   red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
821
822
}

823
824
825
826
827
828
829
static void red_put_rop3(SpiceRop3 *red)
{
    red_put_image(red->src_bitmap);
    red_put_brush(&red->brush);
    red_put_qmask(&red->mask);
}

830
831
static bool red_get_stroke_ptr(RedMemSlotInfo *slots, int group_id,
                               SpiceStroke *red, QXLStroke *qxl, uint32_t flags)
Gerd Hoffmann's avatar
Gerd Hoffmann committed
832
{
833
    red->path = red_get_path(slots, group_id, qxl->path);
Alon Levy's avatar
Alon Levy committed
834
    if (!red->path) {
Frediano Ziglio's avatar
Frediano Ziglio committed
835
        return false;
Alon Levy's avatar
Alon Levy committed
836
    }
837
838
839
840
841
842
    red->attr.flags       = qxl->attr.flags;
    if (red->attr.flags & SPICE_LINE_FLAGS_STYLED) {
        int style_nseg;
        uint8_t *buf;

        style_nseg = qxl->attr.style_nseg;
Frediano Ziglio's avatar
Frediano Ziglio committed
843
        red->attr.style = (SPICE_FIXED28_4*) g_malloc_n(style_nseg, sizeof(SPICE_FIXED28_4));
844
        red->attr.style_nseg  = style_nseg;
845
        spice_assert(qxl->attr.style);
846
        buf = (uint8_t *)memslot_get_virt(slots, qxl->attr.style,
847
                                          style_nseg * sizeof(QXLFIXED), group_id);
Rosen Penev's avatar
Rosen Penev committed
848
        if (buf == nullptr) {
Frediano Ziglio's avatar
Frediano Ziglio committed
849
            return false;
Alon Levy's avatar
Alon Levy committed
850
        }
851
852
853
        memcpy(red->attr.style, buf, style_nseg * sizeof(QXLFIXED));
    } else {
        red->attr.style_nseg  = 0;
Rosen Penev's avatar
Rosen Penev committed
854
        red->attr.style       = nullptr;
855
    }
856
    red_get_brush_ptr(slots, group_id, &red->brush, &qxl->brush, flags);
857
858
    red->fore_mode        = qxl->fore_mode;
    red->back_mode        = qxl->back_mode;
Frediano Ziglio's avatar
Frediano Ziglio committed
859
    return true;
Gerd Hoffmann's avatar
Gerd Hoffmann committed
860
861
}

862
static void red_put_stroke(SpiceStroke *red)
863
{
864
    red_put_brush(&red->brush);
865
    g_free(red->path);
866
    if (red->attr.flags & SPICE_LINE_FLAGS_STYLED) {
867
        g_free(red->attr.style);
868
    }
869
870
}

871
static SpiceString *red_get_string(RedMemSlotInfo *slots, int group_id,
872
                                   QXLPHYSICAL addr)
873
874
875
876
877
878
879
880
881
{
    RedDataChunk chunks;
    QXLString *qxl;
    QXLRasterGlyph *start, *end;
    SpiceString *red;
    SpiceRasterGlyph *glyph;
    uint8_t *data;
    bool free_data;
    size_t chunk_size, qxl_size, red_size, glyph_size;
882
883
884
    int glyphs, i;
    /* use unsigned to prevent integer overflow in multiplication below */
    unsigned int bpp = 0;
885
    uint16_t qxl_flags, qxl_length;
Gerd Hoffmann's avatar