CVE-2023-26479 (Java, published 2023-03-02T18:15Z)
Description: value: 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.

Note 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.

This 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.
Severity: nan (CVSS v3 base score: 6.5)
Weakness: CWE-755 \u2014 Improper Handling of Exceptional Conditions
The product does not handle or incorrectly handles an exceptional condition.
Repository: https://github.com/xwiki/xwiki-platform
Files changed:
xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-parser/src/main/java/org/xwiki/rendering/internal/parser/DefaultContentParser.java
xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-parser/src/test/java/org/xwiki/rendering/internal/parser/DefaultContentParserTest.java
Commit message: XWIKI-19838: Improve handling of unexpected parser errors


Vulnerable code:
        return getParser(syntax).parse(new StringReader(content == null ? "" : content));
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.util.Collections;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.parser.ContentParser;
import org.xwiki.test.mockito.MockitoComponentMockingRule;
 * Unit tests for {@link org.xwiki.rendering.internal.parser.DefaultContentParser}.
public class DefaultContentParserTest
    @Rule
    public final MockitoComponentMockingRule<ContentParser> mocker =
        new MockitoComponentMockingRule<>(DefaultContentParser.class);

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @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()));
        EntityReferenceSerializer<String> serializer = mocker.getInstance(EntityReferenceSerializer.TYPE_STRING);
        when(serializer.serialize(DOCUMENT_REFERENCE)).thenReturn(SOURCE);
    public void parseHasNoMetadataSource() throws Exception
        XDOM xdom = mocker.getComponentUnderTest().parse("", Syntax.PLAIN_1_0);
    public void parseIsAddingMetadataSource() throws Exception
        XDOM xdom = mocker.getComponentUnderTest().parse("", Syntax.PLAIN_1_0, DOCUMENT_REFERENCE);
    public void parseWhenNoParser() 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);
    public void parseWhenNullSource() throws Exception
        XDOM xdom = mocker.getComponentUnderTest().parse(null, Syntax.PLAIN_1_0);

Fixed code:
        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);
        }
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xwiki.rendering.parser.ParseException;
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 DefaultContentParser}.
@ComponentTest
class DefaultContentParserTest

    @InjectMockComponents
    private DefaultContentParser defaultContentParser;

    @MockComponent
    private EntityReferenceSerializer<String> serializer;
    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);
    void parseHasNoMetadataSource() throws Exception
        XDOM xdom = this.defaultContentParser.parse("", PLAIN_1_0);
    void parseIsAddingMetadataSource() throws Exception
        XDOM xdom = this.defaultContentParser.parse("", PLAIN_1_0, DOCUMENT_REFERENCE);
    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
        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());
    void parseWhenNullSource() throws Exception
        XDOM xdom = this.defaultContentParser.parse(null, Syntax.PLAIN_1_0);