Skip to content

Commit 5671d6e

Browse files
authored
Merge pull request #2062 from CosmWasm/aw/fix-dealloc-ub
Correctly construct `Region` to uphold deallocation safety invariant
2 parents f1fa4bb + e0f963d commit 5671d6e

File tree

3 files changed

+81
-6
lines changed

3 files changed

+81
-6
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ and this project adheres to
2626
[#2051]: https://github.com/CosmWasm/cosmwasm/pull/2051
2727
[#2059]: https://github.com/CosmWasm/cosmwasm/pull/2059
2828

29+
### Fixed
30+
31+
- cosmwasm-std: Correctly deallocate vectors that were turned into a `Region`
32+
via `release_buffer` ([#2062])
33+
34+
[#2062]: https://github.com/CosmWasm/cosmwasm/pull/2062
35+
2936
## [2.0.0] - 2024-03-12
3037

3138
### Fixed

packages/std/src/imports.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ impl Api for ExternalApi {
348348
}
349349

350350
fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult<Addr> {
351-
let send = build_region(&canonical);
351+
let send = build_region(canonical.as_slice());
352352
let send_ptr = &*send as *const Region as u32;
353353
let human = alloc(HUMAN_ADDRESS_BUFFER_LENGTH);
354354

packages/std/src/memory.rs

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub fn alloc(size: usize) -> *mut Region {
3333
/// Similar to alloc, but instead of creating a new vector it consumes an existing one and returns
3434
/// a pointer to the Region (preventing the memory from being freed until explicitly called later).
3535
///
36-
/// The resulting Region has capacity = length, i.e. the buffer's capacity is ignored.
36+
/// The resulting Region spans the entire region allocated by the vector, preserving the length and capacity components.
3737
pub fn release_buffer(buffer: Vec<u8>) -> *mut Region {
3838
let region = build_region(&buffer);
3939
mem::forget(buffer);
@@ -69,15 +69,83 @@ pub unsafe fn consume_region(ptr: *mut Region) -> Vec<u8> {
6969
)
7070
}
7171

72+
/// Element that can be used to construct a new `Box<Region>`
73+
///
74+
/// # Safety
75+
///
76+
/// The following invariant must be upheld:
77+
///
78+
/// - full allocated capacity == value returned by capacity
79+
///
80+
/// This is important to uphold the safety invariant of the `dealloc` method, which requires us to pass the same Layout
81+
/// into it as was used to allocate a memory region.
82+
/// And since `size` is one of the parameters, it is important to pass in the exact same capacity.
83+
///
84+
/// See: <https://doc.rust-lang.org/stable/alloc/alloc/trait.GlobalAlloc.html#safety-2>
85+
pub unsafe trait RegionSource {
86+
fn ptr(&self) -> *const u8;
87+
fn len(&self) -> usize;
88+
fn capacity(&self) -> usize;
89+
}
90+
91+
unsafe impl RegionSource for &[u8] {
92+
fn ptr(&self) -> *const u8 {
93+
self.as_ptr()
94+
}
95+
96+
fn len(&self) -> usize {
97+
(*self).len()
98+
}
99+
100+
fn capacity(&self) -> usize {
101+
self.len()
102+
}
103+
}
104+
105+
unsafe impl RegionSource for Vec<u8> {
106+
fn ptr(&self) -> *const u8 {
107+
self.as_ptr()
108+
}
109+
110+
fn len(&self) -> usize {
111+
self.len()
112+
}
113+
114+
fn capacity(&self) -> usize {
115+
self.capacity()
116+
}
117+
}
118+
119+
unsafe impl<T: ?Sized> RegionSource for &T
120+
where
121+
T: RegionSource,
122+
{
123+
fn ptr(&self) -> *const u8 {
124+
(**self).ptr()
125+
}
126+
127+
fn len(&self) -> usize {
128+
(**self).len()
129+
}
130+
131+
fn capacity(&self) -> usize {
132+
(**self).capacity()
133+
}
134+
}
135+
72136
/// Returns a box of a Region, which can be sent over a call to extern
73137
/// note that this DOES NOT take ownership of the data, and we MUST NOT consume_region
74138
/// the resulting data.
75139
/// The Box must be dropped (with scope), but not the data
76-
pub fn build_region(data: &[u8]) -> Box<Region> {
77-
let data_ptr = data.as_ptr() as usize;
140+
pub fn build_region<S>(data: S) -> Box<Region>
141+
where
142+
S: RegionSource,
143+
{
144+
// Well, this technically violates pointer provenance rules.
145+
// But there isn't a stable API for it, so that's the best we can do, I guess.
78146
build_region_from_components(
79-
u32::try_from(data_ptr).expect("pointer doesn't fit in u32"),
80-
u32::try_from(data.len()).expect("length doesn't fit in u32"),
147+
u32::try_from(data.ptr() as usize).expect("pointer doesn't fit in u32"),
148+
u32::try_from(data.capacity()).expect("capacity doesn't fit in u32"),
81149
u32::try_from(data.len()).expect("length doesn't fit in u32"),
82150
)
83151
}

0 commit comments

Comments
 (0)