Skip to content

id phase-9: a joiner's resize (up to 500x200) can permanently poison a world via the guest layout() call exceeding presentation_bytes #811

Closed

Description

@dezren39

Finding

A session with only join scope can send resize up to 500×200, but the screen DTO is capped at 120×50 and the guest layout(width, height) result must fit presentation_bytes (default 8192). A reflowing app that fills a 120×50 grid exceeds 8192 bytes, so one participant resizing their window makes the world's program unhealthy for everyone until an admin reinstalls. This is the same trap-poisoning class as #547, but the trigger is an unprivileged joiner's window resize, which is meant to be per-connection and unjournaled.

Evidence

  • pkgs/id/src/world_presentation.rs:15-17: MAX_SCREEN_WIDTH = 120, MAX_SCREEN_HEIGHT = 50.
  • pkgs/id/src/world_session.rs:2309-2321: ClientFrame::Resize accepts width 1..=500 and height 1..=200 for any session holding join scope, and calls send_view with that viewport.
  • pkgs/id/src/world.rs:78: presentation_bytes: 8192 default.
  • pkgs/id/src/world.rs:3109-3125: ViewAt sets self.program_healthy = false when the layout output exceeds presentation_bytes, and on any layout error — a world-wide state, not a per-session one.
  • A 120×50 grid is 6000 cells plus JSON framing, which easily exceeds 8192 bytes; resize is documented as session-local (viewport_is_session_local_and_never_advances_the_world_sequence).

Why it matters

An unprivileged participant can take a shared world offline with a single resize, and it stays unhealthy for every participant until someone reinstalls the module. This is a denial-of-service the current bounds do not prevent.

Suggested fix (do not fix inline here — see note)

  • Clamp width/height to MAX_SCREEN_WIDTH/MAX_SCREEN_HEIGHT before calling view_at/send_view.
  • In ViewAt, treat an oversized layout result as a per-session error (fall back to view() plus cropping) instead of setting program_healthy = false.

Proving test

A mock WorldProgram whose layout returns >8192 bytes when width > 100. Session A resizes to 500×200; assert session B still receives a view frame and that input is still accepted.

Not fixed inline

Left as an issue (not fixed in PR #804) because the fix lands in world.rs / world_session.rs, which Session B's in-flight module split (PR #808, F11.10) is moving; an inline edit here would collide with that work.

Related

#547 (F11.17: a trapping view must not poison the world) — this is a narrower, join-scope trigger.

Acceptance criteria

  • A join-scoped session's resize can no longer make a world permanently unhealthy: an oversized layout() result for one connection's viewport degrades to a per-connection fallback (crop/clamp to that connection's presentation budget) instead of setting world-wide program_healthy = false.
  • Resize acceptance bounds and the screen DTO caps (120×50) / presentation_bytes budget are reconciled so the guest is never asked for a layout that cannot fit.
  • A regression test reproduces the poison path (joiner resize to a large viewport on a reflowing app) and shows the world stays healthy for other participants.
  • Existing world/presentation tests stay green.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/highImportant and time-sensitive
    effort/mMedium: expected under 1 day of work
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 26, 2026
  2. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    Intake review

    Related: Open PR #927 attempts the #811 fix and is still open; #942 tracks a distinct facet found in that PR (its oversized-layout fallback never re-checks presentation_bytes). Same trap-poisoning class as #547. No merged PR resolves this (verified 2026-10-26).

    Notes: Priority high: an unprivileged joiner can permanently poison a shared world for everyone until an admin reinstalls — an availability bug with a trivial trigger (resizing a window). Acceptance criteria appended by this intake pass; original body preserved verbatim above them.

    Risks: The fix interacts with PR #927's in-flight approach; whoever picks this up should coordinate with that PR rather than open a competing one.

    Suggested approach: Clamp the effective layout viewport to the presentation budget per connection and treat an over-budget layout as a per-connection error, never a world-health transition; land alongside or inside PR #927.

  3. added a commit that references this issue on Oct 26, 2026
    762a918
  4. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · closeout — fixed on main, closing (queue task 1257, #1076 slice 5)

    Re-checked against current main f9c9ffdb (2026-10-27). The poison chain this issue filed — joiner resize → guest layout() output exceeds presentation_bytes → program_healthy = false world-wide — no longer exists at either layer:

    • Actor layer (pkgs/id/src/world/actor.rs, View ~L1085 and ViewAt ~L1122): an over-limit presentation now increments view_failures and returns an error for that view request only; program_healthy is not touched. A trapping view/layout likewise degrades to an error screen (in-code comment: 'does not poison the world').
    • Sandbox layer (pkgs/id/src/sandbox.rs view() / layout_at()): a trapped view or layout does not poison the guest instance (F11.17) — commits 9f4633a3 ('degrade a trapping view to an error screen') and 07a906d0 ('do not poison the guest instance on a trapped view or layout'), both on main.
    • Regression test: a_trapping_view_degrades_to_an_error_and_recovers_on_the_next_event (pkgs/id/src/world/tests.rs:2416) asserts the trap is counted in view_failures and the world recovers on the next event.

    The resize acceptance range itself is unchanged (world_session/mod.rs: width 1..=500, height 1..=200 for join-scope sessions) and presentation_bytes still defaults to 8192 (world/mod.rs:91) — what changed is the consequence: per-viewer error instead of a world-wide outage.

    One residual gap, noted for #1076 slice 6: no dedicated test asserts the oversize (non-trapping) view/layout case leaves the world healthy — the existing test covers the trapping case and the oversize branch is structurally identical in the actor. Tracked as a slice-6 coverage item on #1076 rather than keeping this bug open.

    Closing as fixed.

  5. added a commit that references this issue on Oct 30, 2026
    4b56bc6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/mMedium: expected under 1 day of workkind/bugSomething isn't working (prefixed form of the classic bug label)meta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/highImportant and time-sensitive

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions