Fix CFB v3 OLE writer header and DIFAT boundaries #4811 - #4983
Open
n3crosis wants to merge 1 commit into
Open
Conversation
The legacy XLS writer emitted a non-canonical CFB v3 header and malformed DIFAT metadata when exactly 109 FAT sectors were needed. It also miscounted DIFAT capacity beyond that boundary. Use the fixed CFB v3 sector profile, write minor version 0x003E, correct DIFAT allocation, and reject oversized v3 stream layouts. Add boundary and round-trip coverage.
n3crosis
force-pushed
the
fix/ole-cfb-root-conformance
branch
from
September 4, 2026 13:23
b94f86d to
cd2298f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is:
Checklist:
Why this change is needed?
The legacy XLS writer creates a CFB/OLE version-3 container through
Shared/OLE/PPS/Root. This PR corrects the following CFB conformance defects:The writer emitted header minor version
0x003B; it now emits0x003E. MS-CFB specifies0x003Eas the version-3/4 minor version value. MS-CFB §2.2, Compound File HeaderThe header always declared the CFB v3 512-byte-sector and 64-byte mini-sector layout, while the serializer could internally choose different power-of-two sizes. The writer is now explicitly limited to the v3 512/64-byte profile, which matches the header and the required version-3 sector shifts. MS-CFB §2.2, Compound File Header
Exactly 109 FAT-sector locations fit in the header DIFAT, but the writer created an inconsistent extended-DIFAT header at that boundary: it declared zero DIFAT sectors while setting a first DIFAT sector. This is the issue reported in 当 SAT扇区数正好等于109时,生成的 xls 文件有问题 #4811. The writer now records
ENDOFCHAINand a zero DIFAT count at the 109-FAT boundary. MS-CFB §2.5, DIFAT SectorsExtended-DIFAT capacity was calculated as 128 FAT locations per DIFAT sector. Each DIFAT sector actually has 127 FAT-sector locations and one next-DIFAT-sector pointer. The writer now correctly creates two linked DIFAT sectors for 237 FAT sectors. MS-CFB §2.5, DIFAT Sectors
The v3 writer could serialize an individual stream or aggregate root mini-stream larger than 2 GiB, even though the v3 stream-size field requires the upper 32 bits to be zero. It now rejects these layouts before writing output, and also rejects unrepresentable regular-sector counts. MS-CFB §2.6.1, Compound File Directory Entry
Defect-to-code mapping
Non-canonical v3 header and mismatched sector profile: the writer previously wrote
0x003Bwhile allowing its internal sector sizes to be rounded to arbitrary powers of two. It now writes the canonical minor version and uses named fixed-profile constants.Malformed 109-FAT header in 当 SAT扇区数正好等于109时,生成的 xls 文件有问题 #4811: the old strict comparison treated 109 FAT sectors as requiring extended DIFAT metadata. The inclusive comparison keeps all 109 locations in the header and writes
ENDOFCHAINwith a zero DIFAT count.Incorrect two-DIFAT boundary: a 512-byte DIFAT sector contains 128 DWORDs. Its first 127 DWORDs identify FAT sectors; its final DWORD points to the next DIFAT sector. The header identifies the first 109 FAT sectors, so one DIFAT sector can identify only
109 + 127 = 236FAT sectors. A 237-FAT file therefore needs two DIFAT sectors. The old* $iBlCntcalculation treated all 128 DWORDs as FAT locations and incorrectly declared one DIFAT sector sufficient;* ($iBlCnt - 1)reserves the chain-pointer DWORD. The identical correction insaveBbd()ensures the emitted DIFAT chain matches the header count.Invalid oversized v3 stream layouts: each file stream and the aggregate root mini-stream are checked before serialization. The maximum is inclusive at
0x80000000, as required by CFB v3.Unrepresentable sector IDs: after calculating the complete layout, the writer rejects a result whose regular-sector count exceeds the CFB maximum.
New
OLEPpsRootTestcoverage verifies:OLEPpsRootTestincludes a temporary test-localgetDataByName()helper for the mini-stream reader round-trip. It can be removed onceOLE::getDataByName()is available on the target branch.Validation completed:
Manual verification for #4811
From the repository root, generate the reported 63,000-row fixture with a sufficiently large PHP memory limit:
Inspect offsets 44, 68, and 72 in the generated CFB header. The expected values are
109FAT sectors,0xFFFFFFFE(ENDOFCHAIN) as the first DIFAT sector, and0DIFAT sectors. The generated file can then be opened in Excel to verify that it no longer shows the repair dialog described in #4811.