Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ public static ChunkHeader createChunkHeader(int documentVersion, byte[] data, in
ch.setType((int) LittleEndian.getUInt(data, offset));
ch.setId((int) LittleEndian.getUInt(data, offset + 4));
ch.setUnknown1((int) LittleEndian.getUInt(data, offset + 8));
ch.setLength((int) LittleEndian.getUInt(data, offset + 12));
// Match the v4/v5 branch below: reject lengths that would silently
// truncate when narrowing the uint32 to a signed int, rather than
// letting a wrapped value flow into the offset arithmetic in
// ChunkFactory.createChunk (offset + getLength() + sizeInBytes).
ch.setLength(Math.toIntExact(LittleEndian.getUInt(data, offset + 12)));
ch.setUnknown2(LittleEndian.getShort(data, offset + 16));
ch.setUnknown3(LittleEndian.getUByte(data, offset + 18));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ Licensed to the Apache Software Foundation (ASF) under one or more
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;

import org.apache.poi.hdgf.chunks.ChunkFactory.CommandDefinition;
import org.apache.poi.poifs.storage.RawDataUtil;
import org.apache.poi.util.LittleEndian;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -62,6 +64,50 @@ void testChunkHeaderA() {
assertTrue(header.hasSeparator());
}

/**
* A v6+ chunk header reads its Length field as a 32-bit unsigned integer.
* A crafted file with Length > Integer.MAX_VALUE used to be silently
* narrowed via a plain {@code (int)} cast, letting the wrapped value flow
* into the offset arithmetic in {@code ChunkFactory.createChunk}
* ({@code offset + getLength() + sizeInBytes}). Match the v4/v5 branch,
* which has always used {@code Math.toIntExact} for its Length field, and
* reject the input up-front.
*/
@Test
void testV11RejectsOversizedLength() {
// 19-byte v6+ header: type, id, unknown1, length, unknown2 (short), unknown3 (ubyte)
byte[] header = new byte[19];
LittleEndian.putUInt(header, 0, 0x46L); // type
LittleEndian.putUInt(header, 4, 0xFFFFFFFFL); // id (allowed -1 sentinel)
LittleEndian.putUInt(header, 8, 0x02L); // unknown1
LittleEndian.putUInt(header, 12, 0x80000001L); // length: would wrap to a negative int
LittleEndian.putShort(header, 16, (short)0);
header[18] = 0;

assertThrows(ArithmeticException.class,
() -> ChunkHeader.createChunkHeader(11, header, 0));
}

/**
* Lengths up to {@code Integer.MAX_VALUE} (still nonsensically large but
* representable) must continue to parse — the hardening is only meant to
* catch the silent-narrowing case, not to introduce a new lower ceiling.
* Downstream checks (e.g. {@code IOUtils.safelyClone}) handle bounding.
*/
@Test
void testV11AcceptsMaxIntLength() {
byte[] header = new byte[19];
LittleEndian.putUInt(header, 0, 0x46L);
LittleEndian.putUInt(header, 4, 0x01L);
LittleEndian.putUInt(header, 8, 0x02L);
LittleEndian.putUInt(header, 12, Integer.MAX_VALUE & 0xFFFFFFFFL);
LittleEndian.putShort(header, 16, (short)0);
header[18] = 0;

ChunkHeader h = ChunkHeader.createChunkHeader(11, header, 0);
assertEquals(Integer.MAX_VALUE, h.getLength());
}

@Test
void testChunkHeaderB() {
ChunkHeader h = ChunkHeader.createChunkHeader(11, data_b, 0);
Expand Down