napi_unwrap does not reject objects that were never wrapped (V8 port faults, QuickJS port confuses types)
维护者通常 1 天内回复
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 68/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- cpp, javascript
调研方向
先阅读 Core/Node-API/Source/js_native_api_v8.cc 中的 inline napi_status Unwrap,以及 Core/Node-API/Source/js_native_api_quickjs.cc 中的 napi_unwrap。验证 V8 路径是否在解引用之前检查其内部字段,以及 QuickJS 是否拒绝没有 wrapper 的对象,而不是沿着原型链遍历;完成的标准是,两个 port 对从未被 wrapper 的对象都返回 napi_invalid_arg,且不存在不安全访问或类型混淆。
由索引模型根据 Issue 内容生成。
描述
Summary
napi_unwrap must fail when it is handed an object that was never wrapped. The V8 and QuickJS ports both violate that, so Napi::ObjectWrap<T>::Unwrap cannot safely be called on an object whose type has not already been established. On V8 it is an outright memory-safety hole.
This affects every Unwrap call site in a consumer, not one polyfill. I hit it in BabylonNative's Canvas polyfill (BabylonJS/BabylonNative#1844), where ctx.fill(Object.create(Path2D.prototype)) was an access violation.
V8 port — dereferences internal field 0 unchecked
Core/Node-API/Source/js_native_api_v8.cc, inline napi_status Unwrap(...) (~line 345). A [BABYLON-NATIVE-ADDITION] marked "Increase perf by using internal field instead of private property" replaced the private-property lookup, including its validity check:
// upstream
auto val = obj->GetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper)).ToLocalChecked();
RETURN_STATUS_IF_FALSE(env, val->IsExternal(), napi_invalid_arg);
with a bare obj->GetAlignedPointerFromInternalField(0), whose result is then dereferenced (reference->Data()).
For any object that is not a wrapped instance, internal field 0 is not a Reference*. The read returns garbage and the dereference faults. Reproduction:
const impostor = Object.create(Path2D.prototype);
ctx.fill(impostor); // 0xC0000005
I confirmed this in a local V8 build: deterministic access violation, and it disappears when the unwrap is replaced with a checked lookup.
QuickJS port — walks the prototype chain
Core/Node-API/Source/js_native_api_quickjs.cc, napi_unwrap (~line 2495). After the fast path on js_wrap_class_id there is a "Fallback: search the prototype chain for a legacy wrapper object".
That returns some other object's native pointer — a type confusion rather than a crash. An object created with Object.create(RealType.prototype) unwraps to whatever instance is reachable on the chain. It does at least return napi_invalid_arg when nothing is found.
JSI port
No C API, and ObjectWrap<T>::Unwrap returns nullptr for a non-wrapped object (napi-inl.h:2268) rather than throwing. Safe, but inconsistent with the other two.
Why this is hard to work around downstream
While fixing the Canvas case I found no portable way to do a type check:
napi_type_tag_object/napi_check_object_type_tagexist only injs_native_api_v8.cc.Napi::Object::DefineProperty/PropertyDescriptorare absent from the JSI port, so a non-enumerable brand cannot be installed.GetInstanceData/SetInstanceData/AddCleanupHookare absent from the JSI port, so there is nowhere to keep per-EnvC++ state.Napi::ObjectWrap<T>::Value()throws on the QuickJS port, which rules out an identity check against a candidate instance.
I ended up branding each instance with a Napi::External<T> and validating the pointer against a registry of live addresses. That works, but every consumer having to invent this is a strong argument for fixing the ports.
Suggested fix
Restore the validity check in the V8 port (keep the internal-field fast path, but verify the field actually holds the wrapper before dereferencing), and drop the prototype-chain fallback in the QuickJS port so a non-wrapped object returns napi_invalid_arg.
Related: #225 (the napi_throw family reports failure on success), found while chasing the same PR.
- 主要语言
- C++
- 星标
- 22
- 派生
- 23
- 平均合并
- 2 小时 29 分钟
- 30 天内合并 PR
- 3
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
BabylonJS/JsRuntimeHost 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 84/100
BabylonJS/JsRuntimeHost#234 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 82/100
BabylonJS/JsRuntimeHost#173 ·
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 42/100
BabylonJS/JsRuntimeHost#241 · 3 条评论 ·
维护者通常 1 天内回复
-
难度 5/5 一周以上 新手友好度 35/100
BabylonJS/JsRuntimeHost#228 ·
维护者通常 1 天内回复
-
bug
难度 4/5 3-5 天 新手友好度 35/100
BabylonJS/JsRuntimeHost#219 ·
维护者通常 1 天内回复
查看 BabylonJS/JsRuntimeHost 的全部 Issue
相似的 Issue
-
Component: Ruby Type: bug
难度 2/5 1-3 小时 新手友好度 74/100
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 88/100
维护者通常 3 天内回复
-
难度 1/5 1 小时以内 新手友好度 92/100
kokkos/kokkos-kernels#3328 ·
维护者通常 1 天内回复
-
bug needs triage tcp
难度 2/5 1-3 小时 新手友好度 78/100
project-chip/connectedhomeip#74644 ·
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 82/100
维护者通常 1 天内回复