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 );
