Skip to content

Allocator::grow does not verify all safety conditions of GlobalAlloc::realloc #95746

Open
@djkoloski

Description

@djkoloski

Allocator::grow does not verify that the new allocation size, when rounded up to a multiple of align, does not overflow isize::MAX. This can cause it to call realloc with invalid arguments:

#![deny(unsafe_op_in_unsafe_fn, clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks)]
#![feature(allocator_api)]

use std::alloc::{Allocator, Global, GlobalAlloc, Layout, System};

struct MyGlobalAlloc;

unsafe impl GlobalAlloc for MyGlobalAlloc {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        // SAFETY: The safety requirements for `alloc` match the safety docs for `System.alloc`.
        unsafe { System.alloc(layout) }
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        // SAFETY: The safety requirements for `dealloc` match the safety docs for `System.dealloc`.
        unsafe {
            System.dealloc(ptr, layout);
        }
    }

    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
        let new_layout = Layout::from_size_align(new_size, layout.align()).unwrap();
        // Uh oh! The safety guarantees for `realloc` have been violated!
        assert!(new_layout.pad_to_align().size() <= isize::MAX as usize);

        // SAFETY: The safety requirements for `realloc` match the safety docs for `System.realloc`.
        unsafe { System.realloc(ptr, layout, new_size) }
    }
}

#[global_allocator]
static GLOBAL: MyGlobalAlloc = MyGlobalAlloc;

fn main() {
    let initial_layout = Layout::from_size_align(2, 2).unwrap();
    let final_layout = Layout::from_size_align(isize::MAX as usize, 2).unwrap();
    let ptr = Global.allocate(initial_layout).unwrap();

    // SAFETY:
    // - `ptr` is allocated from `Global` with layout `initial_layout`.
    // - `final_layout.size()` is `isize::MAX` which is greater than `initial_layout.size()`, which
    //   is 2.
    let ptr = unsafe { Global.grow(ptr.cast(), initial_layout, final_layout).unwrap() };
    // SAFETY: `ptr` is allocated from `Global` with `final_layout`.
    unsafe {
        Global.deallocate(ptr.cast(), final_layout);
    }
}

This sample panics at the assertion in <MyGlobalAlloc as GlobalAlloc>::realloc. It's not clear whether:

  • The safety requirements of Allocator::grow need to include the missing clause from realloc.
  • realloc should be relaxed to allow for allocations larger than isize::MAX.
  • The implementation of Allocator::grow should check for this situation and return AllocError.

Meta

rustc --version --verbose:

rustc 1.61.0-nightly (0677edc86 2022-03-31)
binary: rustc
commit-hash: 0677edc86e342f333d4828b0ee1ef395a4e70fe5
commit-date: 2022-03-31
host: x86_64-pc-windows-msvc
release: 1.61.0-nightly
LLVM version: 14.0.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-allocatorsArea: Custom and system allocatorsC-bugCategory: This is a bug.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions