Skip to content

server: refuse a CREATE2 whose tag does not fit the message - #216

Merged
pcarrier merged 1 commit into
mainfrom
eng/create2-tag-validation
Aug 6, 2026
Merged

server: refuse a CREATE2 whose tag does not fit the message#216
pcarrier merged 1 commit into
mainfrom
eng/create2-tag-validation

Conversation

@pcarrier

@pcarrier pcarrier commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #204, from reviewing the merged PTY lifecycle work.

The bug

CREATE2(WANT_STATUS) promises exactly one outcome, and docs/protocol.md says a malformed field answers INVALID. Four of the six fields do. The tag did not:

let tag = if data.len() >= 10 + tag_len {
    std::str::from_utf8(&data[10..10 + tag_len]).unwrap_or_default()
} else {
    ""
};

An out-of-range tag_len and a non-UTF-8 tag both fell back to an empty tag and let the create succeed.

The first is worse than a mislabelled terminal. cursor becomes 10 + tag_len regardless, so an overrunning length leaves it past the end of the message. The cwd and deadline arms bounds-check what they read and would refuse — but a CREATE2 carrying a command and neither of those has nothing left to catch it: data.get(cursor..) returns None, create_payload is None, and the server spawns the default shell instead of the command it was sent. A client that asked to run one thing silently gets a prompt.

The non-UTF-8 case is quieter: the terminal exists with an empty tag, so a client correlating by tag can never match it and has no refusal to react to.

The fix

Pull the read into create2_tag, which returns the tag or the detail string to refuse with, and refuse on both.

Extracted rather than inlined so the two failures are testable — the create arms need an AppState that nothing in the suite constructs, which is the same reason armed_deadline and slots_to_evict are pure.

Verification

Three tests: a well-formed tag (including one followed by later fields), a length past the end (off by one and far), and a non-UTF-8 tag. Checked they are not vacuous by restoring the empty-tag fallback and confirming both refusal tests fail.

Workspace clippy and cargo fmt clean, 290 blit-server tests passing.

`CREATE2(WANT_STATUS)` promises exactly one outcome and documents that a
malformed field answers `INVALID` (docs/protocol.md). Four of the six
fields do that. The tag did not: an out-of-range `tag_len` and a
non-UTF-8 tag both fell back to an empty tag and let the create succeed.

    let tag = if data.len() >= 10 + tag_len {
        std::str::from_utf8(&data[10..10 + tag_len]).unwrap_or_default()
    } else {
        ""
    };

Both cases are wrong, and the first is worse than a mislabelled
terminal. `cursor` is set to `10 + tag_len` regardless, so an
overrunning length leaves it past the end of the message. The cwd and
deadline arms bounds-check what they read and would refuse — but a
`CREATE2` that carries a command and neither of those has nothing left
to catch it: `data.get(cursor..)` returns `None`, `create_payload` is
`None`, and the server spawns the default shell instead of the command
it was sent. A client that asked to run one thing silently gets a
prompt.

The non-UTF-8 case is quieter: the terminal exists with an empty tag, so
a client correlating by tag can never match it and has no refusal to
react to.

Pull the read into `create2_tag`, which returns the tag or the detail
string to refuse with, and refuse on both. Extracted rather than
inlined so the two failures are testable — `supervise` and the create
arms need an `AppState` that nothing in the suite constructs, which is
the same reason `armed_deadline` and `slots_to_evict` are pure.

Three tests cover a well-formed tag (including one followed by later
fields), a length past the end, off by one and far, and a non-UTF-8
tag. Checked they are not vacuous by restoring the empty-tag fallback
and confirming both refusal tests fail.

Workspace clippy and `cargo fmt` clean, 290 blit-server tests passing.

Co-Authored-By: Claude <noreply@anthropic.com>
@indent

indent Bot commented Aug 6, 2026

Copy link
Copy Markdown
PR Summary

Fixes a gap in C2S_CREATE2(WANT_STATUS) handling so a malformed tag answers INVALID instead of silently succeeding, matching docs/protocol.md and the other CREATE2 fields. Previously an out-of-range tag_len or a non-UTF-8 tag fell back to an empty tag and let the create proceed; the out-of-range case left the read cursor past the end of the message, so a command-bearing create with no cwd/deadline dropped the command and spawned the default shell instead.

  • Extracts the tag read into a pure, testable create2_tag(data) -> Result<&str, &'static str> helper in crates/server/src/lib.rs.
  • The CREATE2 handler now refuses with STATUS_INVALID (naming the reason) on both an overrunning tag_len and a non-UTF-8 tag, so cursor never advances past the message end.
  • Adds three unit tests: a well-formed tag (including one followed by later fields and the empty-tag case), a tag_len past the end (off-by-one and far), and a non-UTF-8 tag.

Scope note for reviewers: the legacy C2S_CREATE / _AT / _N paths carry the same tag-fallback pattern, but they are untouched here, are the success-only variants, and are out of this PR's stated scope.

Issues

Review closed.

View session

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Coverage

Crate Lines Functions Regions
alacritty-driver 72.7% (806/1109) 75.0% (63/84) 76.2% (1310/1719)
browser 0.0% (0/822) 0.0% (0/68) 0.0% (0/1401)
cli 24.9% (2436/9801) 36.4% (300/825) 27.5% (4128/15020)
compositor 12.2% (1221/9999) 22.8% (111/487) 12.3% (1651/13411)
fonts 81.4% (721/886) 88.6% (70/79) 83.0% (1427/1719)
fssync 92.5% (4880/5274) 94.1% (445/473) 92.5% (8926/9647)
gateway 25.5% (375/1469) 29.9% (38/127) 19.2% (470/2448)
git 87.5% (4195/4793) 88.5% (332/375) 87.2% (6639/7614)
lsp 76.0% (2503/3295) 78.2% (248/317) 73.8% (3886/5266)
proxy 19.2% (172/898) 20.5% (26/127) 21.0% (293/1392)
remote 90.3% (9306/10302) 93.2% (681/731) 88.5% (15677/17723)
sd-notify 73.9% (68/92) 100.0% (6/6) 83.2% (109/131)
server 40.4% (7868/19456) 55.1% (780/1416) 43.0% (13315/30945)
ssh 32.2% (165/512) 48.2% (27/56) 31.4% (261/830)
upsidedown 31.4% (391/1247) 27.8% (55/198) 34.8% (797/2287)
webrtc-forwarder 2.7% (72/2624) 2.1% (4/187) 1.2% (50/4335)
webserver 62.1% (1133/1825) 65.9% (164/249) 64.5% (1912/2964)
Total 48.8% (36312/74404) 57.7% (3350/5805) 51.2% (60851/118852)

@pcarrier
pcarrier merged commit 1de3cbb into main Aug 6, 2026
11 checks passed
@pcarrier
pcarrier deleted the eng/create2-tag-validation branch August 6, 2026 03:09
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