/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* Copyright (C) 2009,2010 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 . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "common/lz_common.h" #include "spice-bitmap-utils.h" #include "red_common.h" #include "red_memslots.h" #include "red_parse_qxl.h" /* 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 G_STATIC_ASSERT(MAX_DATA_CHUNK <= G_MAXINT32); /* 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) #if 0 static void hexdump_qxl(RedMemSlotInfo *slots, int group_id, QXLPHYSICAL addr, uint8_t bytes) { uint8_t *hex; int i; hex = (uint8_t*)get_virt(slots, addr, bytes, group_id); 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 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; } static uint8_t *red_linearize_chunk(RedDataChunk *head, size_t size, bool *free_chunk) { uint8_t *data, *ptr; RedDataChunk *chunk; uint32_t copy; if (head->next_chunk == NULL) { spice_assert(size <= head->data_size); *free_chunk = false; return head->data; } ptr = data = spice_malloc(size); *free_chunk = true; for (chunk = head; chunk != NULL && size > 0; chunk = chunk->next_chunk) { copy = MIN(chunk->data_size, size); memcpy(ptr, chunk->data, copy); ptr += copy; size -= copy; } spice_assert(size == 0); return data; } static size_t red_get_data_chunks_ptr(RedMemSlotInfo *slots, int group_id, int memslot_id, RedDataChunk *red, QXLDataChunk *qxl) { RedDataChunk *red_prev; uint64_t data_size = 0; uint32_t chunk_data_size; int error; QXLPHYSICAL next_chunk; unsigned num_chunks = 0; red->data_size = qxl->data_size; data_size += red->data_size; red->data = qxl->data; red->prev_chunk = red->next_chunk = NULL; if (!validate_virt(slots, (intptr_t)red->data, memslot_id, red->data_size, group_id)) { red->data = NULL; return 0; } while ((next_chunk = qxl->next_chunk) != 0) { /* 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) { spice_warning("data split in too many chunks, avoiding DoS\n"); goto error; } memslot_id = get_memslot_id(slots, next_chunk); qxl = (QXLDataChunk *)get_virt(slots, next_chunk, sizeof(*qxl), group_id, &error); if (error) goto error; /* 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; red_prev = red; red = spice_new0(RedDataChunk, 1); red->data_size = chunk_data_size; red->prev_chunk = red_prev; red->data = qxl->data; red_prev->next_chunk = red; data_size += chunk_data_size; /* this can happen if client is sending nested chunks */ if (data_size > MAX_DATA_CHUNK) { spice_warning("too much data inside chunks, avoiding DoS\n"); goto error; } if (!validate_virt(slots, (intptr_t)red->data, memslot_id, red->data_size, group_id)) goto error; } red->next_chunk = NULL; return data_size; error: while (red->prev_chunk) { red_prev = red->prev_chunk; free(red); red = red_prev; } red->data_size = 0; red->next_chunk = NULL; red->data = NULL; return 0; } static size_t red_get_data_chunks(RedMemSlotInfo *slots, int group_id, RedDataChunk *red, QXLPHYSICAL addr) { QXLDataChunk *qxl; int error; int memslot_id = get_memslot_id(slots, addr); qxl = (QXLDataChunk *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return 0; } return red_get_data_chunks_ptr(slots, group_id, memslot_id, red, qxl); } static void red_put_data_chunks(RedDataChunk *red) { RedDataChunk *tmp; red = red->next_chunk; while (red) { tmp = red; red = red->next_chunk; free(tmp); } } 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; } void red_get_rect_ptr(SpiceRect *red, const QXLRect *qxl) { red->top = qxl->top; red->left = qxl->left; red->bottom = qxl->bottom; red->right = qxl->right; } static SpicePath *red_get_path(RedMemSlotInfo *slots, int group_id, QXLPHYSICAL addr) { RedDataChunk chunks; QXLPathSeg *start, *end; SpicePathSeg *seg; uint8_t *data; bool free_data; QXLPath *qxl; SpicePath *red; size_t size, mem_size, mem_size2, dsize, segment_size; int n_segments; int i; uint32_t count; int error; qxl = (QXLPath *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return NULL; } size = red_get_data_chunks_ptr(slots, group_id, get_memslot_id(slots, addr), &chunks, &qxl->chunk); data = red_linearize_chunk(&chunks, size, &free_data); red_put_data_chunks(&chunks); n_segments = 0; mem_size = sizeof(*red); start = (QXLPathSeg*)data; end = (QXLPathSeg*)(data + size); while (start+1 < end) { n_segments++; count = start->count; segment_size = sizeof(SpicePathSeg) + count * sizeof(SpicePointFix); mem_size += sizeof(SpicePathSeg *) + SPICE_ALIGN(segment_size, 4); start = (QXLPathSeg*)(&start->points[count]); } red = spice_malloc(mem_size); red->num_segments = n_segments; start = (QXLPathSeg*)data; end = (QXLPathSeg*)(data + size); seg = (SpicePathSeg*)&red->segments[n_segments]; n_segments = 0; mem_size2 = sizeof(*red); while (start+1 < end && n_segments < red->num_segments) { red->segments[n_segments++] = seg; count = start->count; /* Protect against overflow in size calculations before writing to memory */ spice_assert(mem_size2 + sizeof(SpicePathSeg) > mem_size2); mem_size2 += sizeof(SpicePathSeg); spice_assert(count < UINT32_MAX / sizeof(SpicePointFix)); dsize = count * sizeof(SpicePointFix); spice_assert(mem_size2 + dsize > mem_size2); mem_size2 += dsize; /* Verify that we didn't overflow due to guest changing data */ spice_assert(mem_size2 <= mem_size); seg->flags = start->flags; seg->count = count; 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]); } /* Ensure guest didn't tamper with segment count */ spice_assert(n_segments == red->num_segments); if (free_data) { free(data); } return red; } static SpiceClipRects *red_get_clip_rects(RedMemSlotInfo *slots, int group_id, QXLPHYSICAL addr) { RedDataChunk chunks; QXLClipRects *qxl; SpiceClipRects *red; QXLRect *start; uint8_t *data; bool free_data; size_t size; int i; int error; uint32_t num_rects; qxl = (QXLClipRects *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return NULL; } size = red_get_data_chunks_ptr(slots, group_id, get_memslot_id(slots, addr), &chunks, &qxl->chunk); data = red_linearize_chunk(&chunks, size, &free_data); red_put_data_chunks(&chunks); num_rects = qxl->num_rects; spice_assert(num_rects * sizeof(QXLRect) == size); red = spice_malloc(sizeof(*red) + num_rects * sizeof(SpiceRect)); red->num_rects = num_rects; start = (QXLRect*)data; for (i = 0; i < red->num_rects; i++) { red_get_rect_ptr(red->rects + i, start++); } if (free_data) { free(data); } return red; } static SpiceChunks *red_get_image_data_flat(RedMemSlotInfo *slots, int group_id, QXLPHYSICAL addr, size_t size) { SpiceChunks *data; int error; data = spice_chunks_new(1); data->data_size = size; data->chunk[0].data = (void*)get_virt(slots, addr, size, group_id, &error); if (error) { return 0; } 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; for (i = 0, chunk = head; chunk != NULL; chunk = chunk->next_chunk) { i++; } data = spice_chunks_new(i); data->data_size = 0; for (i = 0, chunk = head; chunk != NULL && i < data->num_chunks; chunk = chunk->next_chunk, i++) { data->chunk[i].data = chunk->data; data->chunk[i].len = chunk->data_size; data->data_size += chunk->data_size; } spice_assert(i == data->num_chunks); return data; } 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"; } static const unsigned int MAP_BITMAP_FMT_TO_BITS_PER_PIXEL[] = {0, 1, 1, 4, 4, 8, 16, 24, 32, 32, 8}; static int bitmap_consistent(SpiceBitmap *bitmap) { unsigned int bpp; if (bitmap->format >= SPICE_N_ELEMENTS(MAP_BITMAP_FMT_TO_BITS_PER_PIXEL)) { spice_warning("wrong format specified for image\n"); return FALSE; } bpp = MAP_BITMAP_FMT_TO_BITS_PER_PIXEL[bitmap->format]; if (bitmap->stride < (((uint64_t) bitmap->x * bpp + 7u) / 8u)) { spice_warning("image stride too small for width: %d < ((%d * %d + 7) / 8) (%s=%d)\n", bitmap->stride, bitmap->x, bpp, bitmap_format_to_string(bitmap->format), bitmap->format); return FALSE; } return TRUE; } static SpiceImage *red_get_image(RedMemSlotInfo *slots, int group_id, QXLPHYSICAL addr, uint32_t flags, int is_mask) { RedDataChunk chunks; QXLImage *qxl; SpiceImage *red = NULL; SpicePalette *rp = NULL; uint64_t bitmap_size, size; uint8_t qxl_flags; int error; QXLPHYSICAL palette; if (addr == 0) { return NULL; } qxl = (QXLImage *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return NULL; } red = spice_new0(SpiceImage, 1); 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; 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) { spice_warning("guest error: missing palette on bitmap format=%d\n", red->u.bitmap.format); goto error; } if (red->u.bitmap.x == 0 || red->u.bitmap.y == 0) { spice_warning("guest error: zero area bitmap\n"); goto error; } qxl_flags = qxl->bitmap.flags; if (qxl_flags & QXL_BITMAP_TOP_DOWN) { red->u.bitmap.flags = SPICE_BITMAP_FLAGS_TOP_DOWN; } if (!bitmap_consistent(&red->u.bitmap)) { goto error; } if (palette) { QXLPalette *qp; int i, num_ents; qp = (QXLPalette *)get_virt(slots, palette, sizeof(*qp), group_id, &error); if (error) { goto error; } num_ents = qp->num_ents; if (!validate_virt(slots, (intptr_t)qp->ents, get_memslot_id(slots, palette), num_ents * sizeof(qp->ents[0]), group_id)) { goto error; } rp = spice_malloc_n_m(num_ents, sizeof(rp->ents[0]), sizeof(*rp)); rp->unique = qp->unique; rp->num_ents = num_ents; 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]; } } red->u.bitmap.palette = rp; red->u.bitmap.palette_id = rp->unique; } bitmap_size = (uint64_t) red->u.bitmap.y * red->u.bitmap.stride; if (bitmap_size > MAX_DATA_CHUNK) { goto error; } if (qxl_flags & QXL_BITMAP_DIRECT) { red->u.bitmap.data = red_get_image_data_flat(slots, group_id, qxl->bitmap.data, bitmap_size); } else { size = red_get_data_chunks(slots, group_id, &chunks, qxl->bitmap.data); spice_assert(size == bitmap_size); if (size != bitmap_size) { red_put_data_chunks(&chunks); goto error; } 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, get_memslot_id(slots, addr), &chunks, (QXLDataChunk *)qxl->quic.data); spice_assert(size == red->u.quic.data_size); if (size != red->u.quic.data_size) { red_put_data_chunks(&chunks); goto error; } red->u.quic.data = red_get_image_data_chunked(slots, group_id, &chunks); red_put_data_chunks(&chunks); break; default: spice_warning("unknown type %d", red->descriptor.type); goto error; } return red; error: free(red); free(rp); return NULL; } void red_put_image(SpiceImage *red) { if (red == NULL) return; switch (red->descriptor.type) { case SPICE_IMAGE_TYPE_BITMAP: free(red->u.bitmap.palette); spice_chunks_destroy(red->u.bitmap.data); break; case SPICE_IMAGE_TYPE_QUIC: spice_chunks_destroy(red->u.quic.data); break; } free(red); } static void red_get_brush_ptr(RedMemSlotInfo *slots, int group_id, SpiceBrush *red, QXLBrush *qxl, uint32_t flags) { red->type = qxl->type; switch (red->type) { case SPICE_BRUSH_TYPE_SOLID: if (flags & QXL_COMMAND_FLAG_COMPAT_16BPP) { red->u.color = color_16_to_32(qxl->u.color); } else { red->u.color = qxl->u.color; } break; case SPICE_BRUSH_TYPE_PATTERN: red->u.pattern.pat = red_get_image(slots, group_id, qxl->u.pattern.pat, flags, FALSE); break; } } static void red_put_brush(SpiceBrush *red) { switch (red->type) { case SPICE_BRUSH_TYPE_PATTERN: red_put_image(red->u.pattern.pat); break; } } static void red_get_qmask_ptr(RedMemSlotInfo *slots, int group_id, SpiceQMask *red, QXLQMask *qxl, uint32_t flags) { red->flags = qxl->flags; red_get_point_ptr(&red->pos, &qxl->pos); red->bitmap = red_get_image(slots, group_id, qxl->bitmap, flags, TRUE); } static void red_put_qmask(SpiceQMask *red) { red_put_image(red->bitmap); } static void red_get_fill_ptr(RedMemSlotInfo *slots, int group_id, SpiceFill *red, QXLFill *qxl, uint32_t flags) { red_get_brush_ptr(slots, group_id, &red->brush, &qxl->brush, flags); red->rop_descriptor = qxl->rop_descriptor; red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags); } static void red_put_fill(SpiceFill *red) { red_put_brush(&red->brush); red_put_qmask(&red->mask); } static void red_get_opaque_ptr(RedMemSlotInfo *slots, int group_id, SpiceOpaque *red, QXLOpaque *qxl, uint32_t flags) { red->src_bitmap = red_get_image(slots, group_id, qxl->src_bitmap, flags, FALSE); red_get_rect_ptr(&red->src_area, &qxl->src_area); red_get_brush_ptr(slots, group_id, &red->brush, &qxl->brush, flags); red->rop_descriptor = qxl->rop_descriptor; red->scale_mode = qxl->scale_mode; red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags); } static void red_put_opaque(SpiceOpaque *red) { red_put_image(red->src_bitmap); red_put_brush(&red->brush); red_put_qmask(&red->mask); } static int red_get_copy_ptr(RedMemSlotInfo *slots, int group_id, SpiceCopy *red, QXLCopy *qxl, uint32_t flags) { red->src_bitmap = red_get_image(slots, group_id, qxl->src_bitmap, flags, FALSE); if (!red->src_bitmap) { return 1; } red_get_rect_ptr(&red->src_area, &qxl->src_area); red->rop_descriptor = qxl->rop_descriptor; red->scale_mode = qxl->scale_mode; red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags); return 0; } static void red_put_copy(SpiceCopy *red) { red_put_image(red->src_bitmap); red_put_qmask(&red->mask); } static void red_get_blend_ptr(RedMemSlotInfo *slots, int group_id, SpiceBlend *red, QXLBlend *qxl, uint32_t flags) { red->src_bitmap = red_get_image(slots, group_id, qxl->src_bitmap, flags, FALSE); red_get_rect_ptr(&red->src_area, &qxl->src_area); red->rop_descriptor = qxl->rop_descriptor; red->scale_mode = qxl->scale_mode; red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags); } static void red_put_blend(SpiceBlend *red) { red_put_image(red->src_bitmap); red_put_qmask(&red->mask); } static void red_get_transparent_ptr(RedMemSlotInfo *slots, int group_id, SpiceTransparent *red, QXLTransparent *qxl, uint32_t flags) { red->src_bitmap = red_get_image(slots, group_id, qxl->src_bitmap, flags, FALSE); red_get_rect_ptr(&red->src_area, &qxl->src_area); red->src_color = qxl->src_color; red->true_color = qxl->true_color; } static void red_put_transparent(SpiceTransparent *red) { red_put_image(red->src_bitmap); } static void red_get_alpha_blend_ptr(RedMemSlotInfo *slots, int group_id, SpiceAlphaBlend *red, QXLAlphaBlend *qxl, uint32_t flags) { red->alpha_flags = qxl->alpha_flags; red->alpha = qxl->alpha; red->src_bitmap = red_get_image(slots, group_id, qxl->src_bitmap, flags, FALSE); red_get_rect_ptr(&red->src_area, &qxl->src_area); } static void red_get_alpha_blend_ptr_compat(RedMemSlotInfo *slots, int group_id, SpiceAlphaBlend *red, QXLCompatAlphaBlend *qxl, uint32_t flags) { red->alpha = qxl->alpha; red->src_bitmap = red_get_image(slots, group_id, qxl->src_bitmap, flags, FALSE); red_get_rect_ptr(&red->src_area, &qxl->src_area); } static void red_put_alpha_blend(SpiceAlphaBlend *red) { red_put_image(red->src_bitmap); } static bool get_transform(RedMemSlotInfo *slots, int group_id, QXLPHYSICAL qxl_transform, SpiceTransform *dst_transform) { const uint32_t *t = NULL; int error; if (qxl_transform == 0) return FALSE; t = (uint32_t *)get_virt(slots, qxl_transform, sizeof(*dst_transform), group_id, &error); if (!t || error) return FALSE; memcpy(dst_transform, t, sizeof(*dst_transform)); return TRUE; } static void red_get_composite_ptr(RedMemSlotInfo *slots, int group_id, SpiceComposite *red, QXLComposite *qxl, uint32_t flags) { red->flags = qxl->flags; red->src_bitmap = red_get_image(slots, group_id, qxl->src, flags, FALSE); if (get_transform(slots, group_id, qxl->src_transform, &red->src_transform)) red->flags |= SPICE_COMPOSITE_HAS_SRC_TRANSFORM; if (qxl->mask) { red->mask_bitmap = red_get_image(slots, group_id, qxl->mask, flags, FALSE); 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 { red->mask_bitmap = NULL; } 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); } static void red_get_rop3_ptr(RedMemSlotInfo *slots, int group_id, SpiceRop3 *red, QXLRop3 *qxl, uint32_t flags) { red->src_bitmap = red_get_image(slots, group_id, qxl->src_bitmap, flags, FALSE); red_get_rect_ptr(&red->src_area, &qxl->src_area); red_get_brush_ptr(slots, group_id, &red->brush, &qxl->brush, flags); red->rop3 = qxl->rop3; red->scale_mode = qxl->scale_mode; red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags); } static void red_put_rop3(SpiceRop3 *red) { red_put_image(red->src_bitmap); red_put_brush(&red->brush); red_put_qmask(&red->mask); } static int red_get_stroke_ptr(RedMemSlotInfo *slots, int group_id, SpiceStroke *red, QXLStroke *qxl, uint32_t flags) { int error; red->path = red_get_path(slots, group_id, qxl->path); if (!red->path) { return 1; } 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; red->attr.style = spice_malloc_n(style_nseg, sizeof(SPICE_FIXED28_4)); red->attr.style_nseg = style_nseg; spice_assert(qxl->attr.style); buf = (uint8_t *)get_virt(slots, qxl->attr.style, style_nseg * sizeof(QXLFIXED), group_id, &error); if (error) { return error; } memcpy(red->attr.style, buf, style_nseg * sizeof(QXLFIXED)); } else { red->attr.style_nseg = 0; red->attr.style = NULL; } red_get_brush_ptr(slots, group_id, &red->brush, &qxl->brush, flags); red->fore_mode = qxl->fore_mode; red->back_mode = qxl->back_mode; return 0; } static void red_put_stroke(SpiceStroke *red) { red_put_brush(&red->brush); free(red->path); if (red->attr.flags & SPICE_LINE_FLAGS_STYLED) { free(red->attr.style); } } static SpiceString *red_get_string(RedMemSlotInfo *slots, int group_id, QXLPHYSICAL addr) { 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; int glyphs, i; /* use unsigned to prevent integer overflow in multiplication below */ unsigned int bpp = 0; int error; uint16_t qxl_flags, qxl_length; qxl = (QXLString *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return NULL; } chunk_size = red_get_data_chunks_ptr(slots, group_id, get_memslot_id(slots, addr), &chunks, &qxl->chunk); if (!chunk_size) { /* XXX could be a zero sized string.. */ return NULL; } data = red_linearize_chunk(&chunks, chunk_size, &free_data); red_put_data_chunks(&chunks); qxl_size = qxl->data_size; qxl_flags = qxl->flags; qxl_length = qxl->length; spice_assert(chunk_size == qxl_size); if (qxl_flags & SPICE_STRING_FLAGS_RASTER_A1) { bpp = 1; } else if (qxl_flags & SPICE_STRING_FLAGS_RASTER_A4) { bpp = 4; } else if (qxl_flags & SPICE_STRING_FLAGS_RASTER_A8) { bpp = 8; } spice_assert(bpp != 0); start = (QXLRasterGlyph*)data; end = (QXLRasterGlyph*)(data + chunk_size); red_size = sizeof(SpiceString); glyphs = 0; while (start < end) { spice_assert((QXLRasterGlyph*)(&start->data[0]) <= end); glyphs++; glyph_size = start->height * ((start->width * bpp + 7u) / 8u); red_size += sizeof(SpiceRasterGlyph *) + SPICE_ALIGN(sizeof(SpiceRasterGlyph) + glyph_size, 4); /* do the test correctly, we know end - start->data[0] cannot * overflow, don't use start->data[glyph_size] to test for * buffer overflow as this on 32 bit can cause overflow * on the pointer arithmetic */ spice_assert(glyph_size <= (char*) end - (char*) &start->data[0]); start = (QXLRasterGlyph*)(&start->data[glyph_size]); } spice_assert(start <= end); spice_assert(glyphs == qxl_length); red = spice_malloc(red_size); red->length = qxl_length; red->flags = qxl_flags; start = (QXLRasterGlyph*)data; end = (QXLRasterGlyph*)(data + chunk_size); glyph = (SpiceRasterGlyph *)&red->glyphs[red->length]; for (i = 0; i < red->length; i++) { spice_assert((QXLRasterGlyph*)(&start->data[0]) <= end); red->glyphs[i] = glyph; glyph->width = start->width; glyph->height = start->height; red_get_point_ptr(&glyph->render_pos, &start->render_pos); red_get_point_ptr(&glyph->glyph_origin, &start->glyph_origin); glyph_size = glyph->height * ((glyph->width * bpp + 7u) / 8u); /* see above for similar test */ spice_assert(glyph_size <= (char*) end - (char*) &start->data[0]); memcpy(glyph->data, start->data, glyph_size); start = (QXLRasterGlyph*)(&start->data[glyph_size]); glyph = (SpiceRasterGlyph*) (((uint8_t *)glyph) + SPICE_ALIGN(sizeof(SpiceRasterGlyph) + glyph_size, 4)); } if (free_data) { free(data); } return red; } static void red_get_text_ptr(RedMemSlotInfo *slots, int group_id, SpiceText *red, QXLText *qxl, uint32_t flags) { red->str = red_get_string(slots, group_id, qxl->str); red_get_rect_ptr(&red->back_area, &qxl->back_area); red_get_brush_ptr(slots, group_id, &red->fore_brush, &qxl->fore_brush, flags); red_get_brush_ptr(slots, group_id, &red->back_brush, &qxl->back_brush, flags); red->fore_mode = qxl->fore_mode; red->back_mode = qxl->back_mode; } static void red_put_text_ptr(SpiceText *red) { free(red->str); red_put_brush(&red->fore_brush); red_put_brush(&red->back_brush); } static void red_get_whiteness_ptr(RedMemSlotInfo *slots, int group_id, SpiceWhiteness *red, QXLWhiteness *qxl, uint32_t flags) { red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags); } static void red_put_whiteness(SpiceWhiteness *red) { red_put_qmask(&red->mask); } static void red_get_blackness_ptr(RedMemSlotInfo *slots, int group_id, SpiceBlackness *red, QXLBlackness *qxl, uint32_t flags) { red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags); } static void red_put_blackness(SpiceWhiteness *red) { red_put_qmask(&red->mask); } static void red_get_invers_ptr(RedMemSlotInfo *slots, int group_id, SpiceInvers *red, QXLInvers *qxl, uint32_t flags) { red_get_qmask_ptr(slots, group_id, &red->mask, &qxl->mask, flags); } static void red_put_invers(SpiceWhiteness *red) { red_put_qmask(&red->mask); } static void red_get_clip_ptr(RedMemSlotInfo *slots, int group_id, SpiceClip *red, QXLClip *qxl) { red->type = qxl->type; switch (red->type) { case SPICE_CLIP_TYPE_RECTS: red->rects = red_get_clip_rects(slots, group_id, qxl->data); break; } } static void red_put_clip(SpiceClip *red) { switch (red->type) { case SPICE_CLIP_TYPE_RECTS: free(red->rects); break; } } static int red_get_native_drawable(RedMemSlotInfo *slots, int group_id, RedDrawable *red, QXLPHYSICAL addr, uint32_t flags) { QXLDrawable *qxl; int i; int error = 0; qxl = (QXLDrawable *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return error; } red->release_info = &qxl->release_info; red_get_rect_ptr(&red->bbox, &qxl->bbox); red_get_clip_ptr(slots, group_id, &red->clip, &qxl->clip); red->effect = qxl->effect; red->mm_time = qxl->mm_time; red->self_bitmap = qxl->self_bitmap; red_get_rect_ptr(&red->self_bitmap_area, &qxl->self_bitmap_area); red->surface_id = qxl->surface_id; for (i = 0; i < 3; i++) { red->surface_deps[i] = qxl->surfaces_dest[i]; red_get_rect_ptr(&red->surfaces_rects[i], &qxl->surfaces_rects[i]); } red->type = qxl->type; switch (red->type) { case QXL_DRAW_ALPHA_BLEND: red_get_alpha_blend_ptr(slots, group_id, &red->u.alpha_blend, &qxl->u.alpha_blend, flags); break; case QXL_DRAW_BLACKNESS: red_get_blackness_ptr(slots, group_id, &red->u.blackness, &qxl->u.blackness, flags); break; case QXL_DRAW_BLEND: red_get_blend_ptr(slots, group_id, &red->u.blend, &qxl->u.blend, flags); break; case QXL_DRAW_COPY: error = red_get_copy_ptr(slots, group_id, &red->u.copy, &qxl->u.copy, flags); break; case QXL_COPY_BITS: red_get_point_ptr(&red->u.copy_bits.src_pos, &qxl->u.copy_bits.src_pos); break; case QXL_DRAW_FILL: red_get_fill_ptr(slots, group_id, &red->u.fill, &qxl->u.fill, flags); break; case QXL_DRAW_OPAQUE: red_get_opaque_ptr(slots, group_id, &red->u.opaque, &qxl->u.opaque, flags); break; case QXL_DRAW_INVERS: red_get_invers_ptr(slots, group_id, &red->u.invers, &qxl->u.invers, flags); break; case QXL_DRAW_NOP: break; case QXL_DRAW_ROP3: red_get_rop3_ptr(slots, group_id, &red->u.rop3, &qxl->u.rop3, flags); break; case QXL_DRAW_COMPOSITE: red_get_composite_ptr(slots, group_id, &red->u.composite, &qxl->u.composite, flags); break; case QXL_DRAW_STROKE: error = red_get_stroke_ptr(slots, group_id, &red->u.stroke, &qxl->u.stroke, flags); break; case QXL_DRAW_TEXT: red_get_text_ptr(slots, group_id, &red->u.text, &qxl->u.text, flags); break; case QXL_DRAW_TRANSPARENT: red_get_transparent_ptr(slots, group_id, &red->u.transparent, &qxl->u.transparent, flags); break; case QXL_DRAW_WHITENESS: red_get_whiteness_ptr(slots, group_id, &red->u.whiteness, &qxl->u.whiteness, flags); break; default: spice_warning("unknown type %d", red->type); error = 1; break; }; return error; } static int red_get_compat_drawable(RedMemSlotInfo *slots, int group_id, RedDrawable *red, QXLPHYSICAL addr, uint32_t flags) { QXLCompatDrawable *qxl; int error; qxl = (QXLCompatDrawable *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return error; } red->release_info = &qxl->release_info; red_get_rect_ptr(&red->bbox, &qxl->bbox); red_get_clip_ptr(slots, group_id, &red->clip, &qxl->clip); red->effect = qxl->effect; red->mm_time = qxl->mm_time; red->self_bitmap = (qxl->bitmap_offset != 0); red_get_rect_ptr(&red->self_bitmap_area, &qxl->bitmap_area); red->surface_deps[0] = -1; red->surface_deps[1] = -1; red->surface_deps[2] = -1; red->type = qxl->type; switch (red->type) { case QXL_DRAW_ALPHA_BLEND: red_get_alpha_blend_ptr_compat(slots, group_id, &red->u.alpha_blend, &qxl->u.alpha_blend, flags); break; case QXL_DRAW_BLACKNESS: red_get_blackness_ptr(slots, group_id, &red->u.blackness, &qxl->u.blackness, flags); break; case QXL_DRAW_BLEND: red_get_blend_ptr(slots, group_id, &red->u.blend, &qxl->u.blend, flags); break; case QXL_DRAW_COPY: error = red_get_copy_ptr(slots, group_id, &red->u.copy, &qxl->u.copy, flags); break; case QXL_COPY_BITS: red_get_point_ptr(&red->u.copy_bits.src_pos, &qxl->u.copy_bits.src_pos); red->surface_deps[0] = 0; red->surfaces_rects[0].left = red->u.copy_bits.src_pos.x; red->surfaces_rects[0].right = red->u.copy_bits.src_pos.x + (red->bbox.right - red->bbox.left); red->surfaces_rects[0].top = red->u.copy_bits.src_pos.y; red->surfaces_rects[0].bottom = red->u.copy_bits.src_pos.y + (red->bbox.bottom - red->bbox.top); break; case QXL_DRAW_FILL: red_get_fill_ptr(slots, group_id, &red->u.fill, &qxl->u.fill, flags); break; case QXL_DRAW_OPAQUE: red_get_opaque_ptr(slots, group_id, &red->u.opaque, &qxl->u.opaque, flags); break; case QXL_DRAW_INVERS: red_get_invers_ptr(slots, group_id, &red->u.invers, &qxl->u.invers, flags); break; case QXL_DRAW_NOP: break; case QXL_DRAW_ROP3: red_get_rop3_ptr(slots, group_id, &red->u.rop3, &qxl->u.rop3, flags); break; case QXL_DRAW_STROKE: error = red_get_stroke_ptr(slots, group_id, &red->u.stroke, &qxl->u.stroke, flags); break; case QXL_DRAW_TEXT: red_get_text_ptr(slots, group_id, &red->u.text, &qxl->u.text, flags); break; case QXL_DRAW_TRANSPARENT: red_get_transparent_ptr(slots, group_id, &red->u.transparent, &qxl->u.transparent, flags); break; case QXL_DRAW_WHITENESS: red_get_whiteness_ptr(slots, group_id, &red->u.whiteness, &qxl->u.whiteness, flags); break; default: spice_warning("unknown type %d", red->type); error = 1; break; }; return error; } int red_get_drawable(RedMemSlotInfo *slots, int group_id, RedDrawable *red, QXLPHYSICAL addr, uint32_t flags) { int ret; if (flags & QXL_COMMAND_FLAG_COMPAT) { ret = red_get_compat_drawable(slots, group_id, red, addr, flags); } else { ret = red_get_native_drawable(slots, group_id, red, addr, flags); } return ret; } void red_put_drawable(RedDrawable *red) { red_put_clip(&red->clip); if (red->self_bitmap_image) { red_put_image(red->self_bitmap_image); } switch (red->type) { case QXL_DRAW_ALPHA_BLEND: red_put_alpha_blend(&red->u.alpha_blend); break; case QXL_DRAW_BLACKNESS: red_put_blackness(&red->u.blackness); break; case QXL_DRAW_BLEND: red_put_blend(&red->u.blend); break; case QXL_DRAW_COPY: red_put_copy(&red->u.copy); break; case QXL_DRAW_FILL: red_put_fill(&red->u.fill); break; case QXL_DRAW_OPAQUE: red_put_opaque(&red->u.opaque); break; case QXL_DRAW_INVERS: red_put_invers(&red->u.invers); break; case QXL_DRAW_ROP3: red_put_rop3(&red->u.rop3); break; case QXL_DRAW_COMPOSITE: red_put_composite(&red->u.composite); break; case QXL_DRAW_STROKE: red_put_stroke(&red->u.stroke); break; case QXL_DRAW_TEXT: red_put_text_ptr(&red->u.text); break; case QXL_DRAW_TRANSPARENT: red_put_transparent(&red->u.transparent); break; case QXL_DRAW_WHITENESS: red_put_whiteness(&red->u.whiteness); break; } } int red_get_update_cmd(RedMemSlotInfo *slots, int group_id, RedUpdateCmd *red, QXLPHYSICAL addr) { QXLUpdateCmd *qxl; int error; qxl = (QXLUpdateCmd *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return 1; } red->release_info = &qxl->release_info; red_get_rect_ptr(&red->area, &qxl->area); red->update_id = qxl->update_id; red->surface_id = qxl->surface_id; return 0; } void red_put_update_cmd(RedUpdateCmd *red) { /* nothing yet */ } int red_get_message(RedMemSlotInfo *slots, int group_id, RedMessage *red, QXLPHYSICAL addr) { QXLMessage *qxl; int error; /* * security alert: * qxl->data[0] size isn't specified anywhere -> can't verify * luckily this is for debug logging only, * so we can just ignore it by default. */ qxl = (QXLMessage *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return 1; } red->release_info = &qxl->release_info; red->data = qxl->data; return 0; } void red_put_message(RedMessage *red) { /* nothing yet */ } static unsigned int surface_format_to_bpp(uint32_t format) { switch (format) { case SPICE_SURFACE_FMT_1_A: return 1; case SPICE_SURFACE_FMT_8_A: return 8; case SPICE_SURFACE_FMT_16_555: case SPICE_SURFACE_FMT_16_565: return 16; case SPICE_SURFACE_FMT_32_xRGB: case SPICE_SURFACE_FMT_32_ARGB: return 32; } return 0; } int red_get_surface_cmd(RedMemSlotInfo *slots, int group_id, RedSurfaceCmd *red, QXLPHYSICAL addr) { QXLSurfaceCmd *qxl; uint64_t size; int error; unsigned int bpp; qxl = (QXLSurfaceCmd *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return 1; } red->release_info = &qxl->release_info; red->surface_id = qxl->surface_id; red->type = qxl->type; red->flags = qxl->flags; switch (red->type) { case QXL_SURFACE_CMD_CREATE: red->u.surface_create.format = qxl->u.surface_create.format; red->u.surface_create.width = qxl->u.surface_create.width; red->u.surface_create.height = qxl->u.surface_create.height; red->u.surface_create.stride = qxl->u.surface_create.stride; bpp = surface_format_to_bpp(red->u.surface_create.format); /* check if format is valid */ if (!bpp) { return 1; } /* check stride is larger than required bytes */ size = ((uint64_t) red->u.surface_create.width * bpp + 7u) / 8u; /* the uint32_t conversion is here to avoid problems with -2^31 value */ if (red->u.surface_create.stride == G_MININT32 || size > (uint32_t) abs(red->u.surface_create.stride)) { return 1; } /* the multiplication can overflow, also abs(-2^31) may return a negative value */ size = (uint64_t) red->u.surface_create.height * abs(red->u.surface_create.stride); if (size > MAX_DATA_CHUNK) { return 1; } red->u.surface_create.data = (uint8_t*)get_virt(slots, qxl->u.surface_create.data, size, group_id, &error); if (error) { return 1; } break; } return 0; } void red_put_surface_cmd(RedSurfaceCmd *red) { /* nothing yet */ } static int red_get_cursor(RedMemSlotInfo *slots, int group_id, SpiceCursor *red, QXLPHYSICAL addr) { QXLCursor *qxl; RedDataChunk chunks; size_t size; uint8_t *data; bool free_data; int error; qxl = (QXLCursor *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return 1; } red->header.unique = qxl->header.unique; red->header.type = qxl->header.type; red->header.width = qxl->header.width; red->header.height = qxl->header.height; red->header.hot_spot_x = qxl->header.hot_spot_x; red->header.hot_spot_y = qxl->header.hot_spot_y; red->flags = 0; red->data_size = qxl->data_size; size = red_get_data_chunks_ptr(slots, group_id, get_memslot_id(slots, addr), &chunks, &qxl->chunk); red->data_size = MIN(red->data_size, size); data = red_linearize_chunk(&chunks, size, &free_data); red_put_data_chunks(&chunks); if (free_data) { red->data = data; } else { red->data = spice_malloc(size); memcpy(red->data, data, size); } return 0; } static void red_put_cursor(SpiceCursor *red) { free(red->data); } int red_get_cursor_cmd(RedMemSlotInfo *slots, int group_id, RedCursorCmd *red, QXLPHYSICAL addr) { QXLCursorCmd *qxl; int error; qxl = (QXLCursorCmd *)get_virt(slots, addr, sizeof(*qxl), group_id, &error); if (error) { return error; } red->release_info = &qxl->release_info; red->type = qxl->type; switch (red->type) { case QXL_CURSOR_SET: red_get_point16_ptr(&red->u.set.position, &qxl->u.set.position); red->u.set.visible = qxl->u.set.visible; error = red_get_cursor(slots, group_id, &red->u.set.shape, qxl->u.set.shape); break; case QXL_CURSOR_MOVE: red_get_point16_ptr(&red->u.position, &qxl->u.position); break; case QXL_CURSOR_TRAIL: red->u.trail.length = qxl->u.trail.length; red->u.trail.frequency = qxl->u.trail.frequency; break; } return error; } void red_put_cursor_cmd(RedCursorCmd *red) { switch (red->type) { case QXL_CURSOR_SET: red_put_cursor(&red->u.set.shape); break; } }