Bug诞生记——不定长参数隐藏的类型问题

发表于:2019-4-26 12:22

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:breaksoftware    来源:CSDN

  这个bug的诞生源于项目中使用了一个开源C库。由于对该C库API不熟悉,一个不起眼的错误调用,导致一系列诡异的问题。最终经过调试,我们发现发生了内存覆盖问题。为了直达问题根节,我将问题代码简化如下
   #include <iostream>
  #include <stdarg.h>
  enum type {
  PARAM,
  RESULT
  };
  void set_zero(type t, ...) {
  va_list arg;
  va_start(arg, t);
  if (PARAM == t) {
  long* param_longp = va_arg(arg, long *);
  *param_longp = 0;
  } else {
  int* param_intp = va_arg(arg, int *);
  *param_intp = 0;
  }
  va_end(arg);
  }
  int main() {
  int x = 1;
  int y = 2;
  set_zero(PARAM, &y);
  std::cout << "x = " << x << "; y = " << y << std::endl;
  return 0;
  }
  如果只是简单看一下main函数,可以认为输出是
 x = 1; y = 0
  然而实际输出是
 x = 0; y = 0
  是不是很诡异?我们在main函数中只是把y的值从2修改成0,根本没有“动”过x变量。但是最终x的值变成了0。
  由于示例足够简单,我们可以通过阅读源码来定位问题。第26行传递的参数y是4个字节的int类型。而在第13行,发现参数被当成8个字节的long类型设置为0,这样就覆盖了y空间之后的4个字节。而x变量正好在内存上位于y变量之后,这样x的值也会被改成0。
  现实中,我们的场景比较复杂,最终我们通过GDB来确定该问题。其过程大致如下
   Reading symbols from ./test...done.
  (gdb) b 26
  Breakpoint 1 at 0xb0a: file main.cpp, line 26.
  (gdb) r
  Starting program: /home/fangliang/projects/test_cover/test
  Breakpoint 1, main () at main.cpp:26
  26              set_zero(PARAM, &y);
  (gdb) p &x
  $1 = (int *) 0x7fffffffe434
  (gdb) p x
  $2 = 1
  (gdb) p &y
  $3 = (int *) 0x7fffffffe430
  (gdb) p y
  $4 = 2
  (gdb) x/2x &y
  0x7fffffffe430: 0x00000002      0x00000001
  (gdb) awatch x
  Hardware access (read/write) watchpoint 2: x
  (gdb) c
  Continuing.
  Hardware access (read/write) watchpoint 2: x
  Old value = 1
  New value = 0
  set_zero (t=PARAM) at main.cpp:21
  21      }
  (gdb) disas
  ……
  0x0000555555554a64 <+234>:   mov    -0xd8(%rbp),%rax
  0x0000555555554a6b <+241>:   movq   $0x0,(%rax)
  => 0x0000555555554a72 <+248>:   jmp    0x555555554acb <set_zero(type, ...)+337>
  ……
  0x0000555555554acb <+337>:   nop
  0x0000555555554acc <+338>:   mov    -0xb8(%rbp),%rax
  ---Type <return> to continue, or q <return> to quit---q
  Quit
  (gdb) i r rax
  rax            0x7fffffffe430   140737488348208
  (gdb) x/2x 0x7fffffffe430
  0x7fffffffe430: 0x00000000      0x00000000
  第2行在代码第26行下了断点,为了让我们可以在main函数中查看x、y变量的地址和值。
  第10,14和18行可以看出x和y变量的内存空间是连续的。
  第19行我们给“莫名”被修改的变量x下了内存读写断点。执行continue后,由于x的值被从1改成0,从而触发了断点。
  第30行,我们查看当前代码处的汇编指令。
  第33行,是触发内存断点,即x的值被修改的位置。movq是给8个字节赋值,于是我们只要验证rax地址是否就是y变量的地址。
  第41行验证了rax地址就是y变量地址,从而可以证明就是movq   $0x0,(%rax)导致x变量值被改变。
  第43行,我们查看此时x和y的内存空间的值,它们已经都是0了。
  如果我们把set_zero方法改成针对y变量的函数
  void set_param(long* param_longp) {
  *param_longp = 0;
  }
  这样如果我们给其传递int型变量,编译器就会报错
   main.cpp: In function ‘int main()’:
  main.cpp:30:14: error: cannot convert ‘int*’ to ‘long int*’ for argument ‘1’ to ‘void set_param(long int*)’
  set_param(&y);
  而使用可变长参数则正好掩盖了该问题。
 
      上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号