This is an automated email from the ASF dual-hosted git repository.

leginee pushed a commit to branch fix-letent-use-after-free-bug
in repository https://gitbox.apache.org/repos/asf/openoffice.git

commit e6374a31c1e6b1b5e9f56944ee51c0e6c125c623
Author: Peter Kovacs <[email protected]>
AuthorDate: Mon Jun 15 23:25:32 2026 +0200

    This fixes a latent UAF, found in a debug-CRT-deterministic session,
    but present in all builds.
    ScViewData::ReadUserDataSequence (viewdata.cxx:2821) does delete
    pTabData[nTab]; pTabData[nTab] = new ScViewDataTable; per sheet but
    never refreshes pThisTab (which pointed at pTabData[nTabNo]). Back in
    ScTabView::SetTabNo, line 1660 reads aViewData.
    GetActivePart() → pThisTab->eWhichActive before line 1663 fixes
    pThisTab → use-after-free → AV sc!ScTabView::SetTabNo
    mov ecx,[eax+edx*4+0x664] with edx=0xDDDDDDDD (pGridWin[0xdddddddd]).
    
    This is a genuine latent UAF (reading a dangling pointer is UB);
    it is masked in release by unspecified allocator behaviour.
    The delete and new run in the same-thread, adjacent, same size class,
    with nothing between them. Mainstream allocators keep per-size free
    lists with MRU/LIFO reuse. New almost always returns the just-freed
    block — and when it does, pThisTab == pTabData[nTabNo] is true again and
    points at the live, freshly constructed object.
    
    But it is not guaranteed, and can surface non-deterministically
    (AI anaysis, may be wrong):
    - Windows LFH randomizes allocation slots (Win8+ exploit mitigation),
      so new may return a different slot. In release the freed block isn't
      poisoned, so the immediate read usually still sees plausible old bytes
       — but the window here is not two instructions: the rest of
      ReadUserDataSequence parses all other sheets (lots of allocation),
      any of which can reclaim that block and overwrite the eWhichActive
      offset with a large value → pGridWin[garbage] → a rare,
      timing-dependent release crash.
    - Hardened/diagnostic allocators (PageHeap, Application Verifier, ASan,
      a custom operator new) don't do MRU reuse at all → they crash release
      too.
    - The debug CRT (MSVCR90D) is just the deterministic trigger: it
      poison-fills freed blocks with 0xDD and delays reuse, so new returns
      a different block every time.
---
 main/sc/source/ui/view/viewdata.cxx | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/main/sc/source/ui/view/viewdata.cxx 
b/main/sc/source/ui/view/viewdata.cxx
index 26b4b3ec50..cabcd6eaa9 100644
--- a/main/sc/source/ui/view/viewdata.cxx
+++ b/main/sc/source/ui/view/viewdata.cxx
@@ -2952,6 +2952,15 @@ void ScViewData::ReadUserDataSequence(const 
uno::Sequence <beans::PropertyValue>
             pTabData[nZoomTab]->aPageZoomY = aDefPageZoomY;
         }
 
+       // The loop above delete'd and re-new'd pTabData[] entries (including 
the
+       // active one) but left pThisTab pointing at a freed ScViewDataTable.  
Restore
+       // the pThisTab == pTabData[nTabNo] invariant before anyone 
dereferences it
+       // (e.g. ScTabView::SetTabNo -> GetActivePart()).  Mirrors SetTabNo 
(line
+       // ~1502).  Without it, a debug build AVs on document open (reads 
0xDDDDDDDD);
+       // release masks it only via allocator MRU reuse.
+       CreateTabData( nTabNo );
+       pThisTab = pTabData[nTabNo];
+
        if (nCount)
                SetPagebreakMode( bPageMode );
 

Reply via email to