CVE-2022-25857 (published 2022-08-30T05:15Z)

The package org.yaml:snakeyaml from 0 and before 1.31 are vulnerable to Denial of Service (DoS) due missing to nested depth limitation for collections.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-776
cwe_name: Improper Restriction of Recursive Entity References in DTDs ('XML Entity Expansion')
description: The product uses XML documents and allows their structure to be defined with a Document Type Definition (DTD), but it does not properly control the number of recursive definitions of entities.
extended_description: If the DTD contains a large number of nested or recursive entities, this can lead to explosive growth of data when parsed, causing a denial of service.
         

Fix information:
repo_url: https://bitbucket.org/snakeyaml/snakeyaml
commit_details: repo_url: https://bitbucket.org/snakeyaml/snakeyaml
author: Andrey Somov
author_date: 2022-04-28 16:38:16+04:00
msg: Restrict nested depth for collections to avoid DoS attacks
repository: repo_url: https://bitbucket.org/snakeyaml/snakeyaml
repo_name: visit repo url
description: visit repo url
repo_language: visit repo url
owner: snakeyaml
file_changes: filename: changes.xml
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,9 @@
     </properties>
     <body>
         <release version="1.31" date="in Git" description="Maintenance">
+            <action dev="asomov" type="fix" issue="525">
+                Restrict nested depth for collections to avoid DoS attacks (detected by OSS-Fuzz)
+            </action>
             <action dev="asomov" type="add" issue="525">
                 Add test for stackoverflow
             </action>

