Description
The BoxedInline arm of glib::wrapper! implements
ToGlibContainerFromSlice<'a, *mut ffi::T> with a to_glib_full_from_slice
that allocates room for exactly one element and then copies every element of
the caller's slice into it. For a slice of n >= 2 elements it writes n - 1
elements past the end of the allocation. The method is public and safe, so the
overflow is reachable from safe code in any crate that has a BoxedInline type
in scope.
glib/src/boxed_inline.rs:328 in 0.22.9:
fn to_glib_full_from_slice(t: &[Self]) -> *mut $ffi_name {
let v_ptr = unsafe {
let v_ptr = $crate::ffi::g_malloc(std::mem::size_of::<$ffi_name>()) as *mut $ffi_name;
for (i, s) in t.iter().enumerate() {
let copy_into = |$copy_into_arg_dest: *mut $ffi_name, $copy_into_arg_src: *const $ffi_name| $copy_into_expr;
copy_into(v_ptr.add(i), &s.inner as *const $ffi_name);
}
v_ptr
};
v_ptr
}
The allocation is size_of::<$ffi_name>(), one element, while the loop runs to
t.len(). The two pointer-array implementations in the same file get it right,
allocating size_of::<*const $ffi_name>() * (t.len() + 1) at lines 264 and 279.
Only the inline-array variant is wrong. to_glib_container_from_slice (line
318) delegates to it and carries the same defect.
Every type declared through glib::wrapper! with BoxedInline is affected:
glib::Date, glib::GStringBuilder, gio::FileAttributeInfo, and the pango
types Analysis, Color, Matrix, GlyphGeometry, GlyphInfo and
Rectangle. Other bindings inherit it too, including gdk::Rectangle and
gtk::TextIter in gtk3-rs. The defect lives in the macro, so one fix covers
all of them.
Proof of concept
src/main.rs, against glib = "=0.22.9":
use glib::translate::ToGlibContainerFromSlice;
use glib::{Date, DateMonth};
fn main() {
let d1 = Date::from_dmy(1, DateMonth::January, 2024).unwrap();
let d2 = Date::from_dmy(2, DateMonth::January, 2024).unwrap();
let dates = [d1, d2];
let ptr = <Date as ToGlibContainerFromSlice<'_, *mut glib::ffi::GDate>>
::to_glib_full_from_slice(&dates);
unsafe { glib::ffi::g_free(ptr as *mut _) };
}
There is no unsafe on the path into the overflow. GDate is 8 bytes, so the
second element lands entirely outside the allocation. Under AddressSanitizer:
==3170155==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b50b81e0058
WRITE of size 8 at 0x7b50b81e0058 thread T0
#2 <glib::date::Date as ToGlibContainerFromSlice<*mut GDate>>::to_glib_full_from_slice::{closure#0}
glib-0.22.9/src/boxed_inline.rs:73:48
0x7b50b81e0058 is located 0 bytes after 8-byte region [0x7b50b81e0050,0x7b50b81e0058)
Impact
A heap buffer overflow with attacker-influenced content, reachable without
writing a single unsafe block. The overflow length scales with the slice the
caller passes, so an application that converts a caller-supplied list of
BoxedInline values (gdk::Rectangle damage regions, pango::GlyphInfo runs,
Date values) writes that data over whatever the allocator placed after a
one-element allocation. Depending on the heap layout, the outcome ranges from a
crash to corruption of adjacent allocations or allocator metadata.
Solution
Fixed in commit f54ceb3, merged through gtk-rs/gtk-rs-core#2048 on 2026-09-19. The allocation is now sized by the slice:
let v_ptr = $crate::ffi::g_malloc(std::mem::size_of::<$ffi_name>() * std::cmp::max(t.len(), 1)) as *mut $ffi_name;
The max(t.len(), 1) keeps the empty-slice case allocating one element, because
g_malloc(0) returns NULL and callers expect a non-null pointer back.
The fix is on main only. The newest published glib is 0.22.9 from 2026-08-30,
which is still affected. Until a release lands, avoid these two conversions on
BoxedInline types for slices of two or more elements, or pin glib to a git
revision at or after the fix.
Timeline
- 2026-09-06: Reported publicly as gtk-rs/gtk-rs-core#2040, found with my undefined-behavior static analysis tool while scanning the 5000 most downloaded crates.
- 2026-09-07: Maintainer confirmed the bug.
- 2026-09-19: Fix merged into
mainand the issue closed. No published release contains the fix yet.