rust-libteec 0.6.4

Rust implementation of TEE Client API for secure communication with Trusted Applications.
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2025-2026 KylinSoft Co., Ltd. <https://www.kylinos.cn/>
// See LICENSES for license details.

//! CA 进程地址空间区间校验(defense-in-depth)。
//!
//! GP TEE Client API 的契约要求 CA 保证 TMPREF 缓冲区与注册内存的
//! `buffer`/`size` 真实有效;但 CA 传入错误 size(大于实际分配)时,
//! 库会按声明 size 越界读写 CA 自身进程内存。此模块在每次读写前解析
//! `/proc/self/maps`,要求目标区间完整落在单个具备相应权限的 VMA 内,
//! 将越界访问转化为 `BadParameters` 错误而非内存破坏。

use std::fs;
use std::sync::{LazyLock, RwLock};

#[derive(Clone, Copy)]
enum Perm {
    Readable,
    Writable,
}

/// 校验 `[start, start+len)` 完整位于单个可读 VMA 内。
pub(super) fn range_readable(start: *const u8, len: usize) -> bool {
    range_in_perm_vma(start as usize, len, Perm::Readable)
}

/// 校验 `[start, start+len)` 完整位于单个可写 VMA 内。
pub(super) fn range_writable(start: *const u8, len: usize) -> bool {
    range_in_perm_vma(start as usize, len, Perm::Writable)
}

/// `/proc/self/maps` 最近一次解析结果缓存,避免每次校验都重读该伪文件。
///
/// 缓存仅在「区间未命中」时重读刷新:合法缓冲区即便在缓存构建之后才
/// mmap,未命中也会回退到实时读取,不会误拒。残余局限是 CA 在校验间隙
/// munmap 掉仍在缓存中的区域时,快速路径可能短暂放行(随后访问未映射内存
/// 即崩溃)——这与本模块「maps 不可读时放行」的纵深防御定位一致:CA 拥有
/// 自身进程,本校验只防库侧越界,不构成对抗恶意 CA 的硬边界。
static MAPS_CACHE: LazyLock<RwLock<Option<String>>> = LazyLock::new(|| RwLock::new(None));

/// 遍历 `/proc/self/maps`,检查区间是否完整位于单个带指定权限的 VMA 内。
///
/// `len == 0` 视为有效;区间跨越两个 VMA(即使权限都满足)视为无效——
/// 正常的 CA 缓冲区不会跨越 VMA 边界。maps 读取失败时放行并记录告警:
/// 该校验是纵深防御,不应在 `/proc` 不可用的环境中中断主流程。
fn range_in_perm_vma(start: usize, len: usize, need: Perm) -> bool {
    if len == 0 {
        return true;
    }
    let Some(end) = start.checked_add(len) else {
        return false;
    };

    // 快速路径:命中缓存(区间确定落在某 VMA 内)直接返回,省去重复读取。
    if let Some(hit) = MAPS_CACHE.read().ok().and_then(|guard| {
        guard
            .as_deref()
            .and_then(|maps| check_range_in_maps(maps, start, end, need))
    }) {
        return hit;
    }

    // 慢路径:读取并缓存最新 maps 后再判定。
    let maps = match fs::read_to_string("/proc/self/maps") {
        Ok(maps) => maps,
        Err(e) => {
            log::warn!("vmem: 读取 /proc/self/maps 失败({e}),跳过区间校验");
            return true;
        }
    };
    let result = check_range_in_maps(&maps, start, end, need).unwrap_or(false);
    if let Ok(mut guard) = MAPS_CACHE.write() {
        *guard = Some(maps);
    }
    result
}

/// 在已解析的 maps 文本中查找完整包含 `[start, end)` 的 VMA。
///
/// 返回 `Some(true)`(区间完整落于单个满足权限的 VMA)、`Some(false)`
/// (落于单个 VMA 但权限不满足)或 `None`(无 VMA 完整包含该区间,
/// 含区间跨 VMA 的情况)。
fn check_range_in_maps(maps: &str, start: usize, end: usize, need: Perm) -> Option<bool> {
    for line in maps.lines() {
        // 行格式:`start-end perms offset dev inode path`(path 可缺失)。
        let Some((addr, rest)) = line.split_once(' ') else {
            continue;
        };
        let Some((vma_start, vma_end)) = addr.split_once('-') else {
            continue;
        };
        let (Ok(vma_start), Ok(vma_end)) = (
            usize::from_str_radix(vma_start, 16),
            usize::from_str_radix(vma_end, 16),
        ) else {
            continue;
        };
        if vma_start <= start && end <= vma_end {
            let perms = rest.split(' ').next().unwrap_or("");
            return Some(vma_perm_ok(perms, need));
        }
    }
    None
}

/// 解析 VMA 权限字符串(如 `rw-p`)是否满足所需权限。
fn vma_perm_ok(perms: &str, need: Perm) -> bool {
    let b = perms.as_bytes();
    match need {
        Perm::Readable => b.first() == Some(&b'r'),
        Perm::Writable => b.get(1) == Some(&b'w'),
    }
}

#[cfg(test)]
mod vmem_tests {
    use super::*;

    #[test]
    fn test_stack_range_readable_writable() {
        let buf = [0u8; 8];
        assert!(range_readable(buf.as_ptr(), buf.len()));
        assert!(range_writable(buf.as_ptr(), buf.len()));
    }

    #[test]
    fn test_heap_range_readable_writable() {
        // 100KB 缓冲区必然来自堆分配(mmap 的 anonymous VMA)
        let buf = vec![0u8; 100 * 1024];
        assert!(range_readable(buf.as_ptr(), buf.len()));
        assert!(range_writable(buf.as_ptr(), buf.len()));
    }

    #[test]
    fn test_code_range_readable_not_writable() {
        // 代码段映射为 r-x:可读但不可写
        let p = test_code_range_readable_not_writable as *const u8;
        assert!(range_readable(p, 1));
        assert!(!range_writable(p, 1));
    }

    #[test]
    fn test_zero_len_valid() {
        assert!(range_readable(std::ptr::null(), 0));
        assert!(range_writable(std::ptr::null(), 0));
    }

    #[test]
    fn test_len_overflow_rejected() {
        let buf = [0u8; 8];
        assert!(!range_readable(buf.as_ptr(), usize::MAX));
        assert!(!range_writable(buf.as_ptr(), usize::MAX));
    }

    #[test]
    fn test_cross_vma_rejected() {
        // 512MB 区间必然跨越多个 VMA,应被拒绝
        let buf = [0u8; 8];
        let len = 512 * 1024 * 1024;
        assert!(!range_readable(buf.as_ptr(), len));
    }
}