Skip to content

Safe Rust APIs can construct invalid GFP flags causing C-side out-of-bounds read #1253

Description

@GeorgeAndrou

Hello,

While studying the Rust kernel crate, we found a soundness issue where an invalid GFP flag combination can be constructed and passed to the C allocator using only public safe kernel crate functions, resulting in an out-of-bounds read in mm/page_alloc.c.

Confirmed on current upstream master: 28924df2a08f440c73991b83028032c901de2ae4

The attached KASAN logs were collected on v7.1-rc4 (5200f5f49). We also independently reproduced both paths below on the current upstream commit above.

The Problem

At the tested upstream commit, Flags implements the safe ! operator as follows:

impl core::ops::Not for Flags {
type Output = Self;
fn not(self) -> Self::Output {
Self(!self.0)
}
}

This allows safe Rust code to construct GFP flag combinations that are not valid for the C allocator. For example, complementing GFP_ATOMIC sets both __GFP_MOVABLE and __GFP_RECLAIMABLE.

Page::alloc_page() is a public safe Rust function and passes the raw flags directly to alloc_pages():

linux/rust/kernel/page.rs

Lines 179 to 196 in 28924df

/// Allocate memory for a page and zero its contents.
///
/// ```
/// use kernel::page::Page;
///
/// let page = Page::alloc_page(GFP_KERNEL | __GFP_ZERO)?;
/// # Ok::<(), kernel::alloc::AllocError>(())
/// ```
#[inline]
pub fn alloc_page(flags: Flags) -> Result<Self, AllocError> {
// SAFETY: Depending on the value of `gfp_flags`, this call may sleep. Other than that, it
// is always safe to call this method.
let page = unsafe { bindings::alloc_pages(flags.as_raw(), 0) };
let page = NonNull::new(page).ok_or(AllocError)?;
// INVARIANT: We just successfully allocated a page, so we now have ownership of the newly
// allocated page. We transfer that ownership to the new `Page` object.
Ok(Self { page })
}

When both __GFP_MOVABLE and __GFP_RECLAIMABLE are set, gfp_migratetype() returns MIGRATE_HIGHATOMIC (MIGRATE_PCPTYPES). This value is then used to index past the end of the fallbacks table in the page allocator.

KASAN reports:

BUG: KASAN: global-out-of-bounds
Read of size 4
...
The buggy address belongs to the variable:
 fallbacks+0x18/0x40

Reproduction

The following reproducer contains two independent triggering paths:

// SPDX-License-Identifier: GPL-2.0

//! Minimal Rust kernel API reproducer.

use kernel::{
    alloc::flags::{GFP_ATOMIC, GFP_KERNEL_ACCOUNT},
    bitmap::BitmapVec,
    page::Page,
    prelude::*,
};

module! {
    type: RustRepro,
    name: "rust_repro",
    authors: ["User0"],
    description: "Minimal Rust kernel API reproducer",
    license: "GPL",
}

struct RustRepro;

fn reproduce_page_alloc() {
    pr_info!("rust_repro: reproducing Page::alloc_page path\n");

    let flags = core::ops::Not::not(GFP_ATOMIC);
    let _page = Page::alloc_page(flags);

    pr_info!("rust_repro: Page::alloc_page returned\n");
}

fn reproduce_bitmap_vec() {
    pr_info!("rust_repro: reproducing BitmapVec::new path\n");

    let flags = core::ops::Not::not(GFP_KERNEL_ACCOUNT);
    let _bitmap = BitmapVec::new(0xc0ffee, flags);

    pr_info!("rust_repro: BitmapVec::new returned\n");
}

impl kernel::Module for RustRepro {
    fn init(_module: &'static ThisModule) -> Result<Self> {
        pr_info!("rust_repro: starting reproducer\n");

        reproduce_page_alloc();
        // reproduce_bitmap_vec();

        Ok(Self)
    }
}

The Page::alloc_page path is the simpler trigger:

let flags = core::ops::Not::not(GFP_ATOMIC);
let _page = Page::alloc_page(flags);

The second path is:

let flags = core::ops::Not::not(GFP_KERNEL_ACCOUNT);
let _bitmap = BitmapVec::new(0xc0ffee, flags);

For the BitmapVec path, kmalloc_fix_flags() reports and sanitizes the unexpected GFP mask, but both __GFP_MOVABLE and __GFP_RECLAIMABLE remain set, and the same out-of-bounds access is reached.

KASAN logs:

We have not reported this to the mailing list yet because we are unsure which subsystem should own the fix and what the preferred solution would be. We would appreciate the maintainers' guidance on the preferred approach and reporting route.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    unsoundThe possibility of UB in safe code.• libRelated to the `rust/` library.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions