[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
./cairo-test
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!
Thanks,
Ben.
-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);
}
int
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;
else
{
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