CVE-2023-26479 (published 2023-03-02T18:15Z)

XWiki Platform is a generic wiki platform. Starting in version 6.0, users with write rights can insert well-formed content that is not handled well by the parser. As a consequence, some pages becomes unusable, including the user index (if the page containing the faulty content is a user page) and the page index.\n\nNote that on the page, the normal UI is completely missing and it is not possible to open the editor directly to revert the change as the stack overflow is already triggered while getting the title of the document. This means that it is quite difficult to remove this content once inserted.\n\nThis has been patched in XWiki 13.10.10, 14.4.6, and 14.9-rc-1. A temporary workaround to avoid Stack Overflow errors is to increase the memory allocated to the stack by using the `-Xss` JVM parameter (e.g., `-Xss32m`). This should allow the parser to pass and to fix the faulty content. The consequences for other aspects of the system (e.g., performance) are unknown, and this workaround should be only be used as a temporary solution. The workaround does not prevent the issue occurring again with other content. Consequently, it is strongly advised to upgrade to a version where the issue has been patched.

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

Weakness information:
cwe_id: CWE-755
cwe_name: Improper Handling of Exceptional Conditions
description: The product does not handle or incorrectly handles an exceptional condition.
extended_description: 
            
         
         

Fix information:
repo_url: https://github.com/xwiki/xwiki-platform
commit_details: repo_url: https://github.com/xwiki/xwiki-platform
author: Manuel Leduc
author_date: 2022-10-11 14:53:22+02:00
msg: XWIKI-19838: Improve handling of unexpected parser errors
repository: repo_url: https://github.com/xwiki/xwiki-platform
repo_name: xwiki/xwiki-platform
description: The XWiki platform
repo_language: Java
owner: xwiki
file_changes: filename: DefaultContentParser.java
change_type: ModificationType.MODIFY
diff: @@ -59,7 +59,15 @@ public class DefaultContentParser implements ContentParser
     @Override
     public XDOM parse(String content, Syntax syntax) throws ParseException, MissingParserException
     {
-        return getParser(syntax).parse(new StringReader(content == null ? "" : content));
+        Parser parser = getParser(syntax);
+        try {
+            return parser.parse(new StringReader(content == null ? "" : content));
+        } catch (StackOverflowError | Exception e) {
+            // All exceptions as well as stack overflow errors are captured and wrapped in parse exceptions to make sure
+            // that they are handled correctly by the callers. Without this, some parsing issues can be badly handled,
+            // leading to instability issues.
+            throw new ParseException(String.format("Failed to parse with syntax [%s].", syntax.toIdString()), e);
+        }
     }
 
     @Override

programming_language: Java
filename: DefaultContentParserTest.java
change_type: ModificationType.MODIFY
diff: @@ -19,93 +19,122 @@
  */
 package org.xwiki.rendering.internal.parser;
 
-import static org.hamcrest.CoreMatchers.any;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.mockito.hamcrest.MockitoHamcrest.argThat;
-import static org.mockito.Mockito.when;
-
 import java.io.Reader;
-import java.util.Collections;
 
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
+import org.hamcrest.CoreMatchers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 import org.xwiki.component.internal.ContextComponentManagerProvider;
 import org.xwiki.component.manager.ComponentLookupException;
 import org.xwiki.model.reference.DocumentReference;
 import org.xwiki.model.reference.EntityReferenceSerializer;
-import org.xwiki.rendering.block.Block;
 import org.xwiki.rendering.block.XDOM;
 import org.xwiki.rendering.listener.MetaData;
-import org.xwiki.rendering.parser.ContentParser;
 import org.xwiki.rendering.parser.MissingParserException;
+import org.xwiki.rendering.parser.ParseException;
 import org.xwiki.rendering.parser.Parser;
 import org.xwiki.rendering.syntax.Syntax;
 import org.xwiki.test.annotation.ComponentList;
-import org.xwiki.test.mockito.MockitoComponentMockingRule;
+import org.xwiki.test.junit5.mockito.ComponentTest;
+import org.xwiki.test.junit5.mockito.InjectMockComponents;
+import org.xwiki.test.junit5.mockito.MockComponent;
+import org.xwiki.test.mockito.MockitoComponentManager;
+
+import static java.util.Collections.emptyList;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+import static org.xwiki.rendering.syntax.Syntax.PLAIN_1_0;
+import static org.xwiki.rendering.syntax.Syntax.XWIKI_2_1;
 
 /**
- * Unit tests for {@link org.xwiki.rendering.internal.parser.DefaultContentParser}.
+ * Unit tests for {@link DefaultContentParser}.
  *
  * @version $Id$
  * @since 6.0M2
  */
+@ComponentTest
 @ComponentList(ContextComponentManagerProvider.class)
