Skip to content

Add a PE fixture whose TLS zero fill is larger than the image - #233

Open
zardus wants to merge 1 commit into
masterfrom
feature/tls-zerofill-bound
Open

Add a PE fixture whose TLS zero fill is larger than the image#233
zardus wants to merge 1 commit into
masterfrom
feature/tls-zerofill-bound

Conversation

@zardus

@zardus zardus commented Sep 11, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Problem

SizeOfZeroFill in a PE's TLS directory says how much zero fill follows a
module's initial thread-local data. cle adds it to the data size to get
tls_block_size and then materialises the whole block. It is a raw 32-bit
header field and nothing bounds it, so a file can ask cle for up to 4 GiB of
zeroes whatever its own size is.

No object this repository tracks can show that. All 1,851 tracked files were
walked at fc07821: 30 parse as PE and carry a TLS directory, and every one
of the 30 has SizeOfZeroFill == 0
. The largest TLS block among them is 520
bytes, in tests/x86/windows/TLS.exe. The 58 ELF objects with a PT_TLS
segment top out at 4,104 bytes of p_memsz.

Root cause

Nothing here exercises a large SizeOfZeroFill because nothing here has one.
The consumer needs an object whose declared TLS block does not fit in its own
SizeOfImage, and at fc07821 no tracked file has that shape.

Fix

tests/x86/windows/TLS_huge_zero_fill.exe is a byte copy of the tracked
tests/x86/windows/TLS.exe with that one field changed: the little-endian dword
at file offset 0x68ec goes from 0 to 0xf0000000. The low three bytes are
already zero in the original, so on disk this is one byte, and the two files
are the same length:

$ cmp -l tests/x86/windows/TLS.exe tests/x86/windows/TLS_huge_zero_fill.exe
26864   0 360
file bytes sha256
tests/x86/windows/TLS.exe 36,864 5a9327f87f994a76c84c986a498e105be183a558b520fe6004ceb6e498a7489d
tests/x86/windows/TLS_huge_zero_fill.exe 36,864 f34b38135cd3bd947061ae4d1880a479a990bf12e463179b60b7b73b64ba8c9b

tests_src/tls_huge_zero_fill/build.sh records the edit and rebuilds the object
from the tracked TLS.exe. Deleting the fixture and rerunning that script
reproduces the sha256 above, so there is no provenance to argue about: every byte
either comes from a file this repository already tracks or is the one documented
edit.

Testing

This repository runs no CI. Loaded with cle at master 0e77ade3, the pair
behaves as follows, under ulimit -v 2621440:

tls_data_size tls_block_size loader.tls.new_thread()
TLS.exe (control) 520 520 returns
TLS_huge_zero_fill.exe 520 4,026,532,360 MemoryError

Loader(...) itself succeeds on both. The allocation happens later, in the
ljust(obj.tls_block_size, b"\0") at cle/backends/tls/tls_object.py:46, and
that is where the MemoryError above is raised — before the second copy
Clemory.add_backer would make of the result.

The fixture is a hand edit rather than compiler output, and it is committed
rather than made at test time because a test that patches a copy at run time
still loads an object nothing tracks.

The consumer is a cle change that bounds the field and keeps a regression test
over this pair — the control asserts that nothing about a well-formed TLS
directory changes. Its description will reference this pull request, so CI
resolves the fixture from here, and it cannot merge first. Validation: #233 (comment)

session: sharpen

TLS_huge_zero_fill.exe is a byte copy of the tracked TLS.exe with one field
changed: the little-endian dword at file offset 0x68ec,
IMAGE_TLS_DIRECTORY32.SizeOfZeroFill, goes from 0 to 0xf0000000. On disk that
is a single byte, 0x68ef, from 0x00 to 0xf0; the two files are otherwise
identical and the same length.

cle takes that field at face value, so the fixture asks it for a 3.75 GiB
thread local storage block out of a 124 KiB image.

tests_src/tls_huge_zero_fill/build.sh records the edit and reproduces the
object byte for byte.
@zardus

zardus commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head e2394bdbbdc284d4d7d174e4cd2c05e9319f1782, one commit on fc07821c89535534979b02760e7e1bfc35faf690.
Working tree clean.

The fixture is the tracked file plus one byte

$ cmp -l tests/x86/windows/TLS.exe tests/x86/windows/TLS_huge_zero_fill.exe
26864   0 360
$ sha256sum tests/x86/windows/TLS.exe tests/x86/windows/TLS_huge_zero_fill.exe
5a9327f87f994a76c84c986a498e105be183a558b520fe6004ceb6e498a7489d  tests/x86/windows/TLS.exe
f34b38135cd3bd947061ae4d1880a479a990bf12e463179b60b7b73b64ba8c9b  tests/x86/windows/TLS_huge_zero_fill.exe

cmp -l counts bytes from 1, so its 26864 is file offset 0x68ef, which goes
from 0x00 to 0xf0. Both files are 36,864 bytes. Deleting the fixture and running
tests_src/tls_huge_zero_fill/build.sh rewrites it with the same sha256, so the
recorded construction is the construction.

Shape scan over the tracked objects

How many tracked PEs declare a TLS block larger than their own SizeOfImage --
the property the consumer needs and nothing here had. 0 of 30 at the merge
base, 1 of 31 at this head.
The scanner's complete output at both revisions is
in the output comment. The 58 tracked ELF objects with a PT_TLS segment are
unaffected and top out at 4,104 bytes of p_memsz.

Loaded with cle at master 0e77ade3c39a3cee05f65051e57955675e1ac21b

Under ulimit -v 2621440, cle.Loader(path) then loader.tls.new_thread():

tls_data_size tls_block_size new_thread()
TLS.exe 520 520 returns <PETLSObject Object cle##tls, maps [0x600000:0x600607]>
TLS_huge_zero_fill.exe 520 4,026,532,360 MemoryError

Loader succeeds on both. This repository runs no CI, so there is no job to cite.

@zardus

zardus commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

The shape the consumer needs, counted over every tracked file, before and
after this change: how many objects declare a TLS block larger than their own
SizeOfImage. Complete output of the scanner, which walks git ls-files,
parses every MZ file and reads data directory 9.

Before — no tracked object has it, so the consumer has nothing to assert on:

merge base fc07821
searched 1851 tracked files, 106 parse as PE, 30 carry a TLS directory
largest declared TLS block: 520 bytes (tests/x86/windows/TLS.exe)
0 declare a block larger than their own image

After — one does, and it is the tracked TLS.exe with a single byte changed:

this head e2394bd
  over: tests/x86/windows/TLS_huge_zero_fill.exe block 4026532360 SizeOfImage 126976
searched 1853 tracked files, 107 parse as PE, 31 carry a TLS directory
largest declared TLS block: 4026532360 bytes (tests/x86/windows/TLS_huge_zero_fill.exe)
1 declare a block larger than their own image

The two extra tracked files are the fixture and
tests_src/tls_huge_zero_fill/build.sh, which rebuilds it. The 58 tracked ELF
objects with a PT_TLS segment are unaffected and still top out at 4,104 bytes
of p_memsz.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant