Skip to content

fix: Other local users can no longer connect to the Unity Editor's uloop channel on Windows - #1322

Merged
hatayama merged 4 commits into
v3-betafrom
fix/windows-named-pipe-acl
Jun 12, 2026
Merged

fix: Other local users can no longer connect to the Unity Editor's uloop channel on Windows#1322
hatayama merged 4 commits into
v3-betafrom
fix/windows-named-pipe-acl

Conversation

@hatayama

@hatayama hatayama commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • On Windows, the uloop IPC named pipe is now restricted to the user who owns the Unity Editor. Other local accounts on a shared machine or RDP host can no longer open it.
  • The first attempt at this restriction (managed PipeSecurity) turned out to be silently broken on Unity's Mono runtime; this PR replaces it with an implementation that verifiably applies the ACL on a real Windows editor.
  • IPC server failures are now visible in the Unity console instead of disappearing silently, so a broken server can no longer hang every uloop command without leaving a trace.

User Impact

  • Before: any local user on the same machine could open the pipe (its name is derivable from the project path) and reach execute-dynamic-code, i.e. run arbitrary C# with the Editor owner's privileges.
  • Also before this PR (with only the first commit): on a real Windows editor the server failed to create the pipe at all — every uloop command hung and the Editor burned a full CPU core — with no log output whatsoever, because all server errors were raised through an event nobody subscribes to.
  • After: uloop works normally for the Editor's owner, the pipe's DACL contains exactly one ACE granting access to that user only, and if the server ever fails to accept connections the Unity console says so and the server restarts itself with bounded backoff.

Changes

  • Create the pipe natively (CreateNamedPipeW) with an explicit owner-only security descriptor and wrap the handle in a managed stream, because Unity's Mono ignores the PipeSecurity ctor argument and WindowsIdentity.GetCurrent().User throws NotImplementedException.
  • Resolve the current user's SID from the process token (OpenProcessToken / GetTokenInformation).
  • Use a non-overlapped handle: Mono hangs WaitForConnection on a wrapped overlapped handle. Since closing a non-overlapped handle does not cancel a pending synchronous wait, Stop() now wakes a pending accept with a loopback connection before disposing, and the accept loop discards that wake connection.
  • Remove the dead OnError event (zero subscribers) and log each former call site directly. Genuine failures (accept loop, client session crashes, terminal recovery failure) go to the Unity console — VibeLogger alone compiles out of end-user installs ([Conditional(ULOOP_DEBUG)]). Teardown noise stays at debug-only warning level.
  • Accept-loop failures exit the loop and hand off to the existing bounded-backoff recovery instead of retrying in a tight loop.
  • Unit tests for the SDDL shape, pipe creation, and multi-instance support.

Verification

  • uloop compile (0 errors / 0 warnings), uloop get-logs, and repeated sequential commands against a live Windows 11 / Unity 2022.3.62f3 editor, including recovery across domain reloads.
  • Live pipe DACL inspected from an external process via GetSecurityInfo: O:<owner> D:P(A;;FA;;;<owner SID>) — no Everyone/Anonymous/Administrators/SYSTEM ACEs.
  • uloop run-tests (EditMode): server-related suites 13 passed / 0 failed (new factory tests + listener contract + server shutdown + heartbeat tests).
  • Note: a direct connect attempt from a second local account was not possible on the verification machine (no admin rights / no second-account credentials); rejection is established by the inspected DACL. The note's PowerShell snippet can be used from another account for a direct check if desired.

hatayama added 2 commits June 12, 2026 22:13
The named pipe server was created without an explicit ACL, so it inherited
a default security descriptor that lets other local users open the pipe.
Because any connected client can invoke execute-dynamic-code (arbitrary C#
inside the Editor process), this let any local user on a shared or RDP host
reach the deterministically named pipe and gain code execution as the Editor
owner.

Pass an explicit PipeSecurity that grants FullControl only to the current
user's SID, denying every other local principal at the transport boundary.
…s PipeSecurity

The previous commit (47ec948) restricted the Windows IPC pipe with the
NamedPipeServerStream(..., PipeSecurity) overload. Verification on a real
Windows editor (2022.3.62f3, NET_4_6 Mono) showed that approach is broken
twice over:

- WindowsIdentity.GetCurrent().User throws NotImplementedException, so
  every AcceptClient call failed and the swallowed exception made the
  accept loop spin at full CPU with no pipe ever created; uloop was
  completely unreachable.
- Even with a valid SID, Mono silently ignores the PipeSecurity argument:
  the created pipe kept the default DACL (Everyone/Anonymous read), so the
  ACL was never applied.

Create the pipe through CreateNamedPipeW with an explicit owner-only
security descriptor (D:P(A;;FA;;;<current user SID>)) and wrap the native
handle in a managed stream. The SID comes from the process token via
GetTokenInformation because the managed identity APIs are unimplemented.
The handle is deliberately non-overlapped: Mono hangs WaitForConnection on
a wrapped overlapped handle, and the accept loop already waits on a
dedicated worker thread.

Closing a non-overlapped handle does not cancel a pending synchronous
ConnectNamedPipe wait, so Stop now wakes a pending accept with a loopback
connection before disposing, and AcceptClient discards a wait that ended
after stop/cancellation instead of treating the wake client as a session.

