Skip to content

Commit 72dcebc

Browse files
authored
[37.0.0] Fix externref/anyref ownership in C/C++ API (#11800)
* Fix externref/anyref ownership in C/C++ API This commit is a follow-up to #11514 which was discovered through failing tests in the wasmtime-py repository when updating to Wasmtime 37.0.0. This is a backport to the 37.0.x release branch which contains the minimal infrastructure necessary to get to parity with `main` in terms of avoiding leaks. Care is taken to avoid changing any public APIs here which means that some previously-provided parameters are no longer used, for example. * Run `clang-format`
1 parent 10c784c commit 72dcebc

File tree

21 files changed

+343
-57
lines changed

21 files changed

+343
-57
lines changed

RELEASES.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
## 37.0.2
2+
3+
Released 2025-10-07.
4+
5+
### Fixed
6+
7+
* Fix a memory leak in the C API when using `anyref` or `externref`.
8+
[CVE-2025-61670](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-vvp9-h8p2-xwfc).
9+
10+
--------------------------------------------------------------------------------
11+
112
## 37.0.1
213

314
Released 2025-09-23.
@@ -9,6 +20,8 @@ Released 2025-09-23.
920
sign-extend instead.
1021
[#11734](https://github.com/bytecodealliance/wasmtime/pull/11734)
1122

23+
--------------------------------------------------------------------------------
24+
1225
## 37.0.0
1326

1427
Released 2025-09-20.

crates/c-api/include/wasmtime/func.hh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ NATIVE_WASM_TYPE(double, F64, f64)
8686
template <> struct WasmType<std::optional<ExternRef>> {
8787
static const bool valid = true;
8888
static const ValKind kind = ValKind::ExternRef;
89+
static void store(Store::Context cx, wasmtime_val_raw_t *p,
90+
std::optional<ExternRef> &&ref) {
91+
if (ref) {
92+
p->externref = ref->take_raw(cx);
93+
} else {
94+
p->externref = 0;
95+
}
96+
}
8997
static void store(Store::Context cx, wasmtime_val_raw_t *p,
9098
const std::optional<ExternRef> &ref) {
9199
if (ref) {
@@ -129,6 +137,9 @@ template <typename T> struct WasmTypeList {
129137
static bool matches(ValType::ListRef types) {
130138
return WasmTypeList<std::tuple<T>>::matches(types);
131139
}
140+
static void store(Store::Context cx, wasmtime_val_raw_t *storage, T &&t) {
141+
WasmType<T>::store(cx, storage, t);
142+
}
132143
static void store(Store::Context cx, wasmtime_val_raw_t *storage,
133144
const T &t) {
134145
WasmType<T>::store(cx, storage, t);
@@ -169,6 +180,15 @@ template <typename... T> struct WasmTypeList<std::tuple<T...>> {
169180
size_t n = 0;
170181
return ((WasmType<T>::kind == types.begin()[n++].kind()) && ...);
171182
}
183+
static void store(Store::Context cx, wasmtime_val_raw_t *storage,
184+
std::tuple<T...> &&t) {
185+
size_t n = 0;
186+
std::apply(
187+
[&](auto &...val) {
188+
(WasmType<T>::store(cx, &storage[n++], val), ...); // NOLINT
189+
},
190+
t);
191+
}
172192
static void store(Store::Context cx, wasmtime_val_raw_t *storage,
173193
const std::tuple<T...> &t) {
174194
size_t n = 0;

crates/c-api/include/wasmtime/val.hh

Lines changed: 117 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,34 @@ public:
3838
/// Creates a new `ExternRef` directly from its C-API representation.
3939
explicit ExternRef(wasmtime_externref_t val) : val(val) {}
4040

41+
/// Copy constructor to clone `other`.
42+
ExternRef(const ExternRef &other) {
43+
wasmtime_externref_clone(NULL, &other.val, &val);
44+
}
45+
46+
/// Copy assignment to clone from `other`.
47+
ExternRef &operator=(const ExternRef &other) {
48+
wasmtime_externref_unroot(NULL, &val);
49+
wasmtime_externref_clone(NULL, &other.val, &val);
50+
return *this;
51+
}
52+
53+
/// Move constructor to move the contents of `other`.
54+
ExternRef(ExternRef &&other) {
55+
val = other.val;
56+
wasmtime_externref_set_null(&other.val);
57+
}
58+
59+
/// Move assignment to move the contents of `other`.
60+
ExternRef &operator=(ExternRef &&other) {
61+
wasmtime_externref_unroot(NULL, &val);
62+
val = other.val;
63+
wasmtime_externref_set_null(&other.val);
64+
return *this;
65+
}
66+
67+
~ExternRef() { wasmtime_externref_unroot(NULL, &val); }
68+
4169
/// Creates a new `externref` value from the provided argument.
4270
///
4371
/// Note that `val` should be safe to send across threads and should own any
@@ -54,9 +82,8 @@ public:
5482

5583
/// Creates a new `ExternRef` which is separately rooted from this one.
5684
ExternRef clone(Store::Context cx) {
57-
wasmtime_externref_t other;
58-
wasmtime_externref_clone(cx.ptr, &val, &other);
59-
return ExternRef(other);
85+
(void)cx;
86+
return *this;
6087
}
6188

6289
/// Returns the underlying host data associated with this `ExternRef`.
@@ -66,12 +93,24 @@ public:
6693

6794
/// Unroots this value from the context provided, enabling a future GC to
6895
/// collect the internal object if there are no more references.
69-
void unroot(Store::Context cx) { wasmtime_externref_unroot(cx.ptr, &val); }
96+
void unroot(Store::Context cx) {
97+
(void)cx;
98+
wasmtime_externref_unroot(NULL, &val);
99+
wasmtime_externref_set_null(&val);
100+
}
70101

71102
/// Returns the raw underlying C API value.
72103
///
73104
/// This class still retains ownership of the pointer.
74105
const wasmtime_externref_t *raw() const { return &val; }
106+
107+
/// Consumes ownership of the underlying `wasmtime_externref_t` and returns
108+
/// the result of `wasmtime_externref_to_raw`.
109+
uint32_t take_raw(Store::Context cx) {
110+
uint32_t ret = wasmtime_externref_to_raw(cx.raw_context(), &val);
111+
wasmtime_externref_set_null(&val);
112+
return ret;
113+
}
75114
};
76115

77116
/**
@@ -86,6 +125,32 @@ public:
86125
/// Creates a new `AnyRef` directly from its C-API representation.
87126
explicit AnyRef(wasmtime_anyref_t val) : val(val) {}
88127

128+
/// Copy constructor to clone `other`.
129+
AnyRef(const AnyRef &other) { wasmtime_anyref_clone(NULL, &other.val, &val); }
130+
131+
/// Copy assignment to clone from `other`.
132+
AnyRef &operator=(const AnyRef &other) {
133+
wasmtime_anyref_unroot(NULL, &val);
134+
wasmtime_anyref_clone(NULL, &other.val, &val);
135+
return *this;
136+
}
137+
138+
/// Move constructor to move the contents of `other`.
139+
AnyRef(AnyRef &&other) {
140+
val = other.val;
141+
wasmtime_anyref_set_null(&other.val);
142+
}
143+
144+
/// Move assignment to move the contents of `other`.
145+
AnyRef &operator=(AnyRef &&other) {
146+
wasmtime_anyref_unroot(NULL, &val);
147+
val = other.val;
148+
wasmtime_anyref_set_null(&other.val);
149+
return *this;
150+
}
151+
152+
~AnyRef() { wasmtime_anyref_unroot(NULL, &val); }
153+
89154
/// Creates a new `AnyRef` which is an `i31` with the given `value`,
90155
/// truncated if the upper bit is set.
91156
static AnyRef i31(Store::Context cx, uint32_t value) {
@@ -96,14 +161,17 @@ public:
96161

97162
/// Creates a new `AnyRef` which is separately rooted from this one.
98163
AnyRef clone(Store::Context cx) {
99-
wasmtime_anyref_t other;
100-
wasmtime_anyref_clone(cx.ptr, &val, &other);
101-
return AnyRef(other);
164+
(void)cx;
165+
return *this;
102166
}
103167

104168
/// Unroots this value from the context provided, enabling a future GC to
105169
/// collect the internal object if there are no more references.
106-
void unroot(Store::Context cx) { wasmtime_anyref_unroot(cx.ptr, &val); }
170+
void unroot(Store::Context cx) {
171+
(void)cx;
172+
wasmtime_anyref_unroot(NULL, &val);
173+
wasmtime_anyref_set_null(&val);
174+
}
107175

108176
/// Returns the raw underlying C API value.
109177
///
@@ -201,6 +269,7 @@ public:
201269
val.kind = WASMTIME_EXTERNREF;
202270
if (ptr) {
203271
val.of.externref = ptr->val;
272+
wasmtime_externref_set_null(&ptr->val);
204273
} else {
205274
wasmtime_externref_set_null(&val.of.externref);
206275
}
@@ -210,6 +279,7 @@ public:
210279
val.kind = WASMTIME_ANYREF;
211280
if (ptr) {
212281
val.of.anyref = ptr->val;
282+
wasmtime_anyref_set_null(&ptr->val);
213283
} else {
214284
wasmtime_anyref_set_null(&val.of.anyref);
215285
}
@@ -221,6 +291,35 @@ public:
221291
/// any`.
222292
Val(AnyRef ptr);
223293

294+
/// Copy constructor to clone `other`.
295+
Val(const Val &other) { wasmtime_val_clone(NULL, &other.val, &val); }
296+
297+
/// Copy assignment to clone from `other`.
298+
Val &operator=(const Val &other) {
299+
wasmtime_val_unroot(NULL, &val);
300+
wasmtime_val_clone(NULL, &other.val, &val);
301+
return *this;
302+
}
303+
304+
/// Move constructor to move the contents of `other`.
305+
Val(Val &&other) {
306+
val = other.val;
307+
other.val.kind = WASMTIME_I32;
308+
other.val.of.i32 = 0;
309+
}
310+
311+
/// Move assignment to move the contents of `other`.
312+
Val &operator=(Val &&other) {
313+
wasmtime_val_unroot(NULL, &val);
314+
val = other.val;
315+
other.val.kind = WASMTIME_I32;
316+
other.val.of.i32 = 0;
317+
return *this;
318+
}
319+
320+
/// Unroots the values in `val`, if any.
321+
~Val() { wasmtime_val_unroot(NULL, &val); }
322+
224323
/// Returns the kind of value that this value has.
225324
ValKind kind() const {
226325
switch (val.kind) {
@@ -295,14 +394,15 @@ public:
295394
/// Note that `externref` is a nullable reference, hence the `optional` return
296395
/// value.
297396
std::optional<ExternRef> externref(Store::Context cx) const {
397+
(void)cx;
298398
if (val.kind != WASMTIME_EXTERNREF) {
299399
std::abort();
300400
}
301401
if (wasmtime_externref_is_null(&val.of.externref)) {
302402
return std::nullopt;
303403
}
304404
wasmtime_externref_t other;
305-
wasmtime_externref_clone(cx.ptr, &val.of.externref, &other);
405+
wasmtime_externref_clone(NULL, &val.of.externref, &other);
306406
return ExternRef(other);
307407
}
308408

@@ -312,14 +412,15 @@ public:
312412
/// Note that `anyref` is a nullable reference, hence the `optional` return
313413
/// value.
314414
std::optional<AnyRef> anyref(Store::Context cx) const {
415+
(void)cx;
315416
if (val.kind != WASMTIME_ANYREF) {
316417
std::abort();
317418
}
318419
if (wasmtime_anyref_is_null(&val.of.anyref)) {
319420
return std::nullopt;
320421
}
321422
wasmtime_anyref_t other;
322-
wasmtime_anyref_clone(cx.ptr, &val.of.anyref, &other);
423+
wasmtime_anyref_clone(NULL, &val.of.anyref, &other);
323424
return AnyRef(other);
324425
}
325426

@@ -331,7 +432,12 @@ public:
331432
std::optional<Func> funcref() const;
332433

333434
/// Unroots any GC references this `Val` points to within the `cx` provided.
334-
void unroot(Store::Context cx) { wasmtime_val_unroot(cx.ptr, &val); }
435+
void unroot(Store::Context cx) {
436+
(void)cx;
437+
wasmtime_val_unroot(NULL, &val);
438+
val.kind = WASMTIME_I32;
439+
val.of.i32 = 0;
440+
}
335441
};
336442

337443
} // namespace wasmtime

crates/c-api/src/ref.rs

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{WasmtimeStoreContextMut, abort};
2-
use std::{mem::MaybeUninit, num::NonZeroU64, os::raw::c_void, ptr};
2+
use std::mem::{ManuallyDrop, MaybeUninit};
3+
use std::{num::NonZeroU64, os::raw::c_void, ptr};
34
use wasmtime::{AnyRef, ExternRef, I31, OwnedRooted, Ref, RootScope, Val};
45

56
/// `*mut wasm_ref_t` is a reference type (`externref` or `funcref`), as seen by
@@ -207,14 +208,26 @@ macro_rules! ref_wrapper {
207208
))
208209
}
209210

210-
pub unsafe fn from_wasmtime(self) -> Option<OwnedRooted<$wasmtime>> {
211+
pub unsafe fn into_wasmtime(self) -> Option<OwnedRooted<$wasmtime>> {
212+
ManuallyDrop::new(self).to_owned()
213+
}
214+
215+
unsafe fn to_owned(&self) -> Option<OwnedRooted<$wasmtime>> {
211216
let store_id = NonZeroU64::new(self.store_id)?;
212217
Some(OwnedRooted::from_owned_raw_parts_for_c_api(
213218
store_id, self.a, self.b, self.c,
214219
))
215220
}
216221
}
217222

223+
impl Drop for $c {
224+
fn drop(&mut self) {
225+
unsafe {
226+
let _ = self.to_owned();
227+
}
228+
}
229+
}
230+
218231
impl From<Option<OwnedRooted<$wasmtime>>> for $c {
219232
fn from(rooted: Option<OwnedRooted<$wasmtime>>) -> $c {
220233
let mut ret = $c {
@@ -248,7 +261,7 @@ ref_wrapper!(ExternRef => wasmtime_externref_t);
248261

249262
#[unsafe(no_mangle)]
250263
pub unsafe extern "C" fn wasmtime_anyref_clone(
251-
_cx: WasmtimeStoreContextMut<'_>,
264+
_cx: *mut u8,
252265
anyref: Option<&wasmtime_anyref_t>,
253266
out: &mut MaybeUninit<wasmtime_anyref_t>,
254267
) {
@@ -258,11 +271,13 @@ pub unsafe extern "C" fn wasmtime_anyref_clone(
258271

259272
#[unsafe(no_mangle)]
260273
pub unsafe extern "C" fn wasmtime_anyref_unroot(
261-
_cx: WasmtimeStoreContextMut<'_>,
262-
val: Option<&mut MaybeUninit<wasmtime_anyref_t>>,
274+
_cx: *mut u8,
275+
val: Option<&mut ManuallyDrop<wasmtime_anyref_t>>,
263276
) {
264-
if let Some(val) = val.and_then(|v| v.assume_init_read().from_wasmtime()) {
265-
drop(val);
277+
if let Some(val) = val {
278+
unsafe {
279+
ManuallyDrop::drop(val);
280+
}
266281
}
267282
}
268283

@@ -371,7 +386,7 @@ pub unsafe extern "C" fn wasmtime_externref_data(
371386

372387
#[unsafe(no_mangle)]
373388
pub unsafe extern "C" fn wasmtime_externref_clone(
374-
_cx: WasmtimeStoreContextMut<'_>,
389+
_cx: *mut u8,
375390
externref: Option<&wasmtime_externref_t>,
376391
out: &mut MaybeUninit<wasmtime_externref_t>,
377392
) {
@@ -381,11 +396,13 @@ pub unsafe extern "C" fn wasmtime_externref_clone(
381396

382397
#[unsafe(no_mangle)]
383398
pub unsafe extern "C" fn wasmtime_externref_unroot(
384-
_cx: WasmtimeStoreContextMut<'_>,
385-
val: Option<&mut MaybeUninit<wasmtime_externref_t>>,
399+
_cx: *mut u8,
400+
val: Option<&mut ManuallyDrop<wasmtime_externref_t>>,
386401
) {
387-
if let Some(val) = val.and_then(|v| v.assume_init_read().from_wasmtime()) {
388-
drop(val);
402+
if let Some(val) = val {
403+
unsafe {
404+
ManuallyDrop::drop(val);
405+
}
389406
}
390407
}
391408

0 commit comments

Comments
 (0)