VYPR
Medium severity5.3NVD Advisory· Published May 14, 2026· Updated May 15, 2026

CVE-2026-45205

CVE-2026-45205

Description

Uncontrolled Recursion vulnerability in Apache Commons.

When processing an untrusted configuration file, Commons Configuration will throw a StackOverflowError for YAML input with cycles. This issue affects Apache Commons: from 2.2 before 2.15.0.

Users are recommended to upgrade to version 2.15.0, which fixes the issue.

Affected products

1

Patches

1
b51f6bf26e77

Detect and avoid processing cycles in YAML input (YAMLConfiguration). (#634)

https://github.com/apache/commons-configurationGary GregoryMay 11, 2026via nvd-ref
6 files changed · +69 38
  • src/main/java/org/apache/commons/configuration2/AbstractYAMLBasedConfiguration.java+30 18 modified
    @@ -21,13 +21,16 @@
     import java.util.Collection;
     import java.util.Collections;
     import java.util.HashMap;
    +import java.util.HashSet;
     import java.util.List;
     import java.util.Map;
    +import java.util.Set;
     import java.util.stream.Collectors;
     
     import org.apache.commons.configuration2.ex.ConfigurationException;
     import org.apache.commons.configuration2.io.ConfigurationLogger;
     import org.apache.commons.configuration2.tree.ImmutableNode;
    +import org.apache.commons.lang3.StringUtils;
     
     /**
      * <p>
    @@ -69,44 +72,51 @@ private static void addEntry(final Map<String, Object> map, final String key, fi
         }
     
         /**
    -     * Creates a part of the hierarchical nodes structure of the resulting configuration. The passed in element is converted
    -     * into one or multiple configuration nodes. (If list structures are involved, multiple nodes are returned.)
    +     * Creates a part of the hierarchical nodes structure of the resulting configuration. The passed in element is converted into one or multiple configuration
    +     * nodes. (If list structures are involved, multiple nodes are returned.)
          *
    -     * @param key the key of the new node(s)
    -     * @param elem the element to be processed
    +     * @param key     the key of the new node(s).
    +     * @param elem    the element to be processed.
    +     * @param visited the set of visited objects.
          * @return a list with configuration nodes representing the element
          */
    -    private static List<ImmutableNode> constructHierarchy(final String key, final Object elem) {
    +    private static List<ImmutableNode> constructHierarchy(final String key, final Object elem, final Set<Object> visited) {
             if (elem instanceof Map) {
    -            return parseMap((Map<String, Object>) elem, key);
    +            return isVisisted(elem, visited) ? Collections.emptyList() : parseMap((Map<String, Object>) elem, key, visited);
             }
             if (elem instanceof Collection) {
    -            return parseCollection((Collection<Object>) elem, key);
    +            return isVisisted(elem, visited) ? Collections.emptyList() : parseCollection((Collection<Object>) elem, key, visited);
             }
             return Collections.singletonList(new ImmutableNode.Builder().name(key).value(elem).create());
         }
     
    +    private static boolean isVisisted(final Object elem, final Set<Object> visited) {
    +        return !visited.add(System.identityHashCode(elem));
    +    }
    +
         /**
          * Parses a collection structure. The elements of the collection are processed recursively.
          *
    -     * @param col the collection to be processed
    -     * @param key the key under which this collection is to be stored
    -     * @return a node representing this collection
    +     * @param col     the collection to be processed.
    +     * @param key     the key under which this collection is to be stored.
    +     * @param visited the set of visited objects.
    +     * @return a node representing this collection.
          */
    -    private static List<ImmutableNode> parseCollection(final Collection<Object> col, final String key) {
    -        return col.stream().flatMap(elem -> constructHierarchy(key, elem).stream()).collect(Collectors.toList());
    +    private static List<ImmutableNode> parseCollection(final Collection<Object> col, final String key, final Set<Object> visited) {
    +        return col.stream().flatMap(elem -> constructHierarchy(key, elem, visited).stream()).collect(Collectors.toList());
         }
     
         /**
          * Parses a map structure. The single keys of the map are processed recursively.
          *
    -     * @param map the map to be processed
    -     * @param key the key under which this map is to be stored
    +     * @param map     the map to be processed.
    +     * @param key     the key under which this map is to be stored.
    +     * @param visited the set of visited objects.
          * @return a node representing this map
          */
    -    private static List<ImmutableNode> parseMap(final Map<String, Object> map, final String key) {
    +    private static List<ImmutableNode> parseMap(final Map<String, Object> map, final String key, final Set<Object> visited) {
             final ImmutableNode.Builder subtree = new ImmutableNode.Builder().name(key);
    -        map.forEach((k, v) -> constructHierarchy(k, v).forEach(subtree::addChild));
    +        map.forEach((k, v) -> constructHierarchy(k, v, visited).forEach(subtree::addChild));
             return Collections.singletonList(subtree.create());
         }
     
    @@ -159,7 +169,9 @@ protected Map<String, Object> constructMap(final ImmutableNode node) {
          * @param map the map to be processed
          */
         protected void load(final Map<String, Object> map) {
    -        final List<ImmutableNode> roots = constructHierarchy("", map);
    -        getNodeModel().setRootNode(roots.get(0));
    +        final List<ImmutableNode> roots = constructHierarchy(StringUtils.EMPTY, map, new HashSet<>());
    +        if (!roots.isEmpty()) {
    +            getNodeModel().setRootNode(roots.get(0));
    +        }
         }
     }
    
  • src/main/java/org/apache/commons/configuration2/io/FileHandler.java+0 3 modified
    @@ -586,7 +586,6 @@ public void load(final File file) throws ConfigurationException {
             } catch (final MalformedURLException e1) {
                 throw new ConfigurationException("Cannot create URL from file %s", file);
             }
    -
             load(url);
         }
     
    @@ -687,7 +686,6 @@ public void load(final URL url) throws ConfigurationException {
          */
         private void load(final URL url, final FileLocator locator) throws ConfigurationException {
             InputStream in = null;
    -
             try {
                 final FileSystem fileSystem = FileLocatorUtils.getFileSystem(locator);
                 final URLConnectionOptions urlConnectionOptions = locator.getURLConnectionOptions();
    @@ -732,7 +730,6 @@ private void loadFromStream(final InputStream in, final String encoding, final U
             syncSupport.lock(LockMode.WRITE);
             try {
                 injectFileLocator(url);
    -
                 if (getContent() instanceof InputStreamSupport) {
                     loadFromStreamDirectly(in);
                 } else {
    
  • src/main/java/org/apache/commons/configuration2/YAMLConfiguration.java+5 15 modified
    @@ -21,7 +21,6 @@
     import java.io.InputStream;
     import java.io.Reader;
     import java.io.Writer;
    -import java.util.Map;
     
     import org.apache.commons.configuration2.ex.ConfigurationException;
     import org.apache.commons.configuration2.io.InputStreamSupport;
    @@ -68,8 +67,7 @@ public YAMLConfiguration(final HierarchicalConfiguration<ImmutableNode> c) {
     
         public void dump(final Writer out, final DumperOptions options)
                 throws ConfigurationException, IOException {
    -        final Yaml yaml = new Yaml(options);
    -        yaml.dump(constructMap(getNodeModel().getNodeHandler().getRootNode()), out);
    +        new Yaml(options).dump(constructMap(getNodeModel().getNodeHandler().getRootNode()), out);
         }
     
         /**
    @@ -81,19 +79,15 @@ public void dump(final Writer out, final DumperOptions options)
         @Override
         public void read(final InputStream in) throws ConfigurationException {
             try {
    -            final Yaml yaml = createYamlForReading(new LoaderOptions());
    -            final Map<String, Object> map = yaml.load(in);
    -            load(map);
    +            load(createYamlForReading(new LoaderOptions()).load(in));
             } catch (final Exception e) {
                 rethrowException(e);
             }
         }
     
         public void read(final InputStream in, final LoaderOptions options) throws ConfigurationException {
             try {
    -            final Yaml yaml = createYamlForReading(options);
    -            final Map<String, Object> map = yaml.load(in);
    -            load(map);
    +            load(createYamlForReading(options).load(in));
             } catch (final Exception e) {
                 rethrowException(e);
             }
    @@ -102,19 +96,15 @@ public void read(final InputStream in, final LoaderOptions options) throws Confi
         @Override
         public void read(final Reader in) throws ConfigurationException {
             try {
    -            final Yaml yaml = createYamlForReading(new LoaderOptions());
    -            final Map<String, Object> map = yaml.load(in);
    -            load(map);
    +            load(createYamlForReading(new LoaderOptions()).load(in));
             } catch (final Exception e) {
                 rethrowException(e);
             }
         }
     
         public void read(final Reader in, final LoaderOptions options) throws ConfigurationException {
             try {
    -            final Yaml yaml = createYamlForReading(options);
    -            final Map<String, Object> map = yaml.load(in);
    -            load(map);
    +            load(createYamlForReading(options).load(in));
             } catch (final Exception e) {
                 rethrowException(e);
             }
    
  • src/test/java/org/apache/commons/configuration2/TestYAMLConfiguration.java+15 1 modified
    @@ -34,6 +34,7 @@
     import java.util.Map;
     
     import org.apache.commons.configuration2.ex.ConfigurationException;
    +import org.apache.commons.configuration2.io.FileHandler;
     import org.junit.jupiter.api.BeforeEach;
     import org.junit.jupiter.api.Test;
     import org.junit.jupiter.api.io.TempDir;
    @@ -68,9 +69,22 @@ void testCopyConstructor() {
             assertEquals("bar", yamlConfiguration.getString("foo"));
         }
     
    +    @Test
    +    void testCycle() throws ConfigurationException {
    +        final YAMLConfiguration configuration = new YAMLConfiguration();
    +        final FileHandler handler = new FileHandler(configuration);
    +        handler.load(new File("src/test/resources/org/apache/commons/configuration2/yaml/cycle.yaml"));
    +    }
    +
         @Test
         void testDoubleStringValues() {
    -        final Object property = yamlConfiguration.getProperty("key5.example");
    +        final Object property = yamlConfiguration.getProperty("key5.example2");
    +        assertEquals(Arrays.asList("a", "a", "value"), property);
    +    }
    +
    +    @Test
    +    void testDoubleStringEmptyValues() {
    +        final Object property = yamlConfiguration.getProperty("key5.example1");
             assertEquals(Arrays.asList("", "", "value"), property);
         }
     
    
  • src/test/resources/org/apache/commons/configuration2/yaml/cycle.yaml+17 0 added
    @@ -0,0 +1,17 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    +# contributor license agreements.  See the NOTICE file distributed with
    +# this work for additional information regarding copyright ownership.
    +# The ASF licenses this file to You under the Apache License, Version 2.0
    +# (the "License"); you may not use this file except in compliance with
    +# the License.  You may obtain a copy of the License at
    +#
    +#      https://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +root: &cycle
    +  - *cycle
    
  • src/test/resources/test.yaml+2 1 modified
    @@ -26,4 +26,5 @@ martin:
         skill: Elite
     
     key5:
    -  example: [ '', '', value]
    \ No newline at end of file
    +  example1: [ '', '', value]
    +  example2: [ 'a', 'a', value]  
    \ No newline at end of file
    

Vulnerability mechanics

AI mechanics synthesis has not run for this CVE yet.

References

3

News mentions

0

No linked articles in our index yet.