Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

English | 中文版

附录 B:CVE 代码分析——漏洞 C++ 代码 vs 安全 Rust 缓解方案

本附录展示附录 A 中记录的 CVE 的实际(或重建的)漏洞 C/C++ 代码,配以 ascend-rs 风格的 Rust 代码,从结构上防止每类漏洞。

B.1 引用计数释放后 Use-After-Free(CVE-2023-51042,AMDGPU)

Linux AMDGPU 驱动在释放 fence 引用计数后仍解引用其指针。

漏洞 C 代码(来自 amdgpu_cs.c,修复前 2e54154):

r = dma_fence_wait_timeout(fence, true, timeout);
dma_fence_put(fence);          // 引用释放——fence 可能已被释放
if (r < 0)
    return r;
if (r == 0)
    break;
if (fence->error)              // USE-AFTER-FREE:fence 已被释放
    return fence->error;

ascend-rs 缓解方案——Rust 所有权确保值被消费而非悬垂:

#![allow(unused)]
fn main() {
fn wait_all_fences(fences: &[Arc<Fence>], timeout: Duration) -> Result<()> {
    for fence in fences {
        let status = fence.wait_timeout(timeout)?;
        // 在仍持有 Arc 引用时检查 error
        if let Some(err) = fence.error() {
            return Err(err);
        }
        // Arc 引用在循环迭代结束前一直有效
        // Rust 编译器拒绝在 drop 后使用 fence 的任何代码
    }
    Ok(())
}
}

Rust 如何防止此漏洞Arc<Fence> 是引用计数的。编译器确保你无法在 Arc 被释放后访问 fence.error()——借用检查器在编译期拒绝对已移动/释放值的任何引用。

B.2 未检查用户索引导致越界写入(CVE-2024-0090,NVIDIA)

NVIDIA GPU 驱动通过 ioctl 接受用户提供的索引,未进行边界检查。

漏洞 C 代码(根据 CVE 描述重建):

struct gpu_resource_table {
    uint32_t entries[MAX_GPU_RESOURCES];
    uint32_t count;
};

static int nvidia_ioctl_set_resource(struct gpu_resource_table *table,
                                     struct user_resource_request *req)
{
    // 错误:未检查用户提供的索引
    table->entries[req->index] = req->value;   // 越界写入
    return 0;
}

ascend-rs 缓解方案——Rust 切片在类型层面强制边界检查:

#![allow(unused)]
fn main() {
struct GpuResourceTable {
    entries: Vec<u32>,
}

impl GpuResourceTable {
    fn set_resource(&mut self, index: usize, value: u32) -> Result<()> {
        *self.entries.get_mut(index)
            .ok_or(Error::IndexOutOfBounds)? = value;
        Ok(())
    }
}
}

Rust 如何防止此漏洞Vec<u32> 跟踪自身长度。.get_mut() 对越界访问返回 None。在安全 Rust 中无法静默地写入缓冲区之外。

B.3 整数溢出导致堆缓冲区溢出(CVE-2024-53873,NVIDIA CUDA Toolkit)

CUDA cuobjdump 从伪造的 .cubin 文件读取 2 字节有符号值,符号扩展后用于 memcpy 大小。

漏洞 C 代码(来自 Talos 反汇编分析):

int16_t name_len_raw = *(int16_t*)(section_data);  // 0xFFFF = -1
int32_t name_len = (int32_t)name_len_raw;           // 符号扩展为 -1
int32_t alloc_size = name_len + 1;                   // -1 + 1 = 0
memcpy(dest_buf, src, (size_t)alloc_size);           // 堆缓冲区溢出

ascend-rs 缓解方案——Rust 的检查算术捕获溢出:

#![allow(unused)]
fn main() {
fn parse_debug_section(section: &[u8], dest: &mut [u8]) -> Result<()> {
    let name_len_raw = i16::from_le_bytes(
        section.get(0..2).ok_or(Error::TruncatedInput)?.try_into()?
    );
    let alloc_size: usize = (name_len_raw as i32)
        .checked_add(1)
        .and_then(|n| usize::try_from(n).ok())
        .ok_or(Error::IntegerOverflow)?;

    let src = section.get(offset..offset + alloc_size)
        .ok_or(Error::BufferOverflow)?;
    dest.get_mut(..alloc_size)
        .ok_or(Error::BufferOverflow)?
        .copy_from_slice(src);
    Ok(())
}
}

Rust 如何防止此漏洞checked_add() 在溢出时返回 Noneusize::try_from() 拒绝负值。切片 .get() 对越界范围返回 None

B.4 空容器越界读取(PyTorch Issue #37153)

PyTorch 的 CUDA 归约内核对标量张量的空 shape() 数组进行索引。

漏洞 C++ 代码(来自 Reduce.cuh):

// iter.shape() 对标量输入返回空 IntArrayRef
int64_t dim0;
if (reduction_on_fastest_striding_dimension) {
    dim0 = iter.shape()[0];  // 越界:shape() 为空
    // dim0 = 垃圾值(如 94599111233572)
}

ascend-rs 缓解方案——Rust 的 Option 类型使空值显式化:

#![allow(unused)]
fn main() {
fn configure_reduce_kernel(shape: &[usize]) -> Result<KernelConfig> {
    let dim0 = shape.first()
        .copied()
        .ok_or(Error::ScalarTensorNotSupported)?;

    let (dim0, dim1) = match shape {
        [d0, d1, ..] => (*d0, *d1),
        [d0] => (*d0, 1),
        [] => return Err(Error::EmptyShape),
    };
    Ok(KernelConfig { dim0, dim1 })
}
}

Rust 如何防止此漏洞shape.first() 返回 Option,强制调用者处理空值情况。match 对切片模式是穷举的——编译器要求 [](空)分支。

B.5 整数截断绕过边界检查(CVE-2019-16778,TensorFlow)

TensorFlow 的 UnsortedSegmentSum 内核将 int64 张量大小隐式截断为 int32

漏洞 C++ 代码(来自 segment_reduction_ops.h):

template <typename T, typename Index>  // Index = int32
struct UnsortedSegmentFunctor {
    void operator()(OpKernelContext* ctx,
                    const Index num_segments,  // 截断:int64 -> int32
                    const Index data_size,     // 截断:int64 -> int32
                    const T* data, /* ... */)
    {
        if (data_size == 0) return;  // 被绕过:截断值 != 0
        // data_size = 1(从 4294967297 截断)
    }
};

ascend-rs 缓解方案——Rust 类型系统拒绝隐式窄化:

#![allow(unused)]
fn main() {
fn unsorted_segment_sum(
    data: &DeviceBuffer<f32>,
    segment_ids: &DeviceBuffer<i32>,
    num_segments: usize,
) -> Result<DeviceBuffer<f32>> {
    let data_size: usize = data.len();

    let data_size_i32: i32 = i32::try_from(data_size)
        .map_err(|_| Error::TensorTooLarge {
            size: data_size,
            max: i32::MAX as usize,
        })?;
    // Rust 拒绝:let x: i32 = some_i64;  // 错误:类型不匹配
    Ok(output)
}
}

Rust 如何防止此漏洞:Rust 没有隐式整数窄化。let x: i32 = some_i64; 是编译错误。TryFrom/try_into() 在值不匹配时返回 Err

B.6 锁释放后原始指针 Use-After-Free(CVE-2023-4211,ARM Mali)

ARM Mali GPU 驱动从共享状态复制原始指针,释放锁,休眠,然后解引用已悬垂的指针。

漏洞 C 代码(来自 mali_kbase_mem_linux.c,Project Zero 确认):

static void kbasep_os_process_page_usage_drain(struct kbase_context *kctx)
{
    struct mm_struct *mm;
    spin_lock(&kctx->mm_update_lock);
    mm = rcu_dereference_protected(kctx->process_mm, /*...*/);
    rcu_assign_pointer(kctx->process_mm, NULL);
    spin_unlock(&kctx->mm_update_lock);  // 锁释放

    synchronize_rcu();  // 休眠——mm 可能被其他线程释放

    add_mm_counter(mm, MM_FILEPAGES, -pages);  // USE-AFTER-FREE
}

ascend-rs 缓解方案——Rust 的 Arc + Mutex 防止悬垂引用:

#![allow(unused)]
fn main() {
struct DeviceContext {
    process_mm: Mutex<Option<Arc<MmStruct>>>,
}

impl DeviceContext {
    fn drain_page_usage(&self) {
        let mm = {
            let mut guard = self.process_mm.lock().unwrap();
            guard.take()  // 设为 None,返回 Option<Arc<MmStruct>>
        };
        // 锁在此处释放(guard 被 drop)

        if let Some(mm) = mm {
            synchronize_rcu();
            // mm 仍然存活——Arc 保证了这一点
            mm.add_counter(MmCounter::FilePages, -pages);
        }
        // mm 在此处释放——Arc 引用计数递减
        // 仅在最后一个 Arc 引用被 drop 时才释放底层内存
    }
}
}

Rust 如何防止此漏洞Arc<MmStruct> 是引用计数智能指针。从 Option 中取出后我们拥有一个强引用。即使锁释放后其他线程运行,我们的 Arc 保持 MmStruct 存活。在安全 Rust 中无法从 Arc 获得悬垂原始指针。