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

Linux下运行时检测C/C++指针指向只读内存的适配方案探讨

Linux下只读内存指针检测的问题与适配

传统检测方式的局限性

早期Linux环境中,C/C++底层代码通过以下方式判断指针ptr是否指向只读内存:

extern char etext, edata;
...
(ptr > &etext) && (ptr < &edata);

这类代码目前仍有实际应用,但传统的连续内存布局text -> data -> bss -> heap -> ... -> stack -> kernel已无法保证,且现代内存布局的权威资料稀缺,难以判断传统代码的适用性及适配方案。

另有静态内存检测方案:

(void*)(x) <= (void*)&end || (void*)(x) <= (void*)&edata

该方案假设堆始终位于BSS和只读数据之后,由此引出两个技术疑问:

技术疑问

  • 该堆位置假设在现代Linux系统中是否安全?
  • 若假设成立,结合text段居前特性,如何调整逻辑以准确检测指针是否指向排除BSS的只读内存?

尝试性检测代码

本人编写的检测代码在Ubuntu 22.04传统布局环境下测试有效,但不确定非传统布局场景的适用性:

#include <iostream>
extern char etext, edata;


// 检测指针是否在栈中
void *stack_bottom;
bool __attribute__((noinline)) inStack(void *x) {
    void *stack_top = &stack_top;
    return x <= stack_bottom && x >= stack_top;
}

// 检测指针是否在排除BSS的只读内存中
bool roMem(void* c) {
    if(&etext < &edata && &end > &edata)
    {
        return (c > &etext) && (c < &edata) && (!inStack((void*)c));
    } else if(&etext > &edata && &end > &edata)
    {
        return (void*)(c) <= (void*)&edata && !inStack((void*)c);
    } else if(&end < &edata)
    {
        return (void*)(c) > (void*)&end && (void*)(c) <= (void*)&edata && !inStack((void*)c);
    }
}

class Test {
    public:
    const char* d;
    Test(const char* c) { d = c; }
};

static const char* str5 = "short";

int main() {
    const char* str1 = "short";
    char* str2 = const_cast<char*>("short");
    const char str3[6] = {'s', 'h', 'o', 'r', 't', 0};
    const char* str4 = "longgggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg";
    std::cout << roMem((void*)str1) << std::endl; // 输出1
    std::cout << roMem((void*)(const char*)str2) << std::endl; // 输出1
    std::cout << roMem((void*)str3) << std::endl; // 输出0
    std::cout << roMem((void*)str4) << std::endl; // 输出1
    std::cout << roMem((void*)str5) << std::endl; // 输出1
    std::cout << roMem((void*)"short") << std::endl; // 输出1
    std::cout << roMem((void*)(const char*)2) << std::endl; // 输出0
    Test x((const char*)3);
    std::cout << roMem((void*)x.d) << std::endl; // 输出0
}

问题解答

1. 堆位置假设的安全性

该假设在现代Linux系统中完全不安全,核心原因如下:

  • ASLR(地址空间布局随机化)默认开启,堆、栈、共享库的加载地址会随机偏移,传统连续布局被彻底打破,堆可能出现在任意地址区间。
  • 即使关闭ASLR,部分内核配置、架构或嵌入式场景下,堆可能与其他内存区域交错;进程还可通过mmap创建多个匿名内存区域(类堆区域),位置无规律。
  • 容器、虚拟化环境的内存布局可能进一步偏离传统模式。

2. 排除BSS的只读内存检测逻辑调整

若仅针对关闭ASLR的传统连续布局场景(堆在BSS之后),可基于标准符号调整逻辑,核心是区分只读段(text、rodata)与可写段(data、bss):

  1. 明确各段的标准边界(基于GCC默认符号):
    • .text(代码段,只读):从程序入口地址&_start到&etext
    • .rodata(只读数据段,如字符串常量、const静态变量):从&etext到.data段起始地址(无公开默认符号,需间接判断或自定义链接脚本导出)
    • .data(可写初始化全局变量):从.data起始到&edata
    • .bss(可写未初始化全局/静态变量):从&edata到&end
  2. 调整后的检测逻辑:
    • 先排除栈中的指针,避免栈地址与其他段重叠导致误判
    • 直接检测指针是否在.text区间
    • 若需检测.rodata,最可靠的方式是通过自定义链接脚本导出其边界符号,示例如下:
      // 自定义链接脚本中添加:
      // PROVIDE(rodata_start = .);
      // .rodata : { *(.rodata) *(.rodata.*) }
      // PROVIDE(rodata_end = .);
      
      // 代码中引用符号
      extern char _start, etext, rodata_start, rodata_end;
      
      bool roMem(void* c) {
          if (inStack(c)) return false;
          // 检测text段
          if (c >= &_start && c < &etext) return true;
          // 检测rodata段
          if (c >= &rodata_start && c < &rodata_end) return true;
          return false;
      }
      

注:上述逻辑仅适用于传统连续布局场景,现代系统中更可靠的方式是通过查询内存页权限(如解析/proc/self/maps或使用内存管理系统调用)实现检测,而非依赖段地址范围假设。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 21:25:55