Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

rust: in-process transport frees the connection callback state immediately when `copilot_runtime_connection_open` returns 0 (possible use-after-free)

未关闭 适合新手
#2,793 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
76/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
rust
领域
api, security

调研方向

从 rust/src/ffi.rs 中的 FfiHost::start_blocking 开始,将打开失败分支与 FfiShared::close 和 release_callback_state 进行比较。验证打开失败路径不再过早回收 CallbackState,然后使用所描述的 shim 配合 ASan 或类似工具,确认延迟的 callback 无法访问已释放的状态。

由索引模型根据 Issue 内容生成。

描述

Summary

With the in-process transport (in-process / bundled-in-process features), FfiHost::start_blocking boxes a CallbackState and passes the raw pointer as user_data to copilot_runtime_connection_open. If the call returns 0, the SDK reclaims the pointer right away with Box::from_raw, drops it, and then calls host_shutdown.

Elsewhere, the SDK treats user_data as reclaimable only once the runtime has signalled quiescence. FfiShared::close / release_callback_state free the state only after copilot_runtime_connection_close returns true, and retry on a background thread until it does (the fix from #2610 / #2622 in v1.0.14). The failed-open path was not covered by that fix.

The C ABI as documented in this repo does not justify the immediate free. The prototype comment in go/internal/ffihost/ffihost.go and ADR-007 (java/docs/adr/adr-007-native-bundling-strategy.md) say only that connection_open "registers the on_outbound callback" and "Returns a connection handle (0 = failure)". Neither promises that a failed open did not retain user_data or will not invoke the callback with it. A failed open also returns no connection id, so the host has nothing to close and no quiescence signal to wait for.

If the runtime has already set up outbound delivery before the open fails, a callback can arrive after the SDK freed the state. on_outbound would then dereference a freed CallbackState and send on a dropped tx.

The Go SDK is not exposed to this: it passes an opaque token as user_data and removes it from its lookup map on failure, so a late callback is a harmless miss. The Rust SDK passes a raw heap pointer, so it is exposed.

Affected versions
  • SDK rust v1.0.14 and v1.0.15. The failed-open arm in rust/src/ffi.rs is byte-identical in both.
  • Observed against CLI runtime libraries 1.0.84-5 through 1.0.89.
Code location

rust/src/ffi.rs, impl FfiHost { fn start_blocking }, at rust/v1.0.15:

let state_ptr = Box::into_raw(Box::new(CallbackState { tx, closing: AtomicBool::new(false) }));
let connection_id = unsafe { (self.connection_open)(server_id, on_outbound, state_ptr as *mut c_void, /* … */) };
if connection_id == 0 {
    drop(unsafe { Box::from_raw(state_ptr) });
    unsafe { (self.host_shutdown)(server_id) };
    return Err(Error::with_message(ErrorKind::InvalidConfig, "copilot_runtime_connection_open failed"));
}

Compare FfiShared::close / release_callback_state in the same file. They free only after connection_close returns true.

Minimal reproduction

This is a timing-dependent memory-safety issue, so the reliable repro is a shim library plus a sanitizer:

  1. Build a host with features = ["in-process"] against a shim library. The shim's copilot_runtime_connection_open spawns a thread that calls on_outbound(user_data, bytes, len) after a short delay, then returns 0. The other exports forward to a real runtime library.
  2. Start the client and observe Err("copilot_runtime_connection_open failed").
  3. Under ASan or Miri-style tooling, the delayed callback reads the freed CallbackState and sends on a dropped tx.
Expected vs actual
  • Expected: after a failed open, the SDK does not free user_data unless the C ABI guarantees it was not retained.
  • Actual: it is freed synchronously, while the ABI as documented leaves open whether the runtime still holds it and may invoke the callback with it.
Proposed fix

Minimal fix, which we carry as a local patch: on the connection_id == 0 arm, do not reclaim state_ptr. Deliberately leak the CallbackState, which is one small struct plus an unbounded-channel sender per failed open. In practice only host boot retries produce failed opens. Keep host_shutdown and the error return unchanged. Keep release_callback_state as the single place that calls Box::from_raw.

Alternatives:

  • Pass an opaque token as user_data and look it up in a registry, as the Go SDK does. A late callback then finds no entry.
  • Document in the shared C ABI that a 0 return from connection_open never retains or invokes user_data, with the runtime guaranteeing it. The current free would then be sound as written.
Patch diff summary

rust/src/ffi.rs: 1 hunk. It removes 1 line (drop(unsafe { Box::from_raw(state_ptr) });) and adds a comment explaining the intentional leak. No API change. Afterwards Box::from_raw appears exactly once in the file, inside release_callback_state.

主要语言
TypeScript
星标
10.5k
派生
1.5k
平均合并
1 天 7 小时
30 天内合并 PR
64

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

github/copilot-sdk 的其他 Issue

查看 github/copilot-sdk 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。