2 结构体、联合体的安全规范
规则18 4:不允许使用联合体。这是一个不太近情理的规定,在具体阐述为何《MIS—RA—C:2004》如此“痛恨”联合体之前,首先需要明确与联合体相关的细节:
①联合体的末尾有多少个填充单元?
②联合体中的各个成员如何对齐?
③多字节的数据类型高低字节如何排放顺序?
④如果包含位字段(bit—field),各位如何排放顺序?
针对细节3举个例子。
程序段2.1
typedef union{
uilat32_t word;
uint8_t bytes[4];
}word_msg_t;
unit32_t read_nasg(void){
word_rnsg_t tmp;
/*注:tmp bvte[0]对府干tmp.word的高8位,tmp byter[1]对应于
tmp.word的次高8位,依次类推。*/
tmp.bytes[O]=read_byte();
tmp.bytes[1]=read_byte();
tmp.bytes[2]=read_byte();
tmp.bytes[3]=read_byte();
retlarn(trap.word);
}
以上代码格式在各种通信协议中使用的频率很高,接收端接收到的数据一般都以字节为单位存放,主控程序需要根据相应的协议将接收到的多个字节进行组合。为了实现相同的功能,《MISRA-C:2004》推荐了read_msg()函数的另外一种写法。
程序段2.2
uint32_trcad_msg(void){
uint32_t word;
Word=((unit32_t)read_byte())<<24;
word=word│(((unit32_t)read_byte())<<16);
word=word│(((unit32_t)read_byted_byte<<8);
word=word│(((unit32_t)read_byte());
return(word):
}
无论从程序的清晰程度还是执行效率来讲,程序段2.1都优于程序段2.2。然而,程序段2.1在Intel 80x86/Pentlurn体系(1ittle—endian,存储多字节整数的时候低字节存放在低地址,高字节存放在高地址)CPU中和在Mo—torola 68K体系(big—endian,存储多字节整数的时候高字节存放在低地址,低字节存放在高地址)cPu中的执行结果完全不一样。假设read_byte()函数返回的数据依次是0x01、0x02、0x03和0x04,则在Intel体系中,程序段2.1
中read_msg()函数的返回值是0x432l;在Motorola体系中,read_msg()的返回值是0x1234。
无论在Intel体系还是Motorola体系中,程序段2.2中read_msg()的返回值都是0x1 234。
以上是联合体中多字节整型字节排放顺序不定导致漏洞的一个例子。倘若不明确联合体末尾填充的细节,或者不清楚联合体成员的对齐方式,或者不注意联合体中位字段成员的位排列次序,都有可能导致错误。作为将安全性放在第一位的C标准,MlSRA—C禁止使用联合体并非不可理喻。
然而,联合体毕竟是C语言的一个重要元素,所以MISRA—C主张禁止使用联合体的同时,也为效率和资源要求比较苛刻的情况开了一扇门,程序员在明确联合体各个实现细节的前提下,在万不得已的时候,仍可谨慎使用联合体,在不同体系的CPU间移植程序的时候要注意做相应的修改。
此外,《MISRA—C:2004》中也对结构体和联合体的编程风格作了限定。
规则18 1:所有结构体和联合体的定义必须保证完整性。
由于涉及ISOC中类型定义完整性等概念,碍于篇幅的原因,此处就不再赘述,读者可以参阅《MISRA-C:2004》一书和ISOC标准以了解更多信息,完善自己的编程风格。