Repository navigation
emrg: a size or an offset written as a literal, beside the struct that could measure it #2517
Description
A size or an offset written as a literal, beside the struct that could measure it
The class
A Win32 structure is declared, and its size or a field's offset is written down as a number next to it instead of being measured from the declaration. The number is whatever the interpreter width the author happened to run on answered, and nothing on a host where that call never executes can contradict it.
This is not hypothetical. emrg/sandbox/win32/abi.py declared SECURITY_DESCRIPTOR_MIN_LENGTH = 20 — the 32-bit answer — while InitializeSecurityDescriptor fills a struct whose DACL pointer starts at offset 32 and whose total is 40 bytes on a 64-bit interpreter. registry.apply_dacl built the buffer from that constant, so every DACL application wrote 20 bytes past a create_string_buffer allocation. create_string_buffer bounds nothing, so nothing raised: the surplus write landed in the process heap and the failure surfaced later, in whichever test file the same xdist worker loaded next. It was reported as an access violation in a test with no connection to the registry code.
That instance was repaired by measuring the struct. The class it belongs to is not repaired, because nothing holds the next literal to a measurement.
What was measured on this checkout
One size-like literal remains in the Win32 ABI, and it is correct. abi.SECURITY_MAX_SID_SIZE = 68 equals ctypes.sizeof of a correctly declared SID on this 64-bit interpreter, and it is width-independent for a reason that can be checked rather than trusted: SID has no pointer field, so its layout does not move between a 32-bit and a 64-bit build. It is right, and that is exactly why it is worth a guard — it is right by the accident of the struct's shape, and nothing says so.
The same number is written out again in the tests. tests/test_bash_v2_win32.py passes ctypes.create_string_buffer(68) in two places with a comment naming the constant. If the constant ever changed, those two sites would keep the old number and go on passing, because the buffer would still be large enough for the SID they build. A comment is not a measurement.
A second family is hand-computed offsets. emrg/sandbox/win32/acl.py walks a variable-length ACL by reading types at +0, flags at +1, size at +2, the access mask at +4 and an inline SID from +8. Those five numbers are the ACE_HEADER layout followed by ACCESS_ALLOWED_ACE.Mask and SidStart. They are correct today — the header is three fields totalling four bytes and the ACE carries no pointer — but no declared structure names them, so a reader has no way to check them and no test would notice a drift.
What was measured and found not to be a defect
The other half of the hypothesis does not hold here. Every emrg/sandbox/win32/ module is exercised by at least one test file, and the file that carries nearly all of that coverage is not wholly gated: tests/test_bash_v2_win32.py collects 82 tests, of which 73 run and pass on this macOS host and 9 skip. A deliberate cross-platform half already exists in that file — the guard that caught this class's first instance was placed there for exactly that reason. So the remaining gap is not "a module nobody can test"; it is "a number nobody holds to a measurement".
What a check would look like
The shape that already works, extended — no code in this issue:
- Assert every size-like constant against the struct it claims to describe, on every platform, in the cross-platform half of the test file:
CONSTANT == ctypes.sizeof(TheStruct)where the struct is declared, and a second assertion that the last field the API writes fits inside it (LastField.offset + sizeof(void*) <= CONSTANT), which holds even where the first is trivially true. - For the hand-computed offsets, declare the header structure once and assert the offsets against it rather than reading them from comment prose; the same two-direction discipline applies, because a truncated declaration would shrink both sides together.
- Fail a literal beside a struct. Where a buffer is allocated or a pointer advanced, the size or offset must come from
ctypes.sizeof/ a field's.offset/ the API's own measurement, not from an integer. A check that flags an integer literal in acreate_string_buffercall, or an offset argument made only of constants, would have caught the original defect at its definition and would catch the next one on the macOS leg — which is the only place this host can catch anything at all in this area, since the Win32 calls themselves need Windows.
Why it is worth a check rather than another careful review
The defect crossed a merge with a green macOS leg, green unit tests, and an approving review that read the code closely. The reviewing cycle could see the number; it had no way to know what the number should be, because the answer depends on a struct that only exists on a platform that reviewer was not running. A constant held to sizeof is checked on every leg by construction — the declaration is pure Python and needs no Win32 call to evaluate.
Scope
A test-side guard plus, where it is missing, the structure declaration the guard measures against. No behaviour change, no version declaration, no packaging change.
Activity
argszero commentedon Oct 23, 2026
Handled by #2518