Verified on Windows 11: uloop compile/get-logs succeed across domain
reloads, the live pipe DACL contains exactly one ACE for the owning user,
and sequential CLI commands keep working. PipeStream.GetAccessControl was
also observed to crash the editor natively on this runtime, so the DACL
check must stay external (e.g. GetSecurityInfo from another process).
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@hatayama, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 18 minutes and 16 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8a6dbf15-c267-4426-bda2-fa5238508186

📥 Commits

Reviewing files that changed from the base of the PR and between 395e544 and 645ad4f.

📒 Files selected for processing (2)
  • Packages/src/Editor/Infrastructure/BridgeTransportListener.cs
  • Packages/src/Editor/Infrastructure/WindowsOwnerOnlyNamedPipeFactory.cs
📝 Walkthrough

Walkthrough

This PR adds Windows named pipe security restrictions by introducing an owner-only SDDL-protected pipe factory, integrates it into the bridge transport listener with graceful accept-loop shutdown, replaces the public OnError event with structured logging across the server, and adds recovery failure console visibility.

Changes

Named Pipe Security and Error Reporting

Layer / File(s) Summary
Windows Owner-Only Named Pipe Factory
Packages/src/Editor/Infrastructure/WindowsOwnerOnlyNamedPipeFactory.cs
New internal factory that computes owner-restricted SDDL strings, resolves current user SID via Win32 token APIs, marshals security descriptors, and creates restricted server pipes; includes P/Invoke bindings and unmanaged memory cleanup.
WindowsOwnerOnlyNamedPipeFactory Unit Tests
Assets/Tests/Editor/WindowsOwnerOnlyNamedPipeFactoryTests.cs
Windows-only test fixture validates SDDL generation grants full control to exactly one SID, CreateServer returns usable streams with valid handles, and multiple concurrent instances can coexist on the same pipe name.
BridgeTransportListener Integration with Secure Pipes and Graceful Shutdown
Packages/src/Editor/Infrastructure/BridgeTransportListener.cs
Transport listener resolves owner-only SDDL during start, creates server pipes via the new factory, suppresses spurious wake-up connections, and implements controlled shutdown by connecting a loopback client to release pending synchronous accept before disposal.
Error Reporting Refactoring: OnError Removal and Logging Integration
Packages/src/Editor/Infrastructure/UnityCliLoopBridgeServer.cs, Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerController.cs
Removes public OnError event and routes startup failures, client disconnect, accept-loop faults, task continuations, disposal errors, and recovery failures through VibeLogger and Debug.LogError for consistent editor console visibility.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main security fix: restricting Windows named pipe access to the current user only.
Description check ✅ Passed The description clearly explains the problem, solution, and impact of the changes, directly relating to the changeset modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/windows-named-pipe-acl

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 5 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Packages/src/Editor/Infrastructure/WindowsOwnerOnlyNamedPipeFactory.cs Outdated
Comment thread Packages/src/Editor/Infrastructure/BridgeTransportListener.cs Outdated
…bscribed OnError event

UnityCliLoopBridgeServer raised every failure through an OnError event that
has no subscriber anywhere in the codebase, so accept-loop errors, client
session crashes, and disposal failures all vanished silently. This is why
the Mono PipeSecurity regression (fixed in the previous commit) could spin
the accept loop at full CPU for half an hour without a single log line.

- Remove the dead OnError event and log each former call site directly.
- Accept-loop failures now exit the loop and hand off to the existing
  bounded-backoff recovery path instead of retrying in a tight loop, and
  log to the Unity console: VibeLogger alone is not enough because all of
  its methods are [Conditional(ULOOP_DEBUG)] and compile out of end-user
  installs.
- Terminal recovery failure also logs to the console; previously it ended
  in an unobserved task exception plus a debug-only log, leaving an
  unreachable server completely silent for end users.
- Teardown-noise sites (client disconnect/dispose errors, thread aborts
  outside domain reload) log through VibeLogger at warning level.

Verified with uloop compile (0 errors / 0 warnings) and the server-related
EditMode suites (13 passed / 0 failed) against a live Windows editor.
… path

Address review feedback on the Windows named pipe ACL change:

- Add PIPE_REJECT_REMOTE_CLIENTS to the native CreateNamedPipeW open mode.
  The managed NamedPipeServerStream sets this by default but the native
  path does not, so without it the execute-code channel would accept a
  remote principal over the network. Defense in depth on top of the
  owner-only DACL; local loopback (including the Stop() wake connection)
  is unaffected.
- WakePendingAccept previously swallowed every exception, hiding failures
  in the only path that unblocks a pending synchronous accept. Log the
  failure at debug level: the common case (no accept pending) is harmless,
  but if a stuck accept ever causes a shutdown hang this is the only
  breadcrumb pointing at it.

Verified with uloop compile (0/0) and the factory/listener/shutdown
EditMode suites (7 passed / 0 failed) on a live Windows editor.
@hatayama
hatayama merged commit 8362dd7 into v3-beta Jun 12, 2026
8 checks passed
@hatayama
hatayama deleted the fix/windows-named-pipe-acl branch June 12, 2026 15:34
@github-actions github-actions Bot mentioned this pull request Jun 12, 2026
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