-public class DefaultContentParserTest
+class DefaultContentParserTest
 {
-    @Rule
-    public final MockitoComponentMockingRule<ContentParser> mocker =
-        new MockitoComponentMockingRule<>(DefaultContentParser.class);
-
-    @Rule
-    public ExpectedException thrown = ExpectedException.none();
-
     private static final DocumentReference DOCUMENT_REFERENCE = new DocumentReference("wiki", "space", "page");
+
     private static final String SOURCE = "wiki:space.page";
 
-    @Before
-    public void configure() throws Exception
-    {
-        Parser parser = mocker.registerMockComponent(Parser.class, Syntax.PLAIN_1_0.toIdString());
-        when(parser.parse(argThat(any(Reader.class)))).thenReturn(new XDOM(Collections.<Block>emptyList()));
+    @InjectMockComponents
+    private DefaultContentParser defaultContentParser;
+
+    @MockComponent
+    private EntityReferenceSerializer<String> serializer;
 
-        EntityReferenceSerializer<String> serializer = mocker.getInstance(EntityReferenceSerializer.TYPE_STRING);
-        when(serializer.serialize(DOCUMENT_REFERENCE)).thenReturn(SOURCE);
+    private Parser plain10parser;
+
+    @BeforeEach
+    void setUp(MockitoComponentManager componentManager) throws Exception
+    {
+        this.plain10parser = componentManager.registerMockComponent(Parser.class, PLAIN_1_0.toIdString());
+        when(this.plain10parser.parse(argThat(CoreMatchers.any(Reader.class)))).thenReturn(new XDOM(emptyList()));
+        when(this.serializer.serialize(DOCUMENT_REFERENCE)).thenReturn(SOURCE);
     }
 
     @Test
-    public void parseHasNoMetadataSource() throws Exception
+    void parseHasNoMetadataSource() throws Exception
     {
-        XDOM xdom = mocker.getComponentUnderTest().parse("", Syntax.PLAIN_1_0);
+        XDOM xdom = this.defaultContentParser.parse("", PLAIN_1_0);
 
         assertThat(xdom.getMetaData().getMetaData(MetaData.SOURCE), nullValue());
     }
 
     @Test
-    public void parseIsAddingMetadataSource() throws Exception
+    void parseIsAddingMetadataSource() throws Exception
     {
-        XDOM xdom = mocker.getComponentUnderTest().parse("", Syntax.PLAIN_1_0, DOCUMENT_REFERENCE);
+        XDOM xdom = this.defaultContentParser.parse("", PLAIN_1_0, DOCUMENT_REFERENCE);
 
         assertThat(xdom.getMetaData().getMetaData(MetaData.SOURCE), equalTo(SOURCE));
     }
 
     @Test
-    public void parseWhenNoParser() throws Exception
+    void parseWhenNoParser()
+    {
+        MissingParserException missingParserException = assertThrows(MissingParserException.class,
+            () -> this.defaultContentParser.parse("", XWIKI_2_1, DOCUMENT_REFERENCE));
+        assertEquals(ComponentLookupException.class, missingParserException.getCause().getClass());
+        assertEquals("Failed to find a parser for syntax [XWiki 2.1]", missingParserException.getMessage());
+    }
+
+    @Test
+    void parseWhenNoParserFail() throws Exception
+    {
+        when(this.plain10parser.parse(any())).thenThrow(StackOverflowError.class);
+
+        ParseException parseErrorException =
+            assertThrows(ParseException.class, () -> this.defaultContentParser.parse("content", PLAIN_1_0));
+
+        assertEquals(StackOverflowError.class, parseErrorException.getCause().getClass());
+        assertEquals("Failed to parse with syntax [plain/1.0].", parseErrorException.getMessage());
+    }
+
+    @Test
+    void parseWhenNoParserFailWithSource() throws Exception
     {
-        thrown.expect(MissingParserException.class);
-        thrown.expectMessage("Failed to find a parser for syntax [XWiki 2.1]");
-        thrown.expectCause(any(ComponentLookupException.class));
-        mocker.getComponentUnderTest().parse("", Syntax.XWIKI_2_1, DOCUMENT_REFERENCE);
+        when(this.plain10parser.parse(any())).thenThrow(StackOverflowError.class);
+
+        ParseException parseErrorException =
+            assertThrows(ParseException.class, () -> this.defaultContentParser.parse("content", PLAIN_1_0, null));
+
+        assertEquals(StackOverflowError.class, parseErrorException.getCause().getClass());
+        assertEquals("Failed to parse with syntax [plain/1.0].", parseErrorException.getMessage());
     }
 
     @Test
-    public void parseWhenNullSource() throws Exception
+    void parseWhenNullSource() throws Exception
     {
-        XDOM xdom = mocker.getComponentUnderTest().parse(null, Syntax.PLAIN_1_0);
+        XDOM xdom = this.defaultContentParser.parse(null, Syntax.PLAIN_1_0);
         assertEquals(0, xdom.getChildren().size());
     }
 }

programming_language: Java