Linux C++ realpath函数crash的解决方法

现象

调用realpath函数进程崩溃,可使用如下代码复现:

//test.cpp
#include "stdio.h"
#include "stdlib.h"
#include <limits.h>

int main()
{
    char *p=new char[300];
    realpath("1.txt",p);
    printf("%s\n",p);
    delete[] p;
    return 0;
}

编译命令:g++ test.cpp -O1 -g

如果将O1改成O0则不复现,O2、O3都复现。说明这个BUG与gcc优化有关。

开发环境

ubuntu 22.04 x86_64虚拟机

gcc 11.4

笔者另有一个ubuntu 20.04 x86_64 + gcc 9.4的虚拟机也能复现。但在另外2个gcc 7.3的服务器上不能复现。猜测是gcc版本差异,9.4以上会出现。

问题原因

realpath的第二个参数需要至少PATH_MAX字节的空间才能保证内存安全,即使路径没有那么长。

您可以阅读参考资料获取realpath和PATH_MAX的详细用法。

解决方案

申请PATH_MAX字节的内存即可,该值在多数linux系统中定义为4096。

//test.cpp
#include "stdio.h"
#include "stdlib.h"
#include <limits.h>

int main()
{
    char *p=new char[PATH_MAX];
    realpath("1.txt",p);
    printf("%s\n",p);
    delete[] p;
    return 0;
}

为什么O0不复现

realpath内部是否优化与上述优化参数无关,因为它由glibc实现。通常都是有优化的,除非你手动编译glibc并关闭优化。

crash堆栈在realpath内部,为了搞清同一个环境下优化参数为什么会影响realpath内部,我们对main函数进行反汇编。

下面是O0和O1的反汇编结果:

Dump of assembler code for function main():
   0x00005555555551a9 <+0>:     endbr64
   0x00005555555551ad <+4>:     push   %rbp
   0x00005555555551ae <+5>:     mov    %rsp,%rbp
   0x00005555555551b1 <+8>:     sub    $0x10,%rsp
   0x00005555555551b5 <+12>:    mov    $0x12c,%edi
   0x00005555555551ba <+17>:    callq  0x555555555080 <_Znam@plt>
   0x00005555555551bf <+22>:    mov    %rax,-0x8(%rbp)
   0x00005555555551c3 <+26>:    mov    -0x8(%rbp),%rax
   0x00005555555551c7 <+30>:    mov    %rax,%rsi
   0x00005555555551ca <+33>:    lea    0xe33(%rip),%rax        # 0x555555556004
   0x00005555555551d1 <+40>:    mov    %rax,%rdi
=> 0x00005555555551d4 <+43>:    callq  0x555555555090 <realpath@plt>
   0x00005555555551d9 <+48>:    mov    -0x8(%rbp),%rax
   0x00005555555551dd <+52>:    mov    %rax,%rdi
   0x00005555555551e0 <+55>:    callq  0x5555555550b0 <puts@plt>
   0x00005555555551e5 <+60>:    cmpq   $0x0,-0x8(%rbp)
   0x00005555555551ea <+65>:    je     0x5555555551f8 <main()+79>
   0x00005555555551ec <+67>:    mov    -0x8(%rbp),%rax
   0x00005555555551f0 <+71>:    mov    %rax,%rdi
   0x00005555555551f3 <+74>:    callq  0x5555555550a0 <_ZdaPv@plt>
   0x00005555555551f8 <+79>:    mov    $0x0,%eax
   0x00005555555551fd <+84>:    leaveq
   0x00005555555551fe <+85>:    retq
End of assembler dump.

Dump of assembler code for function main():
   0x00005555555551a9 <+0>:     endbr64
   0x00005555555551ad <+4>:     push   %rbx
   0x00005555555551ae <+5>:     mov    $0x12c,%edi
   0x00005555555551b3 <+10>:    callq  0x555555555080 <_Znam@plt>
   0x00005555555551b8 <+15>:    mov    %rax,%rbx
   0x00005555555551bb <+18>:    mov    $0x12c,%edx
   0x00005555555551c0 <+23>:    mov    %rax,%rsi
   0x00005555555551c3 <+26>:    lea    0xe3a(%rip),%rdi        # 0x555555556004
=> 0x00005555555551ca <+33>:    callq  0x5555555550b0 <__realpath_chk@plt>
   0x00005555555551cf <+38>:    mov    %rbx,%rdi
   0x00005555555551d2 <+41>:    callq  0x5555555550a0 <puts@plt>
   0x00005555555551d7 <+46>:    mov    %rbx,%rdi
   0x00005555555551da <+49>:    callq  0x555555555090 <_ZdaPv@plt>
   0x00005555555551df <+54>:    mov    $0x0,%eax
   0x00005555555551e4 <+59>:    pop    %rbx
   0x00005555555551e5 <+60>:    retq
End of assembler dump.

可以看到优化后的代码调用了一个__realpath_chk函数,这个函数与realpath是不同的。

结论:开启优化后,编译器会用__realpath_chk代替realpath,这个函数的性能更好但容错更低,无论输出长度是多少都会使用PATH_MAX字节,长度不足会越界。

参考资料

realpath(3) - Linux man page (die.net)icon-default.png?t=N7T8https://linux.die.net/man/3/realpathC语言MAX_PATH和PATH_MAX的区别-CSDN博客文章浏览阅读40次。MAX_PATH和PATH_MAXhttps://blog.csdn.net/louObaichu/article/details/140496066

 -------------完--------------

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值