programming_language: Markdown
filename: LoaderOptions.java
change_type: ModificationType.MODIFY
diff: @@ -23,6 +23,7 @@ public class LoaderOptions {
     private boolean allowRecursiveKeys = false;
     private boolean processComments = false;
     private boolean enumCaseSensitive = true;
+    private int nestingDepthLimit = 50;
 
     public boolean isAllowDuplicateKeys() {
         return allowDuplicateKeys;
@@ -67,7 +68,8 @@ public class LoaderOptions {
     }
 
     /**
-     * Restrict the amount of aliases for collections (sequences and mappings) to avoid https://en.wikipedia.org/wiki/Billion_laughs_attack
+     * Restrict the amount of aliases for collections (sequences and mappings)
+     * to avoid https://en.wikipedia.org/wiki/Billion_laughs_attack
      * @param maxAliasesForCollections set max allowed value (50 by default)
      */
     public void setMaxAliasesForCollections(int maxAliasesForCollections) {
@@ -75,7 +77,7 @@ public class LoaderOptions {
     }
 
     /**
-     * Allow recursive keys for mappings. By default it is not allowed.
+     * Allow recursive keys for mappings. By default, it is not allowed.
      * This setting only prevents the case when the key is the value. If the key is only a part of the value
      * (the value is a sequence or a mapping) then this case is not recognized and always allowed.
      * @param allowRecursiveKeys - false to disable recursive keys
@@ -114,4 +116,18 @@ public class LoaderOptions {
     public void setEnumCaseSensitive(boolean enumCaseSensitive) {
         this.enumCaseSensitive = enumCaseSensitive;
     }
+
+    public int getNestingDepthLimit() {
+        return nestingDepthLimit;
+    }
+
+    /**
+     * Set max depth of nested collections. When the limit is exceeded an exception is thrown.
+     * Aliases/Anchors are not counted.
+     * This is to prevent a DoS attack
+     * @param nestingDepthLimit - depth to be accepted (50 by default)
+     */
+    public void setNestingDepthLimit(int nestingDepthLimit) {
+        this.nestingDepthLimit = nestingDepthLimit;
+    }
 }

programming_language: Java
filename: Composer.java
change_type: ModificationType.MODIFY
diff: @@ -36,7 +36,6 @@ import org.yaml.snakeyaml.events.MappingStartEvent;
 import org.yaml.snakeyaml.events.NodeEvent;
 import org.yaml.snakeyaml.events.ScalarEvent;
 import org.yaml.snakeyaml.events.SequenceStartEvent;
-import org.yaml.snakeyaml.nodes.AnchorNode;
 import org.yaml.snakeyaml.nodes.MappingNode;
 import org.yaml.snakeyaml.nodes.Node;
 import org.yaml.snakeyaml.nodes.NodeId;
@@ -63,6 +62,9 @@ public class Composer {
     private final LoaderOptions loadingConfig;
     private final CommentEventsCollector blockCommentsCollector;
     private final CommentEventsCollector inlineCommentsCollector;
+    // keep the nesting of collections inside other collections
+    private int nestingDepth = 0;
+    private final int nestingDepthLimit;
 
     public Composer(Parser parser, Resolver resolver) {
         this(parser, resolver, new LoaderOptions());
@@ -78,6 +80,7 @@ public class Composer {
                 CommentType.BLANK_LINE, CommentType.BLOCK);
         this.inlineCommentsCollector = new CommentEventsCollector(parser,
                 CommentType.IN_LINE);
+        nestingDepthLimit = loadingConfig.getNestingDepthLimit();
     }
 
     /**
@@ -182,6 +185,7 @@ public class Composer {
         } else {
             NodeEvent event = (NodeEvent) parser.peekEvent();
             String anchor = event.getAnchor();
+            increaseNestingDepth();
             // the check for duplicate anchors has been removed (issue 174)
             if (parser.checkEvent(Event.ID.Scalar)) {
                 node = composeScalarNode(anchor, blockCommentsCollector.consume());
@@ -190,6 +194,7 @@ public class Composer {
             } else {
                 node = composeMappingNode(anchor);
             }
+            decreaseNestingDepth();
         }
         recursiveNodes.remove(parent);
         return node;
@@ -316,4 +321,25 @@ public class Composer {
     protected Node composeValueNode(MappingNode node) {
         return composeNode(node);
     }
+
+    /**
+     * Increase nesting depth and fail when it exceeds the denied limit
+     */
+    private void increaseNestingDepth() {
+        if (nestingDepth > nestingDepthLimit) {
+            throw new YAMLException("Nesting Depth exceeded max " + nestingDepthLimit);
+        }
+        nestingDepth++;
+    }
+
+    /**
+     * Indicate that the collection is finished and the nesting is decreased
+     */
+    private void decreaseNestingDepth() {
+        if (nestingDepth > 0) {
+            nestingDepth--;
+        } else {
+            throw new YAMLException("Nesting Depth cannot be negative");
+        }
+    }
 }

programming_language: Java
filename: ReferencesTest.java
change_type: ModificationType.MODIFY
diff: @@ -118,20 +118,22 @@ public class ReferencesTest {
     }
 
     @Test
-    public void referencesWithRestrictedAliases() {
+    public void referencesWithRestrictedNesting() {
         // without alias restriction this size should occupy tons of CPU, memory and time to parse
-        String bigYAML = createDump(35);
+        int depth = 35;
+        String bigYAML = createDump(depth);
         // Load
         long time1 = System.currentTimeMillis();
         LoaderOptions settings = new LoaderOptions();
-        settings.setMaxAliasesForCollections(40);
+        settings.setMaxAliasesForCollections(1000);
         settings.setAllowRecursiveKeys(true);
+        settings.setNestingDepthLimit(depth);
         Yaml yaml = new Yaml(settings);
         try {
             yaml.load(bigYAML);
             fail();
         } catch (Exception e) {
-            assertEquals("Number of aliases for non-scalar nodes exceeds the specified max=40", e.getMessage());
+            assertEquals("Nesting Depth exceeded max 35", e.getMessage());
         }
         long time2 = System.currentTimeMillis();
         float duration = (time2 - time1) / 1000;

programming_language: Java
filename: FuzzyStackOverflowTest.java
change_type: ModificationType.MODIFY
diff: @@ -15,16 +15,17 @@
  */
 package org.yaml.snakeyaml.issues.issue525;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
+import org.yaml.snakeyaml.LoaderOptions;
 import org.yaml.snakeyaml.Util;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.error.YAMLException;
 
 public class FuzzyStackOverflowTest {
-
   @Test
   public void parseOpenUnmatchedMappings() {
     try {
@@ -33,9 +34,21 @@ public class FuzzyStackOverflowTest {
       yaml.load(strYaml);
       fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME stackoverflow
+      assertEquals("Nesting Depth exceeded max 50", e.getMessage());
+    }
+  }
+
+  @Test
+  public void parseOpenUnmatchedMappingsWithCustomLimit() {
+    try {
+      LoaderOptions options = new LoaderOptions();
+      options.setNestingDepthLimit(1000);
+      Yaml yaml = new Yaml(options);
+      String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-4626423186325504");
+      yaml.load(strYaml);
+      fail("Should report invalid YAML");
+    } catch (YAMLException e) {
+      assertEquals("Nesting Depth exceeded max 1000", e.getMessage());
     }
   }
 }

programming_language: Java
filename: Fuzzy47027Test.java
change_type: ModificationType.MODIFY
diff: @@ -15,10 +15,11 @@
  */
 package org.yaml.snakeyaml.issues.issue526;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
+import org.yaml.snakeyaml.LoaderOptions;
 import org.yaml.snakeyaml.Util;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.error.YAMLException;
@@ -34,9 +35,21 @@ public class Fuzzy47027Test {
       yaml.load(strYaml);
       fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME stackoverflow
+      assertEquals("Nesting Depth exceeded max 50", e.getMessage());
+    }
+  }
+
+  @Test
+  public void setCustomLimit100() {
+    try {
+      LoaderOptions options = new LoaderOptions();
+      options.setNestingDepthLimit(100);
+      Yaml yaml = new Yaml(options);
+      String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-5427149240139776");
+      yaml.load(strYaml);
+      fail("Should report invalid YAML");
+    } catch (YAMLException e) {
+      assertEquals("Nesting Depth exceeded max 100", e.getMessage());
     }
   }
 }

programming_language: Java
filename: Fuzzy47047Test.java
change_type: ModificationType.MODIFY
diff: @@ -15,7 +15,7 @@
  */
 package org.yaml.snakeyaml.issues.issue527;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
@@ -27,16 +27,14 @@ import org.yaml.snakeyaml.error.YAMLException;
 public class Fuzzy47047Test {
 
   @Test
-  public void parseOpenUnmatchedSequences_47047() {
+  public void parseKeyIndicators_47047() {
     try {
       Yaml yaml = new Yaml();
       String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-5868638424399872");
-      yaml.load(strYaml);
-      fail("Should report invalid YAML");
+      //yaml.load(strYaml);
+      //TODO FIXME fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME it runs for 35 seconds !!!
+      assertEquals("Nesting Depth exceeded max 25", e.getMessage());
     }
   }
 }

programming_language: Java
filename: changes.xml
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,9 @@
     </properties>
     <body>
         <release version="1.31" date="in Git" description="Maintenance">
+            <action dev="asomov" type="fix" issue="525">
+                Restrict nested depth for collections to avoid DoS attacks (detected by OSS-Fuzz)
+            </action>
             <action dev="asomov" type="add" issue="525">
                 Add test for stackoverflow
             </action>

programming_language: Markdown
filename: LoaderOptions.java
change_type: ModificationType.MODIFY
diff: @@ -23,6 +23,7 @@ public class LoaderOptions {
     private boolean allowRecursiveKeys = false;
     private boolean processComments = false;
     private boolean enumCaseSensitive = true;
+    private int nestingDepthLimit = 50;
 
     public boolean isAllowDuplicateKeys() {
         return allowDuplicateKeys;
@@ -67,7 +68,8 @@ public class LoaderOptions {
     }
 
     /**
-     * Restrict the amount of aliases for collections (sequences and mappings) to avoid https://en.wikipedia.org/wiki/Billion_laughs_attack
+     * Restrict the amount of aliases for collections (sequences and mappings)
+     * to avoid https://en.wikipedia.org/wiki/Billion_laughs_attack
      * @param maxAliasesForCollections set max allowed value (50 by default)
      */
     public void setMaxAliasesForCollections(int maxAliasesForCollections) {
@@ -75,7 +77,7 @@ public class LoaderOptions {
     }
 
     /**
-     * Allow recursive keys for mappings. By default it is not allowed.
+     * Allow recursive keys for mappings. By default, it is not allowed.
      * This setting only prevents the case when the key is the value. If the key is only a part of the value
      * (the value is a sequence or a mapping) then this case is not recognized and always allowed.
      * @param allowRecursiveKeys - false to disable recursive keys
@@ -114,4 +116,18 @@ public class LoaderOptions {
     public void setEnumCaseSensitive(boolean enumCaseSensitive) {
         this.enumCaseSensitive = enumCaseSensitive;
     }
+
+    public int getNestingDepthLimit() {
+        return nestingDepthLimit;
+    }
+
+    /**
+     * Set max depth of nested collections. When the limit is exceeded an exception is thrown.
+     * Aliases/Anchors are not counted.
+     * This is to prevent a DoS attack
+     * @param nestingDepthLimit - depth to be accepted (50 by default)
+     */
+    public void setNestingDepthLimit(int nestingDepthLimit) {
+        this.nestingDepthLimit = nestingDepthLimit;
+    }
 }

programming_language: Java
filename: Composer.java
change_type: ModificationType.MODIFY
diff: @@ -36,7 +36,6 @@ import org.yaml.snakeyaml.events.MappingStartEvent;
 import org.yaml.snakeyaml.events.NodeEvent;
 import org.yaml.snakeyaml.events.ScalarEvent;
 import org.yaml.snakeyaml.events.SequenceStartEvent;
-import org.yaml.snakeyaml.nodes.AnchorNode;
 import org.yaml.snakeyaml.nodes.MappingNode;
 import org.yaml.snakeyaml.nodes.Node;
 import org.yaml.snakeyaml.nodes.NodeId;
@@ -63,6 +62,9 @@ public class Composer {
     private final LoaderOptions loadingConfig;
     private final CommentEventsCollector blockCommentsCollector;
     private final CommentEventsCollector inlineCommentsCollector;
+    // keep the nesting of collections inside other collections
+    private int nestingDepth = 0;
+    private final int nestingDepthLimit;
 
     public Composer(Parser parser, Resolver resolver) {
         this(parser, resolver, new LoaderOptions());
@@ -78,6 +80,7 @@ public class Composer {
                 CommentType.BLANK_LINE, CommentType.BLOCK);
         this.inlineCommentsCollector = new CommentEventsCollector(parser,
                 CommentType.IN_LINE);
+        nestingDepthLimit = loadingConfig.getNestingDepthLimit();
     }
 
     /**
@@ -182,6 +185,7 @@ public class Composer {
         } else {
             NodeEvent event = (NodeEvent) parser.peekEvent();
             String anchor = event.getAnchor();
+            increaseNestingDepth();
             // the check for duplicate anchors has been removed (issue 174)
             if (parser.checkEvent(Event.ID.Scalar)) {
                 node = composeScalarNode(anchor, blockCommentsCollector.consume());
@@ -190,6 +194,7 @@ public class Composer {
             } else {
                 node = composeMappingNode(anchor);
             }
+            decreaseNestingDepth();
         }
         recursiveNodes.remove(parent);
         return node;
@@ -316,4 +321,25 @@ public class Composer {
     protected Node composeValueNode(MappingNode node) {
         return composeNode(node);
     }
+
+    /**
+     * Increase nesting depth and fail when it exceeds the denied limit
+     */
+    private void increaseNestingDepth() {
+        if (nestingDepth > nestingDepthLimit) {
+            throw new YAMLException("Nesting Depth exceeded max " + nestingDepthLimit);
+        }
+        nestingDepth++;
+    }
+
+    /**
+     * Indicate that the collection is finished and the nesting is decreased
+     */
+    private void decreaseNestingDepth() {
+        if (nestingDepth > 0) {
+            nestingDepth--;
+        } else {
+            throw new YAMLException("Nesting Depth cannot be negative");
+        }
+    }
 }

programming_language: Java
filename: ReferencesTest.java
change_type: ModificationType.MODIFY
diff: @@ -118,20 +118,22 @@ public class ReferencesTest {
     }
 
     @Test
-    public void referencesWithRestrictedAliases() {
+    public void referencesWithRestrictedNesting() {
         // without alias restriction this size should occupy tons of CPU, memory and time to parse
-        String bigYAML = createDump(35);
+        int depth = 35;
+        String bigYAML = createDump(depth);
         // Load
         long time1 = System.currentTimeMillis();
         LoaderOptions settings = new LoaderOptions();
-        settings.setMaxAliasesForCollections(40);
+        settings.setMaxAliasesForCollections(1000);
         settings.setAllowRecursiveKeys(true);
+        settings.setNestingDepthLimit(depth);
         Yaml yaml = new Yaml(settings);
         try {
             yaml.load(bigYAML);
             fail();
         } catch (Exception e) {
-            assertEquals("Number of aliases for non-scalar nodes exceeds the specified max=40", e.getMessage());
+            assertEquals("Nesting Depth exceeded max 35", e.getMessage());
         }
         long time2 = System.currentTimeMillis();
         float duration = (time2 - time1) / 1000;

programming_language: Java
filename: FuzzyStackOverflowTest.java
change_type: ModificationType.MODIFY
diff: @@ -15,16 +15,17 @@
  */
 package org.yaml.snakeyaml.issues.issue525;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
+import org.yaml.snakeyaml.LoaderOptions;
 import org.yaml.snakeyaml.Util;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.error.YAMLException;
 
 public class FuzzyStackOverflowTest {
-
   @Test
   public void parseOpenUnmatchedMappings() {
     try {
@@ -33,9 +34,21 @@ public class FuzzyStackOverflowTest {
       yaml.load(strYaml);
       fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME stackoverflow
+      assertEquals("Nesting Depth exceeded max 50", e.getMessage());
+    }
+  }
+
+  @Test
+  public void parseOpenUnmatchedMappingsWithCustomLimit() {
+    try {
+      LoaderOptions options = new LoaderOptions();
+      options.setNestingDepthLimit(1000);
+      Yaml yaml = new Yaml(options);
+      String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-4626423186325504");
+      yaml.load(strYaml);
+      fail("Should report invalid YAML");
+    } catch (YAMLException e) {
+      assertEquals("Nesting Depth exceeded max 1000", e.getMessage());
     }
   }
 }

programming_language: Java
filename: Fuzzy47027Test.java
change_type: ModificationType.MODIFY
diff: @@ -15,10 +15,11 @@
  */
 package org.yaml.snakeyaml.issues.issue526;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
+import org.yaml.snakeyaml.LoaderOptions;
 import org.yaml.snakeyaml.Util;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.error.YAMLException;
@@ -34,9 +35,21 @@ public class Fuzzy47027Test {
       yaml.load(strYaml);
       fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME stackoverflow
+      assertEquals("Nesting Depth exceeded max 50", e.getMessage());
+    }
+  }
+
+  @Test
+  public void setCustomLimit100() {
+    try {
+      LoaderOptions options = new LoaderOptions();
+      options.setNestingDepthLimit(100);
+      Yaml yaml = new Yaml(options);
+      String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-5427149240139776");
+      yaml.load(strYaml);
+      fail("Should report invalid YAML");
+    } catch (YAMLException e) {
+      assertEquals("Nesting Depth exceeded max 100", e.getMessage());
     }
   }
 }

programming_language: Java
filename: Fuzzy47047Test.java
change_type: ModificationType.MODIFY
diff: @@ -15,7 +15,7 @@
  */
 package org.yaml.snakeyaml.issues.issue527;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
@@ -27,16 +27,14 @@ import org.yaml.snakeyaml.error.YAMLException;
 public class Fuzzy47047Test {
 
   @Test
-  public void parseOpenUnmatchedSequences_47047() {
+  public void parseKeyIndicators_47047() {
     try {
       Yaml yaml = new Yaml();
       String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-5868638424399872");
-      yaml.load(strYaml);
-      fail("Should report invalid YAML");
+      //yaml.load(strYaml);
+      //TODO FIXME fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME it runs for 35 seconds !!!
+      assertEquals("Nesting Depth exceeded max 25", e.getMessage());
     }
   }
 }

programming_language: Java
repo_url: https://github.com/snakeyaml/snakeyaml
commit_details: repo_url: https://github.com/snakeyaml/snakeyaml
author: Andrey Somov
author_date: 2022-04-28 16:38:16+04:00
msg: Restrict nested depth for collections to avoid DoS attacks
repository: repo_url: https://github.com/snakeyaml/snakeyaml
repo_name: snakeyaml/snakeyaml
description: Mirror of https://bitbucket.org/snakeyaml/snakeyaml
repo_language: Java
owner: snakeyaml
file_changes: filename: changes.xml
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,9 @@
     </properties>
     <body>
         <release version="1.31" date="in Git" description="Maintenance">
+            <action dev="asomov" type="fix" issue="525">
+                Restrict nested depth for collections to avoid DoS attacks (detected by OSS-Fuzz)
+            </action>
             <action dev="asomov" type="add" issue="525">
                 Add test for stackoverflow
             </action>

programming_language: Markdown
filename: LoaderOptions.java
change_type: ModificationType.MODIFY
diff: @@ -23,6 +23,7 @@ public class LoaderOptions {
     private boolean allowRecursiveKeys = false;
     private boolean processComments = false;
     private boolean enumCaseSensitive = true;
+    private int nestingDepthLimit = 50;
 
     public boolean isAllowDuplicateKeys() {
         return allowDuplicateKeys;
@@ -67,7 +68,8 @@ public class LoaderOptions {
     }
 
     /**
-     * Restrict the amount of aliases for collections (sequences and mappings) to avoid https://en.wikipedia.org/wiki/Billion_laughs_attack
+     * Restrict the amount of aliases for collections (sequences and mappings)
+     * to avoid https://en.wikipedia.org/wiki/Billion_laughs_attack
      * @param maxAliasesForCollections set max allowed value (50 by default)
      */
     public void setMaxAliasesForCollections(int maxAliasesForCollections) {
@@ -75,7 +77,7 @@ public class LoaderOptions {
     }
 
     /**
-     * Allow recursive keys for mappings. By default it is not allowed.
+     * Allow recursive keys for mappings. By default, it is not allowed.
      * This setting only prevents the case when the key is the value. If the key is only a part of the value
      * (the value is a sequence or a mapping) then this case is not recognized and always allowed.
      * @param allowRecursiveKeys - false to disable recursive keys
@@ -114,4 +116,18 @@ public class LoaderOptions {
     public void setEnumCaseSensitive(boolean enumCaseSensitive) {
         this.enumCaseSensitive = enumCaseSensitive;
     }
+
+    public int getNestingDepthLimit() {
+        return nestingDepthLimit;
+    }
+
+    /**
+     * Set max depth of nested collections. When the limit is exceeded an exception is thrown.
+     * Aliases/Anchors are not counted.
+     * This is to prevent a DoS attack
+     * @param nestingDepthLimit - depth to be accepted (50 by default)
+     */
+    public void setNestingDepthLimit(int nestingDepthLimit) {
+        this.nestingDepthLimit = nestingDepthLimit;
+    }
 }

programming_language: Java
filename: Composer.java
change_type: ModificationType.MODIFY
diff: @@ -36,7 +36,6 @@ import org.yaml.snakeyaml.events.MappingStartEvent;
 import org.yaml.snakeyaml.events.NodeEvent;
 import org.yaml.snakeyaml.events.ScalarEvent;
 import org.yaml.snakeyaml.events.SequenceStartEvent;
-import org.yaml.snakeyaml.nodes.AnchorNode;
 import org.yaml.snakeyaml.nodes.MappingNode;
 import org.yaml.snakeyaml.nodes.Node;
 import org.yaml.snakeyaml.nodes.NodeId;
@@ -63,6 +62,9 @@ public class Composer {
     private final LoaderOptions loadingConfig;
     private final CommentEventsCollector blockCommentsCollector;
     private final CommentEventsCollector inlineCommentsCollector;
+    // keep the nesting of collections inside other collections
+    private int nestingDepth = 0;
+    private final int nestingDepthLimit;
 
     public Composer(Parser parser, Resolver resolver) {
         this(parser, resolver, new LoaderOptions());
@@ -78,6 +80,7 @@ public class Composer {
                 CommentType.BLANK_LINE, CommentType.BLOCK);
         this.inlineCommentsCollector = new CommentEventsCollector(parser,
                 CommentType.IN_LINE);
+        nestingDepthLimit = loadingConfig.getNestingDepthLimit();
     }
 
     /**
@@ -182,6 +185,7 @@ public class Composer {
         } else {
             NodeEvent event = (NodeEvent) parser.peekEvent();
             String anchor = event.getAnchor();
+            increaseNestingDepth();
             // the check for duplicate anchors has been removed (issue 174)
             if (parser.checkEvent(Event.ID.Scalar)) {
                 node = composeScalarNode(anchor, blockCommentsCollector.consume());
@@ -190,6 +194,7 @@ public class Composer {
             } else {
                 node = composeMappingNode(anchor);
             }
+            decreaseNestingDepth();
         }
         recursiveNodes.remove(parent);
         return node;
@@ -316,4 +321,25 @@ public class Composer {
     protected Node composeValueNode(MappingNode node) {
         return composeNode(node);
     }
+
+    /**
+     * Increase nesting depth and fail when it exceeds the denied limit
+     */
+    private void increaseNestingDepth() {
+        if (nestingDepth > nestingDepthLimit) {
+            throw new YAMLException("Nesting Depth exceeded max " + nestingDepthLimit);
+        }
+        nestingDepth++;
+    }
+
+    /**
+     * Indicate that the collection is finished and the nesting is decreased
+     */
+    private void decreaseNestingDepth() {
+        if (nestingDepth > 0) {
+            nestingDepth--;
+        } else {
+            throw new YAMLException("Nesting Depth cannot be negative");
+        }
+    }
 }

programming_language: Java
filename: ReferencesTest.java
change_type: ModificationType.MODIFY
diff: @@ -118,20 +118,22 @@ public class ReferencesTest {
     }
 
     @Test
-    public void referencesWithRestrictedAliases() {
+    public void referencesWithRestrictedNesting() {
         // without alias restriction this size should occupy tons of CPU, memory and time to parse
-        String bigYAML = createDump(35);
+        int depth = 35;
+        String bigYAML = createDump(depth);
         // Load
         long time1 = System.currentTimeMillis();
         LoaderOptions settings = new LoaderOptions();
-        settings.setMaxAliasesForCollections(40);
+        settings.setMaxAliasesForCollections(1000);
         settings.setAllowRecursiveKeys(true);
+        settings.setNestingDepthLimit(depth);
         Yaml yaml = new Yaml(settings);
         try {
             yaml.load(bigYAML);
             fail();
         } catch (Exception e) {
-            assertEquals("Number of aliases for non-scalar nodes exceeds the specified max=40", e.getMessage());
+            assertEquals("Nesting Depth exceeded max 35", e.getMessage());
         }
         long time2 = System.currentTimeMillis();
         float duration = (time2 - time1) / 1000;

programming_language: Java
filename: FuzzyStackOverflowTest.java
change_type: ModificationType.MODIFY
diff: @@ -15,16 +15,17 @@
  */
 package org.yaml.snakeyaml.issues.issue525;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
+import org.yaml.snakeyaml.LoaderOptions;
 import org.yaml.snakeyaml.Util;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.error.YAMLException;
 
 public class FuzzyStackOverflowTest {
-
   @Test
   public void parseOpenUnmatchedMappings() {
     try {
@@ -33,9 +34,21 @@ public class FuzzyStackOverflowTest {
       yaml.load(strYaml);
       fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME stackoverflow
+      assertEquals("Nesting Depth exceeded max 50", e.getMessage());
+    }
+  }
+
+  @Test
+  public void parseOpenUnmatchedMappingsWithCustomLimit() {
+    try {
+      LoaderOptions options = new LoaderOptions();
+      options.setNestingDepthLimit(1000);
+      Yaml yaml = new Yaml(options);
+      String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-4626423186325504");
+      yaml.load(strYaml);
+      fail("Should report invalid YAML");
+    } catch (YAMLException e) {
+      assertEquals("Nesting Depth exceeded max 1000", e.getMessage());
     }
   }
 }

programming_language: Java
filename: Fuzzy47027Test.java
change_type: ModificationType.MODIFY
diff: @@ -15,10 +15,11 @@
  */
 package org.yaml.snakeyaml.issues.issue526;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
+import org.yaml.snakeyaml.LoaderOptions;
 import org.yaml.snakeyaml.Util;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.error.YAMLException;
@@ -34,9 +35,21 @@ public class Fuzzy47027Test {
       yaml.load(strYaml);
       fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME stackoverflow
+      assertEquals("Nesting Depth exceeded max 50", e.getMessage());
+    }
+  }
+
+  @Test
+  public void setCustomLimit100() {
+    try {
+      LoaderOptions options = new LoaderOptions();
+      options.setNestingDepthLimit(100);
+      Yaml yaml = new Yaml(options);
+      String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-5427149240139776");
+      yaml.load(strYaml);
+      fail("Should report invalid YAML");
+    } catch (YAMLException e) {
+      assertEquals("Nesting Depth exceeded max 100", e.getMessage());
     }
   }
 }

programming_language: Java
filename: Fuzzy47047Test.java
change_type: ModificationType.MODIFY
diff: @@ -15,7 +15,7 @@
  */
 package org.yaml.snakeyaml.issues.issue527;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
@@ -27,16 +27,14 @@ import org.yaml.snakeyaml.error.YAMLException;
 public class Fuzzy47047Test {
 
   @Test
-  public void parseOpenUnmatchedSequences_47047() {
+  public void parseKeyIndicators_47047() {
     try {
       Yaml yaml = new Yaml();
       String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-5868638424399872");
-      yaml.load(strYaml);
-      fail("Should report invalid YAML");
+      //yaml.load(strYaml);
+      //TODO FIXME fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME it runs for 35 seconds !!!
+      assertEquals("Nesting Depth exceeded max 25", e.getMessage());
     }
   }
 }

programming_language: Java
filename: changes.xml
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,9 @@
     </properties>
     <body>
         <release version="1.31" date="in Git" description="Maintenance">
+            <action dev="asomov" type="fix" issue="525">
+                Restrict nested depth for collections to avoid DoS attacks (detected by OSS-Fuzz)
+            </action>
             <action dev="asomov" type="add" issue="525">
                 Add test for stackoverflow
             </action>

programming_language: Markdown
filename: LoaderOptions.java
change_type: ModificationType.MODIFY
diff: @@ -23,6 +23,7 @@ public class LoaderOptions {
     private boolean allowRecursiveKeys = false;
     private boolean processComments = false;
     private boolean enumCaseSensitive = true;
+    private int nestingDepthLimit = 50;
 
     public boolean isAllowDuplicateKeys() {
         return allowDuplicateKeys;
@@ -67,7 +68,8 @@ public class LoaderOptions {
     }
 
     /**
-     * Restrict the amount of aliases for collections (sequences and mappings) to avoid https://en.wikipedia.org/wiki/Billion_laughs_attack
+     * Restrict the amount of aliases for collections (sequences and mappings)
+     * to avoid https://en.wikipedia.org/wiki/Billion_laughs_attack
      * @param maxAliasesForCollections set max allowed value (50 by default)
      */
     public void setMaxAliasesForCollections(int maxAliasesForCollections) {
@@ -75,7 +77,7 @@ public class LoaderOptions {
     }
 
     /**
-     * Allow recursive keys for mappings. By default it is not allowed.
+     * Allow recursive keys for mappings. By default, it is not allowed.
      * This setting only prevents the case when the key is the value. If the key is only a part of the value
      * (the value is a sequence or a mapping) then this case is not recognized and always allowed.
      * @param allowRecursiveKeys - false to disable recursive keys
@@ -114,4 +116,18 @@ public class LoaderOptions {
     public void setEnumCaseSensitive(boolean enumCaseSensitive) {
         this.enumCaseSensitive = enumCaseSensitive;
     }
+
+    public int getNestingDepthLimit() {
+        return nestingDepthLimit;
+    }
+
+    /**
+     * Set max depth of nested collections. When the limit is exceeded an exception is thrown.
+     * Aliases/Anchors are not counted.
+     * This is to prevent a DoS attack
+     * @param nestingDepthLimit - depth to be accepted (50 by default)
+     */
+    public void setNestingDepthLimit(int nestingDepthLimit) {
+        this.nestingDepthLimit = nestingDepthLimit;
+    }
 }

programming_language: Java
filename: Composer.java
change_type: ModificationType.MODIFY
diff: @@ -36,7 +36,6 @@ import org.yaml.snakeyaml.events.MappingStartEvent;
 import org.yaml.snakeyaml.events.NodeEvent;
 import org.yaml.snakeyaml.events.ScalarEvent;
 import org.yaml.snakeyaml.events.SequenceStartEvent;
-import org.yaml.snakeyaml.nodes.AnchorNode;
 import org.yaml.snakeyaml.nodes.MappingNode;
 import org.yaml.snakeyaml.nodes.Node;
 import org.yaml.snakeyaml.nodes.NodeId;
@@ -63,6 +62,9 @@ public class Composer {
     private final LoaderOptions loadingConfig;
     private final CommentEventsCollector blockCommentsCollector;
     private final CommentEventsCollector inlineCommentsCollector;
+    // keep the nesting of collections inside other collections
+    private int nestingDepth = 0;
+    private final int nestingDepthLimit;
 
     public Composer(Parser parser, Resolver resolver) {
         this(parser, resolver, new LoaderOptions());
@@ -78,6 +80,7 @@ public class Composer {
                 CommentType.BLANK_LINE, CommentType.BLOCK);
         this.inlineCommentsCollector = new CommentEventsCollector(parser,
                 CommentType.IN_LINE);
+        nestingDepthLimit = loadingConfig.getNestingDepthLimit();
     }
 
     /**
@@ -182,6 +185,7 @@ public class Composer {
         } else {
             NodeEvent event = (NodeEvent) parser.peekEvent();
             String anchor = event.getAnchor();
+            increaseNestingDepth();
             // the check for duplicate anchors has been removed (issue 174)
             if (parser.checkEvent(Event.ID.Scalar)) {
                 node = composeScalarNode(anchor, blockCommentsCollector.consume());
@@ -190,6 +194,7 @@ public class Composer {
             } else {
                 node = composeMappingNode(anchor);
             }
+            decreaseNestingDepth();
         }
         recursiveNodes.remove(parent);
         return node;
@@ -316,4 +321,25 @@ public class Composer {
     protected Node composeValueNode(MappingNode node) {
         return composeNode(node);
     }
+
+    /**
+     * Increase nesting depth and fail when it exceeds the denied limit
+     */
+    private void increaseNestingDepth() {
+        if (nestingDepth > nestingDepthLimit) {
+            throw new YAMLException("Nesting Depth exceeded max " + nestingDepthLimit);
+        }
+        nestingDepth++;
+    }
+
+    /**
+     * Indicate that the collection is finished and the nesting is decreased
+     */
+    private void decreaseNestingDepth() {
+        if (nestingDepth > 0) {
+            nestingDepth--;
+        } else {
+            throw new YAMLException("Nesting Depth cannot be negative");
+        }
+    }
 }

programming_language: Java
filename: ReferencesTest.java
change_type: ModificationType.MODIFY
diff: @@ -118,20 +118,22 @@ public class ReferencesTest {
     }
 
     @Test
-    public void referencesWithRestrictedAliases() {
+    public void referencesWithRestrictedNesting() {
         // without alias restriction this size should occupy tons of CPU, memory and time to parse
-        String bigYAML = createDump(35);
+        int depth = 35;
+        String bigYAML = createDump(depth);
         // Load
         long time1 = System.currentTimeMillis();
         LoaderOptions settings = new LoaderOptions();
-        settings.setMaxAliasesForCollections(40);
+        settings.setMaxAliasesForCollections(1000);
         settings.setAllowRecursiveKeys(true);
+        settings.setNestingDepthLimit(depth);
         Yaml yaml = new Yaml(settings);
         try {
             yaml.load(bigYAML);
             fail();
         } catch (Exception e) {
-            assertEquals("Number of aliases for non-scalar nodes exceeds the specified max=40", e.getMessage());
+            assertEquals("Nesting Depth exceeded max 35", e.getMessage());
         }
         long time2 = System.currentTimeMillis();
         float duration = (time2 - time1) / 1000;

programming_language: Java
filename: FuzzyStackOverflowTest.java
change_type: ModificationType.MODIFY
diff: @@ -15,16 +15,17 @@
  */
 package org.yaml.snakeyaml.issues.issue525;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
+import org.yaml.snakeyaml.LoaderOptions;
 import org.yaml.snakeyaml.Util;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.error.YAMLException;
 
 public class FuzzyStackOverflowTest {
-
   @Test
   public void parseOpenUnmatchedMappings() {
     try {
@@ -33,9 +34,21 @@ public class FuzzyStackOverflowTest {
       yaml.load(strYaml);
       fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME stackoverflow
+      assertEquals("Nesting Depth exceeded max 50", e.getMessage());
+    }
+  }
+
+  @Test
+  public void parseOpenUnmatchedMappingsWithCustomLimit() {
+    try {
+      LoaderOptions options = new LoaderOptions();
+      options.setNestingDepthLimit(1000);
+      Yaml yaml = new Yaml(options);
+      String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-4626423186325504");
+      yaml.load(strYaml);
+      fail("Should report invalid YAML");
+    } catch (YAMLException e) {
+      assertEquals("Nesting Depth exceeded max 1000", e.getMessage());
     }
   }
 }

programming_language: Java
filename: Fuzzy47027Test.java
change_type: ModificationType.MODIFY
diff: @@ -15,10 +15,11 @@
  */
 package org.yaml.snakeyaml.issues.issue526;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
+import org.yaml.snakeyaml.LoaderOptions;
 import org.yaml.snakeyaml.Util;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.error.YAMLException;
@@ -34,9 +35,21 @@ public class Fuzzy47027Test {
       yaml.load(strYaml);
       fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME stackoverflow
+      assertEquals("Nesting Depth exceeded max 50", e.getMessage());
+    }
+  }
+
+  @Test
+  public void setCustomLimit100() {
+    try {
+      LoaderOptions options = new LoaderOptions();
+      options.setNestingDepthLimit(100);
+      Yaml yaml = new Yaml(options);
+      String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-5427149240139776");
+      yaml.load(strYaml);
+      fail("Should report invalid YAML");
+    } catch (YAMLException e) {
+      assertEquals("Nesting Depth exceeded max 100", e.getMessage());
     }
   }
 }

programming_language: Java
filename: Fuzzy47047Test.java
change_type: ModificationType.MODIFY
diff: @@ -15,7 +15,7 @@
  */
 package org.yaml.snakeyaml.issues.issue527;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
@@ -27,16 +27,14 @@ import org.yaml.snakeyaml.error.YAMLException;
 public class Fuzzy47047Test {
 
   @Test
-  public void parseOpenUnmatchedSequences_47047() {
+  public void parseKeyIndicators_47047() {
     try {
       Yaml yaml = new Yaml();
       String strYaml = Util.getLocalResource("fuzzer/YamlFuzzer-5868638424399872");
-      yaml.load(strYaml);
-      fail("Should report invalid YAML");
+      //yaml.load(strYaml);
+      //TODO FIXME fail("Should report invalid YAML");
     } catch (YAMLException e) {
-      assertTrue(false);
-    } catch (Error e) {
-      //TODO FIXME it runs for 35 seconds !!!
+      assertEquals("Nesting Depth exceeded max 25", e.getMessage());
     }
   }
 }

programming_language: Java