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 main and the issue closed. No published release contains the fix yet.

References