From faec595a1721a2496e9c258917facbb564f85854 Mon Sep 17 00:00:00 2001 From: rpm-build Date: Wed, 13 May 2020 17:53:13 -0500 Subject: [PATCH] CVE-2019-20446.patch --- librsvg/rsvg-base.c | 90 +++++++++--- librsvg/rsvg-private.h | 5 +- rsvg_internals/src/drawing_ctx.rs | 23 ++-- rsvg_internals/src/structure.rs | 21 ++- tests/errors.c | 52 ++++++- .../errors/308-doubly-recursive-use.svg | 13 ++ tests/fixtures/errors/308-recursive-use.svg | 9 ++ tests/fixtures/errors/308-use-self-ref.svg | 7 + .../errors/515-pattern-billion-laughs.svg | 130 ++++++++++++++++++ .../errors/515-too-many-elements.svgz | Bin 0 -> 40811 bytes 10 files changed, 310 insertions(+), 40 deletions(-) create mode 100644 tests/fixtures/errors/308-doubly-recursive-use.svg create mode 100644 tests/fixtures/errors/308-recursive-use.svg create mode 100644 tests/fixtures/errors/308-use-self-ref.svg create mode 100644 tests/fixtures/errors/515-pattern-billion-laughs.svg create mode 100644 tests/fixtures/errors/515-too-many-elements.svgz diff --git a/librsvg/rsvg-base.c b/librsvg/rsvg-base.c index dbad819..af3d43c 100644 --- a/librsvg/rsvg-base.c +++ b/librsvg/rsvg-base.c @@ -431,12 +431,29 @@ node_set_atts (RsvgNode * node, RsvgHandle *handle, const NodeCreator *creator, } } +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_standard_element_start (RsvgHandle *handle, const char *name, RsvgPropertyBag * atts) { const NodeCreator *creator; RsvgNode *newnode = NULL; + if (loading_limits_exceeded (handle)) { + g_set_error (handle->priv->error, RSVG_ERROR, 0, "instancing limit"); + + xmlStopParser (handle->priv->ctxt); + return; + } + creator = get_node_creator_for_element_name (name); g_assert (creator != NULL && creator->create_fn != NULL); @@ -456,6 +473,7 @@ rsvg_standard_element_start (RsvgHandle *handle, const char *name, RsvgPropertyB handle->priv->treebase = rsvg_node_ref (newnode); } + handle->priv->num_loaded_elements += 1; handle->priv->currentnode = rsvg_node_ref (newnode); node_set_atts (newnode, handle, creator, atts); @@ -1641,6 +1659,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 + */ +static gboolean +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_drawing_ctx_acquire_node: * @ctx: The drawing context in use @@ -1668,16 +1732,15 @@ rsvg_drawing_ctx_acquire_node (RsvgDrawingCtx * ctx, const char *url) if (url == NULL) return NULL; + rsvg_drawing_ctx_increase_num_elements_acquired (ctx); + if (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); } /** @@ -1734,18 +1797,9 @@ rsvg_drawing_ctx_release_node (RsvgDrawingCtx * ctx, RsvgNode *node) if (node == NULL) return; - g_return_if_fail (ctx->acquired_nodes != NULL); - g_return_if_fail (ctx->acquired_nodes->data == node); - ctx->acquired_nodes = g_slist_remove (ctx->acquired_nodes, node); } -void -rsvg_drawing_ctx_increase_num_elements_rendered_through_use (RsvgDrawingCtx *draw_ctx) -{ - draw_ctx->num_elements_rendered_through_use++; -} - void rsvg_drawing_ctx_add_node_and_ancestors_to_stack (RsvgDrawingCtx *draw_ctx, RsvgNode *node) { @@ -1759,12 +1813,6 @@ rsvg_drawing_ctx_add_node_and_ancestors_to_stack (RsvgDrawingCtx *draw_ctx, Rsvg } } -static gboolean -limits_exceeded (RsvgDrawingCtx *draw_ctx) -{ - return draw_ctx->num_elements_rendered_through_use > 500000; -} - gboolean rsvg_drawing_ctx_draw_node_from_stack (RsvgDrawingCtx *ctx, RsvgNode *node, int dominate) { diff --git a/librsvg/rsvg-private.h b/librsvg/rsvg-private.h index aeec8d5..06f4c2b 100644 --- a/librsvg/rsvg-private.h +++ b/librsvg/rsvg-private.h @@ -164,6 +164,7 @@ struct RsvgHandlePrivate { */ RsvgSaxHandler *handler; int handler_nest; + gsize num_loaded_elements; GHashTable *entities; /* g_malloc'd string -> xmlEntityPtr */ @@ -200,7 +201,7 @@ struct RsvgDrawingCtx { RsvgState *state; GError **error; RsvgDefs *defs; - gsize num_elements_rendered_through_use; + gsize num_elements_acquired; PangoContext *pango_context; double dpi_x, dpi_y; RsvgViewBox vb; @@ -502,6 +503,8 @@ RsvgNode *rsvg_drawing_ctx_acquire_node (RsvgDrawingCtx * ctx, const cha G_GNUC_INTERNAL RsvgNode *rsvg_drawing_ctx_acquire_node_of_type (RsvgDrawingCtx * ctx, const char *url, RsvgNodeType type); G_GNUC_INTERNAL +RsvgNode *rsvg_drawing_ctx_acquire_node_ref (RsvgDrawingCtx * ctx, RsvgNode *node); +G_GNUC_INTERNAL void rsvg_drawing_ctx_release_node (RsvgDrawingCtx * ctx, RsvgNode *node); G_GNUC_INTERNAL diff --git a/rsvg_internals/src/drawing_ctx.rs b/rsvg_internals/src/drawing_ctx.rs index 79f0c9f..631b073 100644 --- a/rsvg_internals/src/drawing_ctx.rs +++ b/rsvg_internals/src/drawing_ctx.rs @@ -32,6 +32,11 @@ extern "C" { fn rsvg_drawing_ctx_pop_view_box(draw_ctx: *const RsvgDrawingCtx); + fn rsvg_drawing_ctx_acquire_node_ref( + draw_ctx: *const RsvgDrawingCtx, + node: *const RsvgNode, + ) -> *mut RsvgNode; + fn rsvg_drawing_ctx_acquire_node( draw_ctx: *const RsvgDrawingCtx, url: *const libc::c_char, @@ -45,8 +50,6 @@ extern "C" { fn rsvg_drawing_ctx_release_node(draw_ctx: *const RsvgDrawingCtx, node: *mut RsvgNode); - fn rsvg_drawing_ctx_increase_num_elements_rendered_through_use(draw_ctx: *const RsvgDrawingCtx); - fn rsvg_drawing_ctx_get_current_state_affine(draw_ctx: *const RsvgDrawingCtx) -> cairo::Matrix; fn rsvg_drawing_ctx_set_current_state_affine( @@ -149,6 +152,16 @@ pub fn pop_view_box(draw_ctx: *const RsvgDrawingCtx) { } } +pub fn acquire_node_ref(draw_ctx: *const RsvgDrawingCtx, node: *const RsvgNode) -> Option { + let raw_node = unsafe { rsvg_drawing_ctx_acquire_node_ref(draw_ctx, node) }; + + if raw_node.is_null() { + None + } else { + Some(AcquiredNode(draw_ctx, raw_node)) + } +} + pub fn get_acquired_node(draw_ctx: *const RsvgDrawingCtx, url: &str) -> Option { let raw_node = unsafe { rsvg_drawing_ctx_acquire_node(draw_ctx, str::to_glib_none(url).0) }; @@ -290,12 +303,6 @@ pub fn state_pop(draw_ctx: *const RsvgDrawingCtx) { } } -pub fn increase_num_elements_rendered_through_use(draw_ctx: *const RsvgDrawingCtx) { - unsafe { - rsvg_drawing_ctx_increase_num_elements_rendered_through_use(draw_ctx); - } -} - pub struct AcquiredNode(*const RsvgDrawingCtx, *mut RsvgNode); impl Drop for AcquiredNode { diff --git a/rsvg_internals/src/structure.rs b/rsvg_internals/src/structure.rs index 71c9ff0..e4234ae 100644 --- a/rsvg_internals/src/structure.rs +++ b/rsvg_internals/src/structure.rs @@ -278,6 +278,20 @@ impl NodeTrait for NodeUse { 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. + let self_box = box_node(node.clone()); + let self_acquired = drawing_ctx::acquire_node_ref(draw_ctx, self_box); + rsvg_node_unref(self_box); + if self_acquired.is_none() { + return; + } + let child = if let Some(acquired) = drawing_ctx::get_acquired_node(draw_ctx, link.as_ref().unwrap()) { @@ -286,13 +300,6 @@ impl NodeTrait for NodeUse { return; }; - if Node::is_ancestor(node.clone(), child.clone()) { - // or, if we're 'ing ourselves - return; - } - - drawing_ctx::increase_num_elements_rendered_through_use(draw_ctx); - let nx = self.x.get().normalize(draw_ctx); let ny = self.y.get().normalize(draw_ctx); diff --git a/tests/errors.c b/tests/errors.c index f370d60..ab5898a 100644 --- a/tests/errors.c +++ b/tests/errors.c @@ -22,10 +22,29 @@ get_test_filename (const char *basename) { 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 (void) +test_instancing_limit (gconstpointer data) { - char *filename = get_test_filename ("323-nested-use.svg"); + const char *basename = data; + char *filename = get_test_filename (basename); RsvgHandle *handle; GError *error = NULL; cairo_surface_t *surf; @@ -49,7 +68,34 @@ main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); - g_test_add_func ("/errors/instancing_limit", test_instancing_limit); + 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 0000000..9b248a6 --- /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 0000000..f5d00bf --- /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 0000000..dbf14c5 --- /dev/null +++ b/tests/fixtures/errors/308-use-self-ref.svg @@ -0,0 +1,7 @@ + + + + + + + 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 0000000..a306960 --- /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 0000000000000000000000000000000000000000..a7f7cf678ca2f29af6df61078d1c6a86c73c2d1a GIT binary patch literal 40811 zcmeIuO)I1U007{3c1mhf$VD-7q(+J1MDM}L%|UFTsL8?1JBN{yP&im}QQ{&|QhvZj zljI=9MY%ail5kSH<)g@tkhY%ZCp*LibxH_PE;`Ph^fuo4Xpr-qW6!wVUeOr_1r`p~-)LTcZB@uIs(k zp^3xx{A%Lt>ENbsBzwPKxl_B*S@%4>{ZPBL{_?u~dmaM@3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*Vq ziwEvbqN?)X)ARdf*>V8`1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA;JXJdqN)r0{91`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>f$?1`2;_+E#M0g