Skip to content
This repository
Browse code

Add configurable recursion limit to the Encoder

  • Loading branch information...
commit 56002195a0c2dc2cbb1f08315b27fe07126251b9 1 parent 181b6be
Steffen Müller authored November 23, 2012
13  Perl/Encoder/lib/Sereal/Encoder.pm
@@ -135,6 +135,19 @@ data structures just the same as for a positive value with one
135 135
 exception: For blessed, unsupported items that have string overloading,
136 136
 we silently stringify without warning.
137 137
 
  138
+=item max_recursion_depth
  139
+
  140
+C<Sereal::Encoder> is recursive. If you pass it a Perl data structure
  141
+that is deeply nested, it will eventually exhaust the C stack. Therefore,
  142
+there is a limit on the depth of recursion that is accepted. It defaults
  143
+to 10000 nested calls. You may choose to override this value with the
  144
+C<max_recursion_depth> option. Beware that setting it too high can
  145
+cause hard crashes, so only do that if you B<KNOW> that it is safe to
  146
+do so.
  147
+
  148
+Do note that the setting is somewhat approximate. Setting it to 10000 may break at
  149
+somewhere between 9997 and 10003 nested structures depending on their types.
  150
+
138 151
 =back
139 152
 
140 153
 The thusly allocated encoder object and its output buffer will be reused
20  Perl/Encoder/srl_encoder.c
@@ -71,7 +71,7 @@ extern "C" {
71 71
 #   define DO_SHARED_HASH_ENTRY_REFCOUNT_CHECK 0
72 72
 #endif
73 73
 
74  
-#define MAX_DEPTH 10000
  74
+#define DEFAULT_MAX_RECUR_DEPTH 10000
75 75
 
76 76
 #define DEBUGHACK 0
77 77
 
@@ -162,7 +162,7 @@ srl_clear_encoder(pTHX_ srl_encoder_t *enc)
162 162
         warn("Sereal Encoder being cleared but in virgin state. That is unexpected.");
163 163
     }
164 164
 
165  
-    enc->depth = 0;
  165
+    enc->recursion_depth = 0;
166 166
     if (enc->ref_seenhash != NULL)
167 167
         PTABLE_clear(enc->ref_seenhash);
168 168
     if (enc->str_seenhash != NULL)
@@ -205,7 +205,8 @@ srl_empty_encoder_struct(pTHX)
205 205
     }
206 206
     enc->buf_end = enc->buf_start + INITIALIZATION_SIZE - 1;
207 207
     enc->pos = enc->buf_start;
208  
-    enc->depth = 0;
  208
+    enc->recursion_depth = 0;
  209
+    enc->max_recursion_depth = DEFAULT_MAX_RECUR_DEPTH;
209 210
     enc->operational_flags = 0;
210 211
     /*enc->flags = 0;*/ /* to be set elsewhere */
211 212
 
@@ -270,6 +271,10 @@ srl_build_encoder_struct(pTHX_ HV *opt)
270 271
             enc->snappy_threshold = SvIV(*svp);
271 272
         else
272 273
             enc->snappy_threshold = 1024;
  274
+
  275
+        svp = hv_fetchs(opt, "max_recursion_depth", 0);
  276
+        if ( svp && SvTRUE(*svp))
  277
+            enc->max_recursion_depth = SvUV(*svp);
273 278
     }
