Skip to content

Windows ConPTY backend data race in ptyHandles can crash in remove_pty_baton #921

@stephentoub

Description

@stephentoub

The Windows ConPTY backend appears to have a data race around the global ptyHandles vector in src/win/conpty.cc.

ptyHandles is a process-global std::vector<std::unique_ptr<pty_baton>>. It is accessed from multiple threads without synchronization:

  • the JS/main thread inserts into it in PtyStartProcess
  • the JS/main thread reads from it via get_pty_baton in connect, resize, clear, and kill
  • each PTY process gets a native watcher thread from SetupExitCallback
  • each watcher thread calls remove_pty_baton(baton->id) when its child process exits

The problematic code is:

static std::vector<std::unique_ptr<pty_baton>> ptyHandles;

static bool remove_pty_baton(int id) {
  auto it = std::remove_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {
    return ptyHandle->id == id;
  });
  if (it != ptyHandles.end()) {
    ptyHandles.erase(it);
    return true;
  }
  return false;
}

Because multiple SetupExitCallback watcher threads can call remove_pty_baton concurrently, two std::remove_if calls can race while moving/erasing unique_ptr elements in the same vector. One thread can observe an element that has been moved from by another thread and then dereference a null unique_ptr in the predicate (ptyHandle->id). There are also races between get_pty_baton and removal/erase.

A crash dump from a release build showed two native threads simultaneously inside remove_pty_baton, both from SetupExitCallback watcher lambdas. The faulting thread crashed at the predicate dereference with rax == 0:

conpty!remove_pty_baton::<lambda>::operator()+0x3
cmp dword ptr [rax], ebx
Exception code: 0xc0000005
Attempt to read from address 0000000000000000

This is consistent with ptyHandle being a moved-from/null std::unique_ptr during concurrent std::remove_if.

Metadata

Metadata

Assignees

Labels

bugIssue identified by VS Code Team member as probable bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions