sw/source/filter/html/htmlctxt.cxx |   14 ++++++++------
 sw/source/filter/html/swhtml.cxx   |   13 ++++++++++---
 sw/source/filter/html/swhtml.hxx   |    4 +++-
 3 files changed, 21 insertions(+), 10 deletions(-)

New commits:
commit 9a79d74342a05f87365ab60ad057b1329a3bf216
Author:     Caolán McNamara <[email protected]>
AuthorDate: Fri Mar 6 09:00:40 2026 +0000
Commit:     Caolán McNamara <[email protected]>
CommitDate: Fri Mar 6 13:55:17 2026 +0100

    ofz#390465661 Timeout on deeply nested tags
    
    Try a nested element depth of 1024.
    
    Browsers have some: "adoption agency algorithm" thing that might be
    relevant to this:
    
https://html.spec.whatwg.org/multipage/parsing.html#adoption-agency-algorithm
    but that's a complicated thing.
    
    Change-Id: I469873ee0b3d569b709b562fb39b118a14a99be4
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201109
    Reviewed-by: Caolán McNamara <[email protected]>
    Tested-by: Jenkins

diff --git a/sw/source/filter/html/htmlctxt.cxx 
b/sw/source/filter/html/htmlctxt.cxx
index b32ce055efb2..ac8b70f61ad7 100644
--- a/sw/source/filter/html/htmlctxt.cxx
+++ b/sw/source/filter/html/htmlctxt.cxx
@@ -768,11 +768,12 @@ void SwHTMLParser::InsertAttrs( SfxItemSet &rItemSet,
         if( ppAttr )
         {
             // Set the attribute
-            NewAttr(m_xAttrTab, ppAttr, *pItem);
-
-            // and save context information
-            HTMLAttrs &rAttrs = pContext->GetAttrs();
-            rAttrs.push_back( *ppAttr );
+            if (NewAttr(m_xAttrTab, ppAttr, *pItem))
+            {
+                // and save context information
+                HTMLAttrs &rAttrs = pContext->GetAttrs();
+                rAttrs.push_back( *ppAttr );
+            }
         }
     }
 
@@ -791,7 +792,8 @@ void SwHTMLParser::InsertAttr( HTMLAttr **ppAttr, const 
SfxPoolItem & rItem,
     }
 
     // Set the attribute
-    NewAttr(m_xAttrTab, ppAttr, rItem);
+    if (!NewAttr(m_xAttrTab, ppAttr, rItem))
+        return;
 
     // save context information
     HTMLAttrs &rAttrs = pCntxt->GetAttrs();
diff --git a/sw/source/filter/html/swhtml.cxx b/sw/source/filter/html/swhtml.cxx
index df2bfcb1dcd7..11fe83cd96cb 100644
--- a/sw/source/filter/html/swhtml.cxx
+++ b/sw/source/filter/html/swhtml.cxx
@@ -3105,7 +3105,7 @@ void SwHTMLParser::SetAttr_( bool bChkEnd, bool 
bBeforeTable,
     aFields.clear();
 }
 
-void SwHTMLParser::NewAttr(const std::shared_ptr<HTMLAttrTable>& rAttrTable, 
HTMLAttr **ppAttr, const SfxPoolItem& rItem )
+bool SwHTMLParser::NewAttr(const std::shared_ptr<HTMLAttrTable>& rAttrTable, 
HTMLAttr **ppAttr, const SfxPoolItem& rItem )
 {
     // Font height and font colour as well as escape attributes may not be
     // combined. Therefore they're saved in a list and in it the last opened
@@ -3113,12 +3113,17 @@ void SwHTMLParser::NewAttr(const 
std::shared_ptr<HTMLAttrTable>& rAttrTable, HTM
     // attributes count is just incremented.
     if( *ppAttr )
     {
+        // limit chain depth to avoid getting stuck in BuildPortions
+        if ((*ppAttr)->m_nChainDepth >= HTMLAttr::MAX_CHAIN_DEPTH)
+            return false;
         HTMLAttr *pAttr = new HTMLAttr(*m_pPam->GetPoint(), rItem, ppAttr, 
rAttrTable);
+        pAttr->m_nChainDepth = (*ppAttr)->m_nChainDepth + 1;
         pAttr->InsertNext( *ppAttr );
         (*ppAttr) = pAttr;
     }
     else
         (*ppAttr) = new HTMLAttr(*m_pPam->GetPoint(), rItem, ppAttr, 
rAttrTable);
+    return true;
 }
 
 bool SwHTMLParser::EndAttr( HTMLAttr* pAttr, bool bChkEmpty )
@@ -5539,7 +5544,8 @@ HTMLAttr::HTMLAttr( const SwPosition& rPos, const 
SfxPoolItem& rItem,
     m_xAttrTab(std::move( xAttrTab )),
     m_pNext( nullptr ),
     m_pPrev( nullptr ),
-    m_ppHead( ppHd )
+    m_ppHead( ppHd ),
+    m_nChainDepth( 0 )
 {
 }
 
@@ -5556,7 +5562,8 @@ HTMLAttr::HTMLAttr( const HTMLAttr &rAttr, const SwNode 
&rEndPara,
     m_xAttrTab(std::move( xAttrTab )),
     m_pNext( nullptr ),
     m_pPrev( nullptr ),
-    m_ppHead( ppHd )
+    m_ppHead( ppHd ),
+    m_nChainDepth( 0 )
 {
 }
 
diff --git a/sw/source/filter/html/swhtml.hxx b/sw/source/filter/html/swhtml.hxx
index 5147d8e8c6fa..08d6e052ab81 100644
--- a/sw/source/filter/html/swhtml.hxx
+++ b/sw/source/filter/html/swhtml.hxx
@@ -149,6 +149,8 @@ class HTMLAttr
     HTMLAttr *m_pNext;   // still to close attributes with different values
     HTMLAttr *m_pPrev;   // already closed but not set attributes
     HTMLAttr **m_ppHead; // list head
+    sal_uInt16 m_nChainDepth; // depth of m_pNext chain
+    static constexpr sal_uInt16 MAX_CHAIN_DEPTH = 1024;
 
     HTMLAttr( const SwPosition& rPos, const SfxPoolItem& rItem,
                HTMLAttr **pHd, std::shared_ptr<HTMLAttrTable> xAttrTab );
@@ -533,7 +535,7 @@ class SwHTMLParser : public SfxHTMLParser, public 
SvtListener
     // start/end an attribute
     // ppDepAttr indicated an attribute table entry, which attribute has to be
     // set, before the attribute is closed
-    void NewAttr(const std::shared_ptr<HTMLAttrTable>& rAttrTab, HTMLAttr 
**ppAttr, const SfxPoolItem& rItem);
+    bool NewAttr(const std::shared_ptr<HTMLAttrTable>& rAttrTab, HTMLAttr 
**ppAttr, const SfxPoolItem& rItem);
     bool EndAttr( HTMLAttr *pAttr, bool bChkEmpty=true );
     void DeleteAttr( HTMLAttr* pAttr );
 

Reply via email to