[cairo] crash copying recording surface to PDF surface with tags

Ben Pfaff blp at cs.stanford.edu
Sat Dec 26 04:59:32 UTC 2020

Hi!  The cairo library is fantastic and I enjoy working with it.
However, I've encountered an invalid memory access bug if I perform
some output enclosed in a tag to a recording surface that has a
defined extent, and then copy the recording surface to a PDF surface.

I've found this problem using cairo packaged for Debian, Debian's
version 1.16.0-4.  I haven't tested cairo from upstream, but I have
appended to this email a simple test program (greatly simplified from
my real program) that reproduces the problem.  It does the following:

	* Creates a recording surface and draws a rectangle in it
	  surrounded by a CAIRO_TAG_DEST destination tag.

	* Creates a PDF surface.

	* Copies the recording surface to the PDF surface.

	* Call cairo_surface_show_page() on the PDF surface.

	* Destroys both surfaces.

To try it, save it as cairo-test.c and compile it with:
	gcc -Wall -Wextra -g cairo-test.c -o cairo-test -lcairo

If you run it, it produces output.pdf.  This isn't interesting, it's
just a 100x100 point PDF with a black rectangle drawn in the middle.

Now turn on glibc malloc perturbation and malloc checking, like this,
and run it again:

    export MALLOC_PERTURB_=165
    export MALLOC_CHECK_=2

On my system, it yields a segmentation fault with this backtrace:

    0x00007ffff7eee801 in _cairo_surface_detach_snapshot (
	snapshot=0xfffffffffffffee8) at ../../../../src/cairo-surface.c:343
    343     ../../../../src/cairo-surface.c: No such file or directory.
    (gdb) bt
    #0  0x00007ffff7eee801 in _cairo_surface_detach_snapshot (
	snapshot=0xfffffffffffffee8) at ../../../../src/cairo-surface.c:343
    #1  0x00007ffff7eee5bc in _cairo_surface_detach_snapshots (
	surface=0x7ffff7f5ede0 <_cairo_surface_nil>)
	at ../../../../src/cairo-surface.c:334
    #2  _cairo_surface_flush (surface=0x7ffff7f5ede0 <_cairo_surface_nil>, 
	flags=0) at ../../../../src/cairo-surface.c:1626
    #3  0x00007ffff7eeaa03 in _cairo_surface_snapshot_flush (
	abstract_surface=0x555555560160, flags=0)
	at ../../../../src/cairo-surface-snapshot.c:74
    #4  0x00007ffff7eee735 in _cairo_surface_finish_snapshots (
	surface=0x555555560160) at ../../../../src/cairo-surface.c:1019
    #5  INT_cairo_surface_destroy (surface=0x555555560160)
	at ../../../../src/cairo-surface.c:963
    #6  0x00007ffff7eee5bc in _cairo_surface_detach_snapshots (
	surface=0x55555555c0d0) at ../../../../src/cairo-surface.c:334
    #7  _cairo_surface_flush (surface=0x55555555c0d0, flags=0)
	at ../../../../src/cairo-surface.c:1626
    #8  0x00007ffff7eee735 in _cairo_surface_finish_snapshots (
	surface=0x55555555c0d0) at ../../../../src/cairo-surface.c:1019
    #9  INT_cairo_surface_destroy (surface=0x55555555c0d0)
	at ../../../../src/cairo-surface.c:963
    #10 0x00005555555554e9 in main (argc=1, argv=0x7fffffffe038)
	at cairo-test.c:65

"valgrind ./cairo-test" reports something similar:

    Invalid read of size 8
       at 0x48EF801: _cairo_surface_detach_snapshot (cairo-surface.c:343)
       by 0x48EF5BB: _cairo_surface_detach_snapshots (cairo-surface.c:334)
       by 0x48EF5BB: _cairo_surface_flush (cairo-surface.c:1626)
       by 0x48EBA02: _cairo_surface_snapshot_flush (cairo-surface-snapshot.c:74)
       by 0x48EF734: _cairo_surface_finish_snapshots (cairo-surface.c:1019)
       by 0x48EF734: cairo_surface_destroy (cairo-surface.c:963)
       by 0x48EF5BB: _cairo_surface_detach_snapshots (cairo-surface.c:334)
       by 0x48EF5BB: _cairo_surface_flush (cairo-surface.c:1626)
       by 0x48EF734: _cairo_surface_finish_snapshots (cairo-surface.c:1019)
       by 0x48EF734: cairo_surface_destroy (cairo-surface.c:963)
       by 0x1094E8: main (cairo-test.c:65)
    Address 0xffffffffffffffe0 is not stack'd, malloc'd or (recently)

== Tags ==

The problem is related to the tag.  If you run it with --no-tag to
avoid creating the tag, there is no problem, with or without valgrind.

== Extents on recording surface ==

