sprintf处理int64时要特别小心

sprintf异常成因分析

作者: koangel 发表于: 2017-06-25 10:45:12

0x1 起因

维护老的产品代码时遇到一个存储过程的执行函数采用sprintf组合存储过程以及相关参数的,由于新功能要修改该函数,而该函数中的结尾2个参数采用的是int64表示最大时间,所以开始没注意,其代码的编写格式如下:

sprintf(execBuff,"exec SP_RUNUP %d,%d,%d",flag,startTime,endTime)

其中startTime和endTime为标准的int64,而本身程序是一个运行于windows的32位程序,那么问题来了,我要新增2个flag

sprintf(execBuff,"exec SP_RUNUP %d,%d,%d,%d,%d",flag,startTime,endTime,flag2,flag3)

这个时候打印出的execBuff出现异常:

exec SP_RUNUP 1,375698816,1,36000,0

0x2 分析

我感到很诧异,第一我们看代码参数的顺序和数量都是对的,而flag2为0、flag3为86,为什么打印出来的buffer是一个这么异常的参数呢?而且紧跟一个1,因为endTime是36000,我们并没有传入任何带有1的数据,那么我们继续分析。

通过查看sprintf函数本身的汇编,我们发现实际上所有参数被截断了,startTime由于是一个int64数据,他的长度是8个字节,而一个标准的DWORD长度为4个字节,我们多出来4个字节而这4个字节会往后取代endTime所在的位置成为下一个待组合的数据,而恰巧startTime的计算方式他超过了一个DWORD的最大数据容量,刚好出现了8个字节的数据,所以自然而然的在sprintf中被当作endTime打印,而endTime等参数依次向后挪动,才出现如此诡异的情况。

0x3 解决方案

既然分析出成因我们也就可以提出解决办法,解决办法是针对不同平台的情况使用对应的longlong打印方式,例如linux:

sprintf(execBuff,"exec SP_RUNUP %d,%d,%lld,%lld,%d",flag,startTime,endTime,flag2,flag3)

而windows上则可以:

sprintf(execBuff,"exec SP_RUNUP %d,%d,%I64d,%I64d,%d",flag,startTime,endTime,flag2,flag3)

此处还要注意,并非所有平台对于longlong和int64的处理是一致的,尤其在打印层面,本文此处代码为__int64是标准的windows用法,所以在其他平台请特别自行处理。

具体内容详见下表:

变量定义 输出方式 gcc(mingw32) g++(mingw32) gcc(linux i386) g++(linux i386) MicrosoftVisual C++ 6.0
long long “%lld” 错误 错误 正确 正确 无法编译
long long “%I64d” 正确 正确 错误 错误 无法编译
__int64 “lld” 错误 错误 无法编译 无法编译 错误
__int64 “%I64d” 正确 正确 无法编译 无法编译 正确
long long cout 非C++ 正确 非C++ 正确 无法编译
__int64 cout 非C++ 正确 非C++ 无法编译 无法编译
long long printint64() 正确 正确 正确 正确 无法编译

以上统计数据较为老旧仅供参考。