Download docs/research/SELECTOR_BOUNDS.md from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 9.14 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/SELECTOR_BOUNDS.md
- Command line
-
hf download hf://nmarshall/urt-rs/docs/research/SELECTOR_BOUNDS.md
-
curl -L -o SELECTOR_BOUNDS.md https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/SELECTOR_BOUNDS.md
Selector integer conversion, storage bounds and dispatcher entry routes
Static pass, 2026-10-11. Reproduce with python3 scripts/analyze_selector_bounds.py; check with python3 scripts/test_selector_bounds.py. Generated evidence retains verified QVM/native provenance, 18 integer instruction traces, composed digit/parser/slot-bound fixtures, name-copy call bounds, match insertion boundaries, dispatcher arguments and ten Linux ELF64 function graphs. This extends authentication queries. No original process or complete QVM runs; all CALLs remain recorded with zero placeholder results. The bounded evaluator can now stop explicitly at a helper's LEAVE instruction and capture its operand stack without implementing a real VM frame return.
BND-001: decimal parser and wrap behavior
Entry 37159 reads signed bytes through LOAD1/SEX8. It skips leading bytes whose signed value is at most 32, returning zero if it encounters NUL. Consequently bytes 128–255 also satisfy its leading-skip comparison: the standalone parser fixture ff 31 32 returns 12. This is separate from the selector's digit classifier, which sends such a string to name handling rather than numeric parsing.
After leading skipping, the parser consumes one optional + or -, initializes an accumulator to zero, and consumes digits until the first nondigit. Each digit performs accumulator * 10 + digit through MULI/ADD, and the result is multiplied by the sign. There is no visible range/overflow check, decimal-point handling or trailing-input validation.
| Input | Result in the bounded 32-bit evaluator |
|---|---|
Empty / whitespace / junk |
0 |
01, +12, -12junk, 12.5 |
1, 12, -12, 12 respectively |
2147483647 / 2147483648 |
2147483647 / -2147483648 |
4294967295 / 4294967296 / 4294967299 |
-1 / 0 / 3 |
-2147483648 / -2147483649 |
-2147483648 / 2147483647 |
The tests also compare actual instruction-loop results against independently computed modulo-2^32 arithmetic at multiple boundaries. This establishes the bounded evaluator's interpretation of the supplied instructions, not original interpreter/JIT runtime conformance. Original execution modes and cross-platform arithmetic remain to be checked.
Composed fixtures feed digit-only strings through the selector's actual classifier, this parser and its actual signed slot-bounds slice with a supplied count of four. 4294967296 and 4294967299 then pass as slots zero and three; 4294967295 and 2147483648 fail as negative slots. These are separate slices whose results are explicitly connected, not execution of the whole selector or a native command. The Rust selector contract must decide this compatibility behavior based on an original-runtime fixture rather than assuming a checked integer parse behaves identically.
BND-002: name copy and match-list capacity
Selector instructions 253692–253703 call copy helper 69338 with local destination 24, source record + 504 and capacity 64. The previous copy-helper recovery identifies game trap 102 / native strncpy. The helper's positive-capacity slice supplies native copy count 63 and explicitly writes NUL at destination offset 63. Source-null and capacity-below-one branches invoke the existing error helper; they are not treated as returning safely here.
The report composes a modeled first-63-byte copy with the actual cleaning instructions. Native libc is not invoked and padding/overlap/error behavior is not newly verified. Under that copy contract, 63 A bytes, 64 A bytes and 63 A bytes followed by X yield the same cleaned name. Color escapes also consume space before cleanup: a 64-byte source beginning with ^1 has only its first 63 bytes available for cleaning. Complete selection of those colliding names still depends on exact-match precedence and the selector text. The report does not claim a whole-selector collision test has run.
The name selector stores partial-match pointers at local 96 + match_count * 4 and increments match_count. Local 352 is the next recovered scalar temporary, so local offsets 96 through 348 form a 256-byte / 64-pointer region. Bounded insertion fixtures show:
| Prior match count | Written local offset | New count |
|---|---|---|
| 0 | 96 | 1 |
| 63 | 348 | 64 |
| 64 | 352 | 65 |
There is no explicit match-count bound in this insertion slice. The last fixture deliberately violates the intended count/layout invariant; it shows the 65th insertion crossing into local 352, not a demonstrated original-runtime overflow or a complete account of its effects. The surrounding name loop contributes at most one partial entry per visited slot and exits immediately on an exact match. A client count at most 64 therefore fits this region, subject to normal loop/state invariants.
The loop count at 34992524 is initialized at instruction 4220 from the integer mirror 34089604, identified as sv_maxclients (main registry default 12, raw flags 37). The same initialization region clears 405504 = 64 * 6336 bytes at VM address 101536 and sets the record-base global to that address. The QVM count-copy slice contains no clamp. All 109 literal references to the count address are indexed; the collector does not claim alias/bulk-write coverage.
Both Linux ELF64 SV_Startup graphs show the normal native startup correction path: integer values at most zero request 1, values above 64 request 64, then the cvar integer is reloaded for allocation. SV_BoundMaxClients similarly accepts a lower-bound argument and uses upper comparison 64. Native Cvar_Set timing/merge semantics, alternate startup/restart paths, dynamic max-client changes and maliciously inconsistent VM/native state are not verified here. Thus native startup evidence supports the normal 64-client invariant but does not prove that every selector invocation has a safe count.
BND-003: native/QVM/chat entry chain
Initialized QVM entry table 3536 maps command 0 to instruction 21, command 6 to 105, and command 9 to 125. Retained entry windows establish these routes:
- Initialization: case 0 forwards its three arguments to entry 3877. The native
SV_InitGameProgstail calls VM command zero after VM creation and time retrieval. This connects the count-copy initialization above and the previously recovered call to internal auth-mode setup at instruction 5042 to the native game initialization route; restart/re-entry scheduling is still open. - Client commands: case 6 forwards its first argument to entry 204035. The native
SV_ExecuteClientCommandtail calls VM command six, deriving a native client slot from its client pointer. Entry 204035 uses that slot to address 34091368 + slot * 880, checks object offset 520 for a nonzero record pointer and retrieves command argv 0. Complete native admission/flood/command filtering is retained in its graph but not fully specified by this pass. - Console commands: case 9 calls 262012 and returns its result. Native
SV_GameCommandreturns zero unless native server state equals 2; on that path it invokes VM command nine. This establishes a server-state gate and the console-dispatch entry, not native rcon authentication or shell/console access policy.
Entry 204035 compares command names against say, say_team and me, passing its object to chat handler 187911 with raw second arguments 0, 1 and 4, respectively, and a zero third argument. Three actual argument slices are retained.
Correction (2026-10-11): with ENTER frame 3340, incoming argument locals 3348/3352/3356 are the first/second/third arguments. Local 3352 holds channel mode, whereas the early branch reads 3356, the third argument. The original pass mistakenly mapped that branch to the second argument. All three observed say/say_team/me callers supply third argument zero, so all three take the path toward slash/auth checks. A synthetic nonzero third argument calls helper 180858 and jumps to the common send path at 189262; this is a third-argument bypass, not a channel restriction. Corrected fixtures keep channel modes 0/1/4 separate from this argument. Full channel formatting/recipient behavior remains open.
Direct scanning finds exactly the five adjacent call sites linking these three dispatcher targets: two in QVM entry 0 and three in entry 204035. Computed calls and native-to-VM transfers are separate evidence and are not counted as those adjacent QVM calls.
Completion and remaining work
RE-257–259 cover parser/slot composition, selector storage/count-source boundaries and cross-layer dispatcher routes. RE-256 remains open for complete parser/copy/match/runtime conformance and mode/trust behavior. RE-260 tracks interpreter/JIT arithmetic comparison, complete maxclients startup/restart/mutation invariants, native client-command filtering and nonzero chat-mode behavior. The rewrite still needs safe ownership/limits and evidence-backed compatibility contracts before implementation.