274 279
     else {
275 280
         /* SRL_F_SHARED_HASHKEYS on by default */
@@ -851,6 +856,11 @@ srl_dump_sv(pTHX_ srl_encoder_t *enc, SV *src)
851 856
     ssize_t ref_rewrite_pos= 0;      /* preserved between loops */
852 857
     assert(src);
853 858
 
  859
+    if (++enc->recursion_depth == enc->max_recursion_depth) {
  860
+        croak("Hit maximum recursion depth (%lu), aborting serialization",
  861
+              (unsigned long)enc->max_recursion_depth);
  862
+    }
  863
+
854 864
 redo_dump:
855 865
     mg= NULL;
856 866
     backrefs= NULL;
@@ -887,11 +897,13 @@ srl_dump_sv(pTHX_ srl_encoder_t *enc, SV *src)
887 897
     if ( expect_false( refcount > 1 ) ) {
888 898
         if (src == &PL_sv_yes) {
889 899
             srl_buf_cat_char(enc, SRL_HDR_TRUE);
  900
+            --enc->recursion_depth;
890 901
             return;
891 902
         }
892 903
         else
893 904
         if (src == &PL_sv_no) {
894 905
             srl_buf_cat_char(enc, SRL_HDR_FALSE);
  906
+            --enc->recursion_depth;
895 907
             return;
896 908
         }
897 909
         else {
@@ -908,6 +920,7 @@ srl_dump_sv(pTHX_ srl_encoder_t *enc, SV *src)
908 920
                     srl_buf_cat_varint(aTHX_ enc, SRL_HDR_ALIAS, (UV)oldoffset);
909 921
                 }
910 922
                 SRL_SET_FBIT(*(enc->buf_start + oldoffset));
  923
+                --enc->recursion_depth;
911 924
                 return;
912 925
             }
913 926
             if (DEBUGHACK) warn("storing %p as %lu", src, BUF_POS_OFS(enc));
@@ -1055,5 +1068,6 @@ srl_dump_sv(pTHX_ srl_encoder_t *enc, SV *src)
1055 1068
         SRL_HANDLE_UNSUPPORTED_TYPE(enc, src, svt, refsv, ref_rewrite_pos);
1056 1069
 #undef SRL_HANDLE_UNSUPPORTED_TYPE
1057 1070
     }
  1071
+    --enc->recursion_depth;
1058 1072
 }
1059 1073
 
4  Perl/Encoder/srl_encoder.h
@@ -19,7 +19,9 @@ typedef struct {
19 19
 
20 20
     U32 operational_flags;   /* flags that pertain to one encode run (rather than being options): See SRL_OF_* defines */
21 21
     U32 flags;               /* flag-like options: See SRL_F_* defines */
22  
-    unsigned int depth;      /* current Perl-ref recursion depth */
  22
+    UV max_recursion_depth;  /* Configurable limit on the number of recursive calls we're willing to make */
  23
+
  24
+    UV recursion_depth;      /* current Perl-ref recursion depth */
23 25
     ptable_ptr ref_seenhash; /* ptr table for avoiding circular refs */
24 26
     ptable_ptr weak_seenhash; /* ptr table for avoiding dangling weakrefs */
25 27
     ptable_ptr str_seenhash; /* ptr table for issuing COPY commands */
30  Perl/Encoder/t/160_recursion.t
... ...
@@ -0,0 +1,30 @@
  1
+#!perl
  2
+use strict;
  3
+use warnings;
  4
+use Sereal::Encoder qw(encode_sereal);
  5
+use File::Spec;
  6
+
  7
+use lib File::Spec->catdir(qw(t lib));
  8
+BEGIN {
  9
+    lib->import('lib')
  10
+        if !-d 't';
  11
+}
  12
+
  13
+use Test::More;
  14
+
  15
+my $recur_depth = 1000;
  16
+my $ref = [];
  17
+my $pos = $ref;
  18
+$pos = $pos->[0] = [] for 1..$recur_depth-1;
  19
+
  20
+my $out = encode_sereal($ref, {max_recursion_depth => $recur_depth+1});
  21
+pass("alive");
  22
+my $no_exception = eval {
  23
+    $out = encode_sereal($ref, {max_recursion_depth => $recur_depth-1});
  24
+    1
  25
+};
  26
+ok(!$no_exception);
  27
+
  28
+done_testing();
  29
+note("All done folks!");
  30
+

0 notes on commit 5600219

Please sign in to comment.
Something went wrong with that request. Please try again.