[Spice-devel] [PATCH] Avoid NULL-dereference if canvas_get_image errors out
Christophe de Dinechin
christophe.de.dinechin at gmail.com
Tue Jul 3 13:16:19 UTC 2018
> On 3 Jul 2018, at 11:47, Christophe Fergeau <cfergeau at redhat.com> wrote:
>
> On Fri, Jun 29, 2018 at 05:21:22PM +0200, Christophe de Dinechin wrote:
>> From: Christophe de Dinechin <dinechin at redhat.com>
>>
>> In some error cases, canvas_get_image may return NULL.
>> When this happens, calls like pixman_image_get_width(s)
>> will crash. Add additional error paths to deal with
>> these cases.
>
> I assume you don't have a reproducer for this, this is just something
> you noticed while looking at that code?
During the code review of some recent patches Frediano sent, to understand the impact of returning NULL.
I did not have a reproducer, but I did some testing using using fault injection, e.g.:
SPICE_TRACES=canvas_get_image=13 spicy -p 5900 -h turbo
[snip]
[539 0.328228] canvas_get_image: Get image canvas 0x7f8c90003c00 image 0x7f8c8bc2ba50 original 0 real_get 1
[540 0.328291] spice_critical: condition `src_image != NULL’ failed
Basically with fault injection code like this:
diff --git a/common/canvas_base.c b/common/canvas_base.c
index 6bf6e5d..c53e80b 100644
--- a/common/canvas_base.c
+++ b/common/canvas_base.c
@@ -1128,6 +1128,9 @@ static pixman_image_t *get_surface_from_canvas(CanvasBase *canvas,
* you have to be able to handle any image format. This is useful to avoid
* e.g. losing alpha when blending a argb32 image on a rgb16 surface.
*/
+
+RECORDER_DEFINE(canvas_get_image, 32, "Canvas get image");
+
static pixman_image_t *canvas_get_image_internal(CanvasBase *canvas, SpiceImage *image,
int want_original, int real_get)
{
@@ -1136,6 +1139,15 @@ static pixman_image_t *canvas_get_image_internal(CanvasBase *canvas, SpiceImage
pixman_format_code_t wanted_format, surface_format;
int saved_want_original;
+ record(canvas_get_image, "Get image canvas %p image %p original %d real_get %d",
+ canvas, image, want_original, real_get);
+ if (RECORDER_TRACE(canvas_get_image) > 10) {
+ RECORDER_TRACE(canvas_get_image)--;
+ if ((RECORDER_TRACE(canvas_get_image) & 0xC) == 0xC) {
+ return NULL;
+ }
+ }
+
/* When touching, only really allocate if we need to cache, or
* if we're loading a GLZ stream (since those need inter-thread communication
* to happen which breaks if we don't. */
That being said, I obviously I did not test all paths.
>
>>
>> Signed-off-by: Christophe de Dinechin <dinechin at redhat.com>
>> ---
>> common/canvas_base.c | 38 +++++++++++++++++++++++++++++++++++---
>> 1 file changed, 35 insertions(+), 3 deletions(-)
>>
>> diff --git a/common/canvas_base.c b/common/canvas_base.c
>> index 6bf6e5d..bbaaf96 100644
>> --- a/common/canvas_base.c
>> +++ b/common/canvas_base.c
>> @@ -3072,6 +3072,7 @@ static void canvas_draw_stroke(SpiceCanvas *spice_canvas, SpiceRect *bbox,
>> gc.tile = canvas_get_image(canvas,
>> stroke->brush.u.pattern.pat,
>> FALSE);
>> + spice_return_if_fail(gc.tile != NULL);
>
> I'd favour g_return_if_fail(), I'd really like to kill these various
> spice_* calls mirroring glib API. And spice_return_if_fail() will
> abort() I think.
That’s inconsistent with the rest of the file (there are currently 47 instances of spice_return macros in canvas_base.c).
What about doing a first pass that replaces all of them with g_return_if_fail? That would be another patch, specifically to kill that macro. I sent the patches. If they get acked before I resubmit this one, then I’ll fix it here too.
>
>> }
>> gc.tile_offset_x = stroke->brush.u.pattern.pos.x;
>> gc.tile_offset_y = stroke->brush.u.pattern.pos.y;
>> @@ -3158,6 +3159,10 @@ static void canvas_draw_rop3(SpiceCanvas *spice_canvas, SpiceRect *bbox,
>> heigth = bbox->bottom - bbox->top;
>>
>> d = canvas_get_image_from_self(spice_canvas, bbox->left, bbox->top, width, heigth, FALSE);
>> + if (d == NULL) {
>> + spice_critical("no rop3 destination");
>
> spice_critical calls abort() too
Depending on context, it may. So what? Are you suggesting to do something else?
>
>> + goto d_error;
>> + }
>> surface_canvas = canvas_get_surface(canvas, rop3->src_bitmap);
>> if (surface_canvas) {
>> s = surface_canvas->ops->get_image(surface_canvas, FALSE);
>> @@ -3176,10 +3181,14 @@ static void canvas_draw_rop3(SpiceCanvas *spice_canvas, SpiceRect *bbox,
>> src_pos.x = rop3->src_area.left;
>> src_pos.y = rop3->src_area.top;
>> }
>> + if (s == NULL) {
>> + spice_critical("no rop3 source");
>> + goto s_error;
>> + }
>> if (pixman_image_get_width(s) - src_pos.x < width ||
>> pixman_image_get_height(s) - src_pos.y < heigth) {
>> spice_critical("bad src bitmap size");
>> - return;
>> + goto s_error;
>> }
>> if (rop3->brush.type == SPICE_BRUSH_TYPE_PATTERN) {
>> SpiceCanvas *_surface_canvas;
>> @@ -3191,6 +3200,12 @@ static void canvas_draw_rop3(SpiceCanvas *spice_canvas, SpiceRect *bbox,
>> } else {
>> p = canvas_get_image(canvas, rop3->brush.u.pattern.pat, FALSE);
>> }
>> + if (p == NULL) {
>> + spice_critical("no rop3 pattern");
>> + /* degrade to color only */
>> + do_rop3_with_color(rop3->rop3, d, s, &src_pos, rop3->brush.u.color);
>
> Could you test this? If not, I feel a bit uncomfortable adding such a
> fallback.
Thinking more about it, I agree with both Frediano and you, will remove it in next iteration.
>
>> + goto p_error;
>> + }
>> SpicePoint pat_pos;
>>
>> pat_pos.x = (bbox->left - rop3->brush.u.pattern.pos.x) % pixman_image_get_width(p);
>> @@ -3200,14 +3215,16 @@ static void canvas_draw_rop3(SpiceCanvas *spice_canvas, SpiceRect *bbox,
>> } else {
>> do_rop3_with_color(rop3->rop3, d, s, &src_pos, rop3->brush.u.color);
>> }
>> +p_error:
>
> Do we need 3 labels with very similar names? I'd think something like
> this could work?
>
> error:
> if (s != NULL) {
> pixman_image_unref(s);
>
> spice_canvas->ops->blit_image(spice_canvas, &dest_region, d,
> bbox->left,
> bbox->top);
> }
>
> if (d != NULL) {
> pixman_image_unref(d);
> }
>
> pixman_region32_fini(&dest_region);
I find this harder to read that way. But I’ll rewrite it.
>
> Christophe
More information about the Spice-devel
mailing list