Chinaunix首页 | 论坛 | 博客
  • 博客访问: 303946
  • 博文数量: 23
  • 博客积分: 2589
  • 博客等级: 少校
  • 技术积分: 960
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-30 10:26
文章分类

全部博文(23)

文章存档

2012年(1)

2011年(3)

2009年(13)

2008年(6)

我的朋友

分类: C/C++

2008-05-27 13:50:40

在一个很简单的printf调用中:
 printf( "blit (T:%d, L:%d, W:%d, H:%d) to (T:%d, L:%d, W:%d, H:%d) costs %d
          ticks(%dms)\n",
            nPositionY, nPositionX, nWidth, nHeight,
            nPositionY, nPositionX, nWidth, nHeight,
            clkCost, time_ms( clkCost ) );
原意是让clkCost匹配costs %dticks,让time_ms(clkCost)匹配(%dms)。
但clkCost是long long类型(8字节),匹配完costs %dticks(4字节)后会侵占后面(%dms)中的位置,直接导致time(clkCost)的值没有位置输出。

C中的printf函数没有参数长度检查,所以在编译期间无法发现这个漏洞。
类似的错误还有参数长度不足或严重不匹配(多见于字符串),直接导致程序crash。

这两错误的本因是printf调用的压栈、解读策略:
首先定义两个名词:
实参 -- printf中第一个参数之后的参数,即除格式字符串之外的所有参数。
槽   -- 格式字符串中的匹配点,即%d, %s, %f等。
1. 调用printf压栈时实参逐个进栈,并不记录栈中每个实参的具体位置。
2. 解读格式字符串时每遇到一个槽即从栈中读取相应字节数进行替换:例如遇到%d则读4个字节进行替换(读指针随即下移);遇到%s则读取4个字节作为字符串首址读取字符串并替换(首址错误可以导致crash或堆栈溢出);遇到%llx字读取8个字节进行替换......

这种不检查长度的替换策略导致的问题中最臭名昭著的莫过于“堆栈溢出攻击”。本文不详谈。
下面再看看开头的例子,其中clkCost是8字节类型,进栈后一切变量类型都失去了意义,所有数据只有被解读时才被赋予意义。cost %dticks中的槽从栈中读取4字节替换%d,clkCost的高字节则被(%dms)中的槽读去,格式字符串解读结束。

不知python的发明人是否多次被此类问题叨扰,在python的"压栈、解读"包struct中有明确的长度检查,即使实参是字符也会要求明确其长度:
cont = 'abcd'
struct.pack( '%ds' % len(cont), cont )
struct.unpack( 's'*len(cont), cont )
不过python中的问题也只有在运行时才会出现,这一点并不比C优越,但python抛出的异常可以提供更多的错误信息,比C的直接crash要来的温和很多。
阅读(1513) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~