Skip to content

Commit 73df497

Browse files
committed
Limit allowed maximum nesting level
Loading deeply nested, but still small YAML documents, like [[[[[[[[[[...]]]]]]]]]] can be significantly slower than a "normal" document with the same size. By default the depth is limited to 512, which should be more than enough for 99% of the use cases. You can set the allowed depth with `max_depth`.
1 parent b280f73 commit 73df497

4 files changed

Lines changed: 68 additions & 0 deletions

File tree

lib/YAML/PP.pm

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ sub new {
3232
my $footer = delete $args{footer};
3333
my $require_footer = delete $args{require_footer};
3434
my $duplicate_keys = delete $args{duplicate_keys};
35+
my $max_depth = delete $args{max_depth};
3536
my $yaml_version = $class->_arg_yaml_version(delete $args{yaml_version});
3637
my $default_yaml_version = $yaml_version->[0];
3738
my $version_directive = delete $args{version_directive};
@@ -71,6 +72,7 @@ sub new {
7172
preserve => $preserve,
7273
duplicate_keys => $duplicate_keys,
7374
require_footer => $require_footer,
75+
max_depth => $max_depth,
7476
);
7577
my $dumper = YAML::PP::Dumper->new(
7678
schema => $default_schema,
@@ -523,6 +525,8 @@ L<https://perlpunk.github.io/YAML-PP-p5/test-suite.html>
523525
header => 1,
524526
footer => 0,
525527
version_directive => 0,
528+
require_footer => 0,
529+
max_depth => 512,
526530
);
527531
528532
Options:
@@ -665,6 +669,8 @@ Print document header C<--->
665669
666670
Default: 0
667671
672+
Since version v0.39
673+
668674
This option is for dumping.
669675
670676
Print document footer C<...>
@@ -691,6 +697,19 @@ the complete content, for example over network.
691697
---
692698
a: 2
693699
700+
=item max_depth
701+
702+
Since version v0.40
703+
704+
default: 512
705+
706+
Loading deeply nested, but still small YAML documents, like
707+
708+
[[[[[[[[[[...]]]]]]]]]]
709+
710+
can be significantly slower than a "normal" document with the same size.
711+
By default the depth is limited to 512, which should be more than enough
712+
for 99% of the use cases.
694713
695714
=item yaml_version
696715

lib/YAML/PP/Constructor.pm

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use Carp qw/ croak /;
1515
use constant DEBUG => ($ENV{YAML_PP_LOAD_DEBUG} or $ENV{YAML_PP_LOAD_TRACE}) ? 1 : 0;
1616
use constant TRACE => $ENV{YAML_PP_LOAD_TRACE} ? 1 : 0;
1717

18+
use constant MAX_DEPTH => 2 ** 9;
19+
1820
my %cyclic_refs = qw/ allow 1 ignore 1 warn 1 fatal 1 /;
1921

2022
sub new {
@@ -26,6 +28,7 @@ sub new {
2628
$duplicate_keys = 0;
2729
}
2830
my $require_footer = delete $args{require_footer};
31+
my $max_depth = delete $args{max_depth} || MAX_DEPTH;
2932
my $preserve = delete $args{preserve} || 0;
3033
if ($preserve == 1) {
3134
$preserve = PRESERVE_ORDER | PRESERVE_SCALAR_STYLE | PRESERVE_FLOW_STYLE | PRESERVE_ALIAS;
@@ -46,6 +49,7 @@ sub new {
4649
preserve => $preserve,
4750
duplicate_keys => $duplicate_keys,
4851
require_footer => $require_footer,
52+
max_depth => $max_depth,
4953
}, $class;
5054
$self->init;
5155
return $self;
@@ -59,6 +63,7 @@ sub clone {
5963
default_yaml_version => $self->{default_yaml_version},
6064
cyclic_refs => $self->cyclic_refs,
6165
preserve => $self->{preserve},
66+
max_depth => $self->{max_depth},
6267
};
6368
return bless $clone, ref $self;
6469
}
@@ -92,6 +97,7 @@ sub preserve_flow_style { return $_[0]->{preserve} & PRESERVE_FLOW_STYLE }
9297
sub preserve_alias { return $_[0]->{preserve} & PRESERVE_ALIAS }
9398
sub duplicate_keys { return $_[0]->{duplicate_keys} }
9499
sub require_footer { return $_[0]->{require_footer} }
100+
sub max_depth { return $_[0]->{max_depth} }
95101

96102
sub document_start_event {
97103
my ($self, $event) = @_;
@@ -129,6 +135,15 @@ sub document_end_event {
129135
$self->set_stack([]);
130136
}
131137

138+
sub _check_depth {
139+
my ($self) = @_;
140+
my $stack = $self->stack;
141+
my $c = @$stack;
142+
if ($c > ($self->max_depth || MAX_DEPTH)){
143+
croak sprintf 'Depth of nesting exceeds maximum %s', $self->max_depth;
144+
}
145+
}
146+
132147
sub mapping_start_event {
133148
my ($self, $event) = @_;
134149
my ($data, $on_data) = $self->schema->create_mapping($self, $event);
@@ -139,6 +154,7 @@ sub mapping_start_event {
139154
event => $event,
140155
on_data => $on_data,
141156
};
157+
$self->_check_depth;
142158
my $stack = $self->stack;
143159

144160
my $preserve_order = $self->preserve_order;
@@ -241,6 +257,7 @@ sub sequence_start_event {
241257
on_data => $on_data,
242258
};
243259
my $stack = $self->stack;
260+
$self->_check_depth;
244261

245262
my $preserve_style = $self->preserve_flow_style;
246263
my $preserve_alias = $self->preserve_alias;

lib/YAML/PP/Loader.pm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ sub new {
1717
my $preserve = delete $args{preserve};
1818
my $duplicate_keys = delete $args{duplicate_keys};
1919
my $require_footer = delete $args{require_footer};
20+
my $max_depth = delete $args{max_depth};
2021
my $schemas = delete $args{schemas};
2122
$schemas ||= {
2223
'1.2' => YAML::PP->default_schema(
@@ -31,6 +32,7 @@ sub new {
3132
preserve => $preserve,
3233
duplicate_keys => $duplicate_keys,
3334
require_footer => $require_footer,
35+
max_depth => $max_depth,
3436
);
3537
my $parser = delete $args{parser};
3638
unless ($parser) {

t/58.max-depth.t

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env perl
2+
use strict;
3+
use warnings;
4+
use Test::More;
5+
use YAML::PP;
6+
7+
my $yp = YAML::PP->new(
8+
max_depth => 5,
9+
);
10+
11+
my @errors = (
12+
'[[[[[[]]]]]]',
13+
'[{[{[{}]}]}]',
14+
'{{{{{{}}}}}}',
15+
'{[{[{[]}]}]}',
16+
);
17+
for my $yaml (@errors) {
18+
eval {
19+
$yp->load_string($yaml);
20+
};
21+
like $@, qr{Depth of nesting exceeds maximum 5}, "'$yaml' expected error message";
22+
}
23+
24+
my $yaml = '[[[[[]]]]]';
25+
eval {
26+
$yp->load_string($yaml);
27+
};
28+
is $@, '', 'no error message';
29+
30+
done_testing;

0 commit comments

Comments
 (0)