mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
54 lines
3.1 KiB
Markdown
54 lines
3.1 KiB
Markdown
|
|
# Survey: command parser/dispatcher (command.cpp)
|
||
|
|
|
||
|
|
Audit of the command parser in `mux/modules/engine/command.cpp` — the second of
|
||
|
|
the three parsers the boolexp header names (functions = eval.cpp, commands =
|
||
|
|
command.cpp, locks = boolexp.cpp). It processes every line of player input:
|
||
|
|
`process_command` (decompose → match → dispatch) and `process_cmdent` (permission
|
||
|
|
+ argument parsing + handler call). Methodology matches the boolexp/wild/JIT
|
||
|
|
campaign. **Result: CLEAN — no memory-safety or unbounded-recursion bug found.**
|
||
|
|
|
||
|
|
## Why it's well-hardened (unlike boolexp's parser)
|
||
|
|
|
||
|
|
**Recursion is structurally bounded.** The boolexp DoS (#839) was unbounded
|
||
|
|
parser recursion reachable by any player. command.cpp has no equivalent:
|
||
|
|
- Regular commands in action lists are **queued** (`wait_que` → bounded by the
|
||
|
|
command-queue cycle limits), not run on the C stack.
|
||
|
|
- The only direct stack-recursion paths — `@assert/inline`, `@break/inline`,
|
||
|
|
`@lua/inline` (`process_command`/Eval called directly) — are **`CA_WIZARD`**
|
||
|
|
(the `/inline` switch is wizard-only; `@lua/inline` re-checks `Wizard`), and
|
||
|
|
even then bounded by the LBUF command-string length (~2048 levels of
|
||
|
|
`@break/inline 1=…`; verified 500 survives, longer chains truncate at the LBUF
|
||
|
|
input cap, rc=0, no crash).
|
||
|
|
- `@train` has its own `train_nest_lev` guard.
|
||
|
|
- Function/`[u()]` nesting inside commands is bounded by `func_nest_lim` /
|
||
|
|
`func_invk_lim` (reset per top-level command) and the AST evaluator's
|
||
|
|
`nStackNest`/`bStackLimitReached` spam guard (incremented in ast.cpp).
|
||
|
|
|
||
|
|
**Buffers are bounded.**
|
||
|
|
- `process_command`'s working buffers (`preserve_cmd`, `SpaceCompressCommand`,
|
||
|
|
`LowerCaseCommand`) are `thread_local UTF8[LBUF_SIZE]` — not stack arrays, so
|
||
|
|
deep call chains don't balloon the stack.
|
||
|
|
- The space-compress loop and command-name extraction use the loose
|
||
|
|
`q < buf + LBUF_SIZE` guard (off-by-one vs `-1`, like boolexp's object-ref
|
||
|
|
loop) but are **not exploitable**: command input is always NUL-terminated
|
||
|
|
within `LBUF_SIZE-1`, so the loop stops on the NUL before `q` reaches the end.
|
||
|
|
Verified: a 32760-char single-token command and a max-length spaced command
|
||
|
|
both survive (no OOB, rc=0).
|
||
|
|
- Fixed buffers all use bounded fills: `switch_buff[200]` (`mux_strncpy`),
|
||
|
|
`pkg[SBUF_SIZE]` in `handle_gmcp` (`nPkg` clamped), `result[8000]` in `do_lua`
|
||
|
|
(`sizeof(result)` passed to the Lua control), `check2[UTF8_SIZE4+1]`,
|
||
|
|
`qbuff[I64BUF_SIZE]`.
|
||
|
|
|
||
|
|
**Argument parsing delegates to bounded helpers.** `process_cmdent` splits args
|
||
|
|
via `parse_to` / `parse_arglist(..., args, MAX_ARG, ..., &nargs)` — the shared,
|
||
|
|
`MAX_ARG`-bounded splitters — into `alloc_lbuf` buffers that are freed on every
|
||
|
|
path. `CS_*` calling-sequence handling is table-driven.
|
||
|
|
|
||
|
|
## Surfaces examined
|
||
|
|
|
||
|
|
`process_command` (decompose, prefix/single-letter leadins, `@icmd`/hook checks,
|
||
|
|
switch stripping), `process_cmdent` (perms, switch loop, `CS_NO_ARG`/`CS_ONE_ARG`/
|
||
|
|
`CS_TWO_ARG`/`CS_ARGV` arg parsing, hooks), `handle_gmcp` (telnet GMCP → A_GMCP),
|
||
|
|
`do_lua`, `do_assert`/`do_break`/`do_train` (recursion paths), `is_prefix_cmd`,
|
||
|
|
`cmdtest`/`zonecmdtest`. All bounded or wizard-gated. No fix needed.
|