The problem is related to specifying extents on the recording surface.
If you run it with --no-extents to avoid specifying the extents, there
is no problem.

However, "valgrind --leak-check=full ./cairo-test --no-extents"
does report a leak:

    384 bytes in 1 blocks are definitely lost in loss record 4 of 11
       at 0x483877F: malloc (vg_replace_malloc.c:307)
       by 0x48EBAF9: _cairo_surface_snapshot (cairo-surface-snapshot.c:264)
       by 0x48C986B: _cairo_pattern_init_snapshot (cairo-pattern.c:422)
       by 0x48DA700: _cairo_recording_surface_paint (cairo-recording-surface.c:743)
       by 0x48F0277: _cairo_surface_paint (cairo-surface.c:2198)
       by 0x48F0277: _cairo_surface_paint (cairo-surface.c:2171)
       by 0x48F0277: _cairo_surface_paint (cairo-surface.c:2198)
       by 0x48F0277: _cairo_surface_paint (cairo-surface.c:2171)
       by 0x48A76D5: _cairo_gstate_paint (cairo-gstate.c:1061)
       by 0x48FD044: cairo_paint (cairo.c:2220)
       by 0x10930C: copy_surface (cairo-test.c:25)
       by 0x1094CA: main (cairo-test.c:57)

== cairo_surface_show_page() ==

The problem manifests a little differently if one deletes the call to
cairo_surface_show_page().  Run it with --no-show-page to do this.
This crashes with or without valgrind.  valgrind reports the error
location as:

    Invalid read of size 4
       at 0x48B2953: _cairo_image_analyze_transparency (cairo-image-surface.c:1214)
       by 0x48D959C: _cairo_recording_surface_merge_source_attributes.isra.9 (cairo-recording-surface.c:1779)
       by 0x48D9CDF: _cairo_recording_surface_replay_internal (cairo-recording-surface.c:2007)
       by 0x48DB04A: _cairo_recording_surface_replay_and_create_regions (cairo-recording-surface.c:2197)
       by 0x48BBC2B: _paint_page (cairo-paginated-surface.c:417)
       by 0x48BC232: _cairo_paginated_surface_show_page (cairo-paginated-surface.c:583)
       by 0x48BC31F: _cairo_paginated_surface_finish (cairo-paginated-surface.c:205)
       by 0x48EE2B1: _cairo_surface_finish (cairo-surface.c:1030)
       by 0x48EF757: cairo_surface_destroy (cairo-surface.c:970)
       by 0x1094F4: main (cairo-test.c:66)
     Address 0x0 is not stack'd, malloc'd or (recently) free'd

I'd very much appreciate your guidance!



-8<--------------------------cut here-------------------------->8--

#include <cairo/cairo-pdf.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void
draw_rectangle (cairo_surface_t *surface, bool add_tag)
  cairo_t *cr = cairo_create (surface);
  if (add_tag)
    cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='mydest'");
  cairo_rectangle (cr, 25, 25, 50, 50);
  cairo_stroke (cr);
  if (add_tag)
    cairo_tag_end (cr, CAIRO_TAG_DEST);
  cairo_destroy (cr);

static void
copy_surface (cairo_surface_t *dst, cairo_surface_t *src)
  cairo_t *cr = cairo_create (dst);
  cairo_set_source_surface (cr, src, 0, 0);
  cairo_paint (cr);
  cairo_destroy (cr);

main (int argc, char *argv[])
  bool add_tag = true;
  bool use_extents = true;
  bool show_page = true;
  for (int i = 1; i < argc; i++)
    if (!strcmp (argv[i], "--no-tag"))
      add_tag = false;
    else if (!strcmp (argv[i], "--no-extents"))
      use_extents = false;
    else if (!strcmp (argv[i], "--no-show-page"))
      show_page = false;
        fprintf (stderr, "%s: not one of the known options "
                 "(--no-tag, --no-extents, --no-show-page)\n", argv[i]);
        exit (1);

  /* Create recording surface and draw a rectangle on it. */
  cairo_rectangle_t extents = { .width = 100, .height = 100 };
  cairo_surface_t *recording = cairo_recording_surface_create (
    CAIRO_CONTENT_COLOR_ALPHA, use_extents ? &extents : NULL);
  draw_rectangle (recording, add_tag);

  /* Create PDF surface and copy the recording surface to it. */
  cairo_surface_t *pdf = cairo_pdf_surface_create ("output.pdf", 100.0, 100.0);
  copy_surface (pdf, recording);

  if (show_page)
      /* Bang! */
      cairo_surface_show_page (pdf);

  cairo_surface_destroy (recording); /* Alternate bang! */
  cairo_surface_destroy (pdf);

  return 0;

 * Local variables:
 * compile-command: "gcc -Wall -Wextra -g cairo-test.c -o cairo-test -lcairo"
 * End:

More information about the cairo mailing list