You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust FFI指针越界未触发panic/段错误的原因及解决方法

Rust FFI内存越界未触发错误的原因及无size参数的安全检查方案

问题重现

在Rust FFI场景中,执行以下代码时,指针偏移100后访问越界内存,既未触发panic也未产生段错误,仅输出0:

Rust代码

#[repr(C)]
pub struct MyStruct {
    pub item: u32
    // 其他字段省略
}

#[no_mangle]
pub unsafe extern fn do_something(mut data: *mut MyStruct) {
    println!("{:p}", data);
    data = data.offset(100);
    println!("{:p}", data);
    println!("{}", (*data).item);
    if data.is_null() {
        println!("data is null");
    }
}

C++调用代码

#include "my_bindings.h"
int main() {
    MyStruct *data = new MyStruct[2];
    do_something(data);
    return 0;
}

程序输出

0x55f0ba739eb0
0x55f0ba73a108
0

为什么未触发panic或段错误?

  1. Rust unsafe代码无自动边界检查:do_something函数标记为unsafe,意味着主动放弃了Rust的内存安全保障,编译器不会自动插入数组边界检查逻辑,因此访问越界不会触发panic。
  2. 段错误的触发条件限制:段错误(SIGSEGV)是操作系统的内存保护机制,只有当访问的内存页完全不属于当前进程地址空间时才会触发。偏移100后的地址仍处于进程的堆地址范围内(堆分配通常会预留额外空间,或该地址属于之前分配后未回收的内存区域),操作系统不会拦截,此时读取到的是该内存地址的现有值(刚好为0)。

不传入size参数的前提下如何避免越界?

裸指针本身不携带任何内存块长度信息,不存在通用、跨平台且安全的方法仅通过裸指针获取内存大小,以下是几种可行的替代方案:

1. 用带长度的结构体封装指针

定义一个同时包含指针和长度的C兼容结构体,让C++端传递这个结构体而非裸指针,本质是将长度与指针绑定:

#[repr(C)]
pub struct StructSlice {
    data: *mut MyStruct,
    len: usize,
}

#[no_mangle]
pub unsafe extern fn do_something(slice: StructSlice) {
    if slice.len <= 100 {
        // 处理越界情况,比如直接返回或打印错误
        return;
    }
    let data = slice.data.offset(100);
    println!("{}", (*data).item);
}

C++端需要定义对应结构体:

struct StructSlice {
    MyStruct* data;
    size_t len;
};

int main() {
    MyStruct* data = new MyStruct[2];
    StructSlice slice = {data, 2};
    do_something(slice);
    delete[] data;
    return 0;
}

2. 使用哨兵值标记数组末尾

在数组最后一个元素放置一个业务场景中不可能出现的特殊值,遍历或偏移时检查该值以判断是否越界。例如,约定item字段为0xFFFFFFFF时表示数组结束:

#[no_mangle]
pub unsafe extern fn do_something(mut data: *mut MyStruct) {
    // 遍历到哨兵值停止
    for i in 0.. {
        if (*data).item == 0xFFFFFFFF {
            break;
        }
        // 处理逻辑
        data = data.offset(1);
    }
}

C++端初始化数组时需添加哨兵:

int main() {
    MyStruct* data = new MyStruct[3]; // 多分配一个位置放哨兵
    data[0].item = 1;
    data[1].item = 2;
    data[2].item = 0xFFFFFFFF;
    do_something(data);
    delete[] data;
    return 0;
}

注意:该方案依赖业务逻辑,若数组中可能出现哨兵值则不可用。

3. 依赖特定内存分配器的API(平台相关)

如果C++和Rust使用同一款内存分配器(如系统默认的malloc/free),可使用分配器的私有API获取内存块大小。例如GNU系统下的malloc_usable_size:

use libc::{c_void, malloc_usable_size};

#[no_mangle]
pub unsafe extern fn do_something(data: *mut MyStruct) {
    let block_size = malloc_usable_size(data as *mut c_void);
    let max_elements = block_size / std::mem::size_of::<MyStruct>();
    if max_elements <= 100 {
        // 越界处理
        return;
    }
    // 安全访问
}

注意:该方案仅适用于特定平台和分配器,且无法区分new[]分配的数组和单独分配的结构体,兼容性差,不推荐作为通用方案。


内容的提问来源于stack exchange,提问作者t348575

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 11:25:25