diff --git a/README b/README
index e90519811..37524abc9 100644
--- a/README
+++ b/README
@@ -4,7 +4,7 @@ librsvg-og
This is librsvg-og (read: librsvg "old gen") - A small library to render
Scalable Vector Graphics (SVG). It has been created as a fork of the original
librsvg of version 2.40.20, which was the last stable release untainted by rust
-additions. librsvg-ng performs the same task as the original librsvg, to render
+additions. librsvg-og performs the same task as the original librsvg, to render
SVG files to Cairo surfaces, and should be a drop-in replacement for librsvg,
assuming no API-breaking changes by upstream.
diff --git a/configure.ac b/configure.ac
index 3b1ebe869..35dcd47eb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,11 +1,11 @@
m4_define([rsvg_major_version],[2])
m4_define([rsvg_minor_version],[40])
-m4_define([rsvg_micro_version],[20])
+m4_define([rsvg_micro_version],[21])
m4_define([rsvg_extra_version],[])
m4_define([rsvg_version],[rsvg_major_version.rsvg_minor_version.rsvg_micro_version()rsvg_extra_version])
m4_define([rsvg_lt_version_info],m4_eval(rsvg_major_version + rsvg_minor_version):rsvg_micro_version:rsvg_minor_version)
-AC_INIT([RSVG],[rsvg_version],[https://bugzilla.gnome.org/enter_bug.cgi?product=librsvg],[librsvg])
+AC_INIT([RSVG],[rsvg_version],[https://github.com/oaken-source/librsvg-og/issues],[librsvg])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_SRCDIR([rsvg.h])
diff --git a/rsvg-base.c b/rsvg-base.c
index b637f93bf..756f149ae 100644
--- a/rsvg-base.c
+++ b/rsvg-base.c
@@ -705,12 +705,36 @@ rsvg_start_xinclude (RsvgHandle * ctx, RsvgPropertyBag * atts)
/* end xinclude */
+static gboolean
+loading_limits_exceeded (RsvgHandle *handle)
+{
+ /* This is a mitigation for SVG files which create millions of elements
+ * in an attempt to exhaust memory. We don't allow loading more than
+ * this number of elements during the initial streaming load process.
+ */
+ return handle->priv->num_loaded_elements > 200000;
+}
+
static void
rsvg_start_element (void *data, const xmlChar * name, const xmlChar ** atts)
{
RsvgPropertyBag *bag;
RsvgHandle *ctx = (RsvgHandle *) data;
+ /* In a different way from librsvg 2.42, we do the following check here, not
+ * in rsvg_standard_element_start() as it is done there. This is because
+ * librsvg 2.40 still creates nodes for
and elements, and
+ * we'd like to prevent unbounded memory consuption for those elements, too.
+ */
+ if (loading_limits_exceeded (ctx)) {
+ g_set_error (ctx->priv->error, RSVG_ERROR, 0, "instancing limit");
+
+ xmlStopParser (ctx->priv->ctxt);
+ return;
+ }
+
+ ctx->priv->num_loaded_elements += 1;
+
bag = rsvg_property_bag_new ((const char **) atts);
if (ctx->priv->handler) {
@@ -1387,6 +1411,7 @@ rsvg_handle_get_dimensions_sub (RsvgHandle * handle, RsvgDimensionData * dimensi
RsvgNodeSvg *root = NULL;
RsvgNode *sself = NULL;
RsvgBbox bbox;
+ gboolean retval = FALSE;
gboolean handle_subelement = TRUE;
@@ -1446,6 +1471,12 @@ rsvg_handle_get_dimensions_sub (RsvgHandle * handle, RsvgDimensionData * dimensi
rsvg_node_draw (handle->priv->treebase, draw, 0);
bbox = RSVG_CAIRO_RENDER (draw->render)->bbox;
+ if (rsvg_drawing_ctx_limits_exceeded (draw)) {
+ retval = FALSE;
+ } else {
+ retval = TRUE;
+ }
+
cairo_restore (cr);
rsvg_state_pop (draw);
rsvg_drawing_ctx_free (draw);
@@ -1463,6 +1494,8 @@ rsvg_handle_get_dimensions_sub (RsvgHandle * handle, RsvgDimensionData * dimensi
dimension_data->height = (int) (_rsvg_css_hand_normalize_length (&root->h, handle->priv->dpi_y,
bbox.rect.height + bbox.rect.y * 2,
12) + 0.5);
+
+ retval = TRUE;
}
dimension_data->em = dimension_data->width;
@@ -1472,7 +1505,7 @@ rsvg_handle_get_dimensions_sub (RsvgHandle * handle, RsvgDimensionData * dimensi
(*handle->priv->size_func) (&dimension_data->width, &dimension_data->height,
handle->priv->user_data);
- return TRUE;
+ return retval;
}
/**
@@ -1498,7 +1531,7 @@ rsvg_handle_get_position_sub (RsvgHandle * handle, RsvgPositionData * position_d
RsvgDimensionData dimension_data;
cairo_surface_t *target = NULL;
cairo_t *cr = NULL;
- gboolean ret = FALSE;
+ gboolean retval = FALSE;
g_return_val_if_fail (handle, FALSE);
g_return_val_if_fail (position_data, FALSE);
@@ -1544,6 +1577,12 @@ rsvg_handle_get_position_sub (RsvgHandle * handle, RsvgPositionData * position_d
rsvg_node_draw (handle->priv->treebase, draw, 0);
bbox = RSVG_CAIRO_RENDER (draw->render)->bbox;
+ if (rsvg_drawing_ctx_limits_exceeded (draw)) {
+ retval = FALSE;
+ } else {
+ retval = TRUE;
+ }
+
cairo_restore (cr);
rsvg_state_pop (draw);
rsvg_drawing_ctx_free (draw);
@@ -1560,15 +1599,13 @@ rsvg_handle_get_position_sub (RsvgHandle * handle, RsvgPositionData * position_d
(*handle->priv->size_func) (&dimension_data.width, &dimension_data.height,
handle->priv->user_data);
- ret = TRUE;
-
bail:
if (cr)
cairo_destroy (cr);
if (target)
cairo_surface_destroy (target);
- return ret;
+ return retval;
}
/**
@@ -2166,6 +2203,52 @@ rsvg_push_discrete_layer (RsvgDrawingCtx * ctx)
ctx->render->push_discrete_layer (ctx);
}
+void
+rsvg_drawing_ctx_increase_num_elements_acquired (RsvgDrawingCtx *draw_ctx)
+{
+ draw_ctx->num_elements_acquired++;
+}
+
+/* This is a mitigation for the security-related bugs:
+ * https://gitlab.gnome.org/GNOME/librsvg/issues/323
+ * https://gitlab.gnome.org/GNOME/librsvg/issues/515
+ *
+ * Imagine the XML [billion laughs attack], but done in SVG's terms:
+ *
+ * - #323 above creates deeply nested groups of `` elements.
+ * The first one references the second one ten times, the second one
+ * references the third one ten times, and so on. In the file given,
+ * this causes 10^17 objects to be rendered. While this does not
+ * exhaust memory, it would take a really long time.
+ *
+ * - #515 has deeply nested references of `` elements. Each
+ * object inside each pattern has an attribute
+ * fill="url(#next_pattern)", so the number of final rendered objects
+ * grows exponentially.
+ *
+ * We deal with both cases by placing a limit on how many references
+ * will be resolved during the SVG rendering process, that is,
+ * how many `url(#foo)` will be resolved.
+ *
+ * [billion laughs attack]: https://bitbucket.org/tiran/defusedxml
+ */
+gboolean
+rsvg_drawing_ctx_limits_exceeded (RsvgDrawingCtx *draw_ctx)
+{
+ return draw_ctx->num_elements_acquired > 500000;
+}
+
+RsvgNode *
+rsvg_drawing_ctx_acquire_node_ref (RsvgDrawingCtx * ctx, RsvgNode *node)
+{
+ if (g_slist_find (ctx->acquired_nodes, node))
+ return NULL;
+
+ ctx->acquired_nodes = g_slist_prepend (ctx->acquired_nodes, node);
+
+ return node;
+}
+
/*
* rsvg_acquire_node:
* @ctx: The drawing context in use
@@ -2186,16 +2269,18 @@ rsvg_acquire_node (RsvgDrawingCtx * ctx, const char *url)
{
RsvgNode *node;
+ if (url == NULL)
+ return NULL;
+
+ rsvg_drawing_ctx_increase_num_elements_acquired (ctx);
+ if (rsvg_drawing_ctx_limits_exceeded (ctx))
+ return NULL;
+
node = rsvg_defs_lookup (ctx->defs, url);
if (node == NULL)
return NULL;
- if (g_slist_find (ctx->acquired_nodes, node))
- return NULL;
-
- ctx->acquired_nodes = g_slist_prepend (ctx->acquired_nodes, node);
-
- return node;
+ return rsvg_drawing_ctx_acquire_node_ref (ctx, node);
}
/*
diff --git a/rsvg-cairo-render.c b/rsvg-cairo-render.c
index 19ba53270..1f4b08568 100644
--- a/rsvg-cairo-render.c
+++ b/rsvg-cairo-render.c
@@ -174,6 +174,7 @@ rsvg_cairo_new_drawing_ctx (cairo_t * cr, RsvgHandle * handle)
draw->dpi_y = handle->priv->dpi_y;
draw->vb.rect.width = data.em;
draw->vb.rect.height = data.ex;
+ draw->num_elements_acquired = 0;
draw->pango_context = NULL;
draw->drawsub_stack = NULL;
draw->acquired_nodes = NULL;
@@ -219,6 +220,7 @@ rsvg_handle_render_cairo_sub (RsvgHandle * handle, cairo_t * cr, const char *id)
{
RsvgDrawingCtx *draw;
RsvgNode *drawsub = NULL;
+ gboolean retval = FALSE;
g_return_val_if_fail (handle != NULL, FALSE);
@@ -247,11 +249,17 @@ rsvg_handle_render_cairo_sub (RsvgHandle * handle, cairo_t * cr, const char *id)
rsvg_node_draw ((RsvgNode *) handle->priv->treebase, draw, 0);
+ if (rsvg_drawing_ctx_limits_exceeded (draw)) {
+ retval = FALSE;
+ } else {
+ retval = TRUE;
+ }
+
cairo_restore (cr);
rsvg_state_pop (draw);
rsvg_drawing_ctx_free (draw);
- return TRUE;
+ return retval;
}
/**
diff --git a/rsvg-private.h b/rsvg-private.h
index 8c3083dc2..82157bc21 100644
--- a/rsvg-private.h
+++ b/rsvg-private.h
@@ -167,6 +167,7 @@ struct RsvgHandlePrivate {
*/
RsvgSaxHandler *handler;
int handler_nest;
+ gsize num_loaded_elements;
GHashTable *entities; /* g_malloc'd string -> xmlEntityPtr */
@@ -203,6 +204,7 @@ struct RsvgDrawingCtx {
RsvgState *state;
GError **error;
RsvgDefs *defs;
+ gsize num_elements_acquired;
PangoContext *pango_context;
double dpi_x, dpi_y;
RsvgViewBox vb;
@@ -369,6 +371,10 @@ void rsvg_pop_discrete_layer (RsvgDrawingCtx * ctx);
G_GNUC_INTERNAL
void rsvg_push_discrete_layer (RsvgDrawingCtx * ctx);
G_GNUC_INTERNAL
+gboolean rsvg_drawing_ctx_limits_exceeded (RsvgDrawingCtx *draw_ctx);
+G_GNUC_INTERNAL
+RsvgNode *rsvg_drawing_ctx_acquire_node_ref (RsvgDrawingCtx * ctx, RsvgNode *node);
+G_GNUC_INTERNAL
RsvgNode *rsvg_acquire_node (RsvgDrawingCtx * ctx, const char *url);
G_GNUC_INTERNAL
void rsvg_release_node (RsvgDrawingCtx * ctx, RsvgNode *node);
diff --git a/rsvg-structure.c b/rsvg-structure.c
index 3c972e50a..88b7ce254 100644
--- a/rsvg-structure.c
+++ b/rsvg-structure.c
@@ -40,6 +40,9 @@ rsvg_node_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate)
RsvgState *state;
GSList *stacksave;
+ if (rsvg_drawing_ctx_limits_exceeded (ctx))
+ return;
+
state = self->state;
stacksave = ctx->drawsub_stack;
@@ -159,27 +162,11 @@ rsvg_node_group_pack (RsvgNode * self, RsvgNode * child)
child->parent = self;
}
-static gboolean
-rsvg_node_is_ancestor (RsvgNode * potential_ancestor, RsvgNode * potential_descendant)
-{
- /* work our way up the family tree */
- while (TRUE) {
- if (potential_ancestor == potential_descendant)
- return TRUE;
- else if (potential_descendant->parent == NULL)
- return FALSE;
- else
- potential_descendant = potential_descendant->parent;
- }
-
- g_assert_not_reached ();
- return FALSE;
-}
-
static void
rsvg_node_use_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate)
{
RsvgNodeUse *use = (RsvgNodeUse *) self;
+ RsvgNode *self_acquired = NULL;
RsvgNode *child;
RsvgState *state;
cairo_matrix_t affine;
@@ -191,14 +178,26 @@ rsvg_node_use_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate)
rsvg_state_reinherit_top (ctx, self->state, dominate);
- if (use->link == NULL)
- return;
+ /* is an element that is used directly, unlike
+ * , which is used through a fill="url(#...)"
+ * reference. However, will always reference another
+ * element, potentially itself or an ancestor of itself (or
+ * another which references the first one, etc.). So,
+ * we acquire the element itself so that circular
+ * references can be caught.
+ */
+ self_acquired = rsvg_drawing_ctx_acquire_node_ref (ctx, self);
+ if (!self_acquired) {
+ goto out;
+ }
+
+ if (use->link == NULL) {
+ goto out;
+ }
+
child = rsvg_acquire_node (ctx, use->link);
- if (!child)
- return;
- else if (rsvg_node_is_ancestor (child, self)) { /* or, if we're 'ing ourself */
- rsvg_release_node (ctx, child);
- return;
+ if (!child) {
+ goto out;
}
state = rsvg_current_state (ctx);
@@ -251,6 +250,12 @@ rsvg_node_use_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate)
rsvg_release_node (ctx, child);
}
+
+out:
+
+ if (self_acquired) {
+ rsvg_release_node (ctx, self_acquired);
+ }
}
static void
diff --git a/tests/Makefile.am b/tests/Makefile.am
index de1e6198f..637988c6a 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,12 +1,14 @@
include $(top_srcdir)/glib-tap.mk
+# Keep "errors" at the end; they are the slowest to run
test_programs = \
loading \
rsvg-test \
crash \
styles \
render-crash \
- dimensions
+ dimensions \
+ errors
test_utils_common_sources = \
test-utils.c \
@@ -16,6 +18,10 @@ rsvg_test_SOURCES = \
rsvg-test.c \
$(test_utils_common_sources)
+errors_SOURCES = \
+ errors.c \
+ $(test_utils_common_sources)
+
crash_SOURCES = \
crash.c \
$(test_utils_common_sources)
@@ -51,6 +57,7 @@ dist_installed_test_data = \
$(wildcard $(srcdir)/resources/*) \
$(wildcard $(srcdir)/fixtures/crash/*.svg) \
$(wildcard $(srcdir)/fixtures/crash/*.png) \
+ $(wildcard $(srcdir)/fixtures/errors/*) \
$(wildcard $(srcdir)/fixtures/loading/*) \
$(wildcard $(srcdir)/fixtures/reftests/*.svg) \
$(wildcard $(srcdir)/fixtures/reftests/*.png) \
diff --git a/tests/errors.c b/tests/errors.c
new file mode 100644
index 000000000..1eaf86c88
--- /dev/null
+++ b/tests/errors.c
@@ -0,0 +1,100 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 nowrap ai expandtab sw=4: */
+
+#include "config.h"
+
+#include
+#include
+
+#include "rsvg.h"
+#include "test-utils.h"
+
+/* These tests are meant to test the error handlers in librsvg. As of 2.44.x we
+ * don't have a public API that can actually report detailed errors; we just
+ * report a boolean success value from the rendering functions. In time, we can
+ * add a richer API and test for specific errors here.
+ */
+
+static char *
+get_test_filename (const char *basename) {
+ return g_build_filename (test_utils_get_test_data_path (),
+ "errors",
+ basename,
+ NULL);
+}
+
+static void
+test_loading_error (gconstpointer data)
+{
+ const char *basename = data;
+ char *filename = get_test_filename (basename);
+ RsvgHandle *handle;
+ GError *error = NULL;
+
+ handle = rsvg_handle_new_from_file (filename, &error);
+ g_free (filename);
+
+ g_assert (handle == NULL);
+ g_assert (g_error_matches (error, RSVG_ERROR, RSVG_ERROR_FAILED));
+
+ g_error_free (error);
+}
+
+static void
+test_instancing_limit (gconstpointer data)
+{
+ const char *basename = data;
+ char *filename = get_test_filename (basename);
+ RsvgHandle *handle;
+ GError *error = NULL;
+ cairo_surface_t *surf;
+ cairo_t *cr;
+
+ handle = rsvg_handle_new_from_file (filename, &error);
+ g_free (filename);
+ g_assert (handle != NULL);
+ g_assert (error == NULL);
+
+ surf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 11);
+ cr = cairo_create (surf);
+
+ g_assert (!rsvg_handle_render_cairo (handle, cr));
+
+ g_object_unref (handle);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_data_func_full ("/errors/instancing_limit/323-nested-use.svg",
+ "323-nested-use.svg",
+ test_instancing_limit,
+ NULL);
+
+ g_test_add_data_func_full ("/errors/instancing_limit/515-pattern-billion-laughs.svg",
+ "515-pattern-billion-laughs.svg",
+ test_instancing_limit,
+ NULL);
+
+ g_test_add_data_func_full ("/errors/instancing_limit/308-use-self-ref.svg",
+ "308-use-self-ref.svg",
+ test_instancing_limit,
+ NULL);
+ g_test_add_data_func_full ("/errors/instancing_limit/308-recursive-use.svg",
+ "308-recursive-use.svg",
+ test_instancing_limit,
+ NULL);
+ g_test_add_data_func_full ("/errors/instancing_limit/308-doubly-recursive-use.svg",
+ "308-doubly-recursive-use.svg",
+ test_instancing_limit,
+ NULL);
+
+ g_test_add_data_func_full ("/errors/515-too-many-elements.svgz",
+ "515-too-many-elements.svgz",
+ test_loading_error,
+ NULL);
+
+ return g_test_run ();
+}
diff --git a/tests/fixtures/errors/308-doubly-recursive-use.svg b/tests/fixtures/errors/308-doubly-recursive-use.svg
new file mode 100644
index 000000000..9b248a6fb
--- /dev/null
+++ b/tests/fixtures/errors/308-doubly-recursive-use.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/fixtures/errors/308-recursive-use.svg b/tests/fixtures/errors/308-recursive-use.svg
new file mode 100644
index 000000000..f5d00bf29
--- /dev/null
+++ b/tests/fixtures/errors/308-recursive-use.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/fixtures/errors/308-use-self-ref.svg b/tests/fixtures/errors/308-use-self-ref.svg
new file mode 100644
index 000000000..dbf14c54c
--- /dev/null
+++ b/tests/fixtures/errors/308-use-self-ref.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/fixtures/errors/323-nested-use.svg b/tests/fixtures/errors/323-nested-use.svg
new file mode 100644
index 000000000..075b48eb8
--- /dev/null
+++ b/tests/fixtures/errors/323-nested-use.svg
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/fixtures/errors/515-pattern-billion-laughs.svg b/tests/fixtures/errors/515-pattern-billion-laughs.svg
new file mode 100644
index 000000000..1cb6cbe8c
--- /dev/null
+++ b/tests/fixtures/errors/515-pattern-billion-laughs.svg
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/fixtures/errors/515-too-many-elements.svgz b/tests/fixtures/errors/515-too-many-elements.svgz
new file mode 100644
index 000000000..a7f7cf678
Binary files /dev/null and b/tests/fixtures/errors/515-too-many-elements.svgz differ