讲 授 内 容 | |
splint 一.splint介绍 splint是一个开源的静态代码检测工具,用于动态检查C语言程序安全弱点和编写错误的程序。splint会进行多种常规检查,包括未使用的变量,类型不一致,使用未定义变量,无法执行的代码,忽略返回值,执行路径未返回,无限循环等错误。它以大师级的眼光来审阅你的代码,Splint只能检测标准C代码。 'lint-clean' -- 程序能够顺利通过lint程序的检查。这是微软的编码要求。 二.splint的安装 (一)Linux环境下的安装 1.rpm安装 GTES 10.5和11版本已经整合有splint软件包,直接可以使用。 2.源代码安装 下载地址: /downloads/splint-3.1. 源码包安装: # tar zxvf splint-3.1. # cd splint-3.1.2 # ./configure # make install (二)Windows环境下的安装 1. 将软件解压到c:\。 2.设置用户变量: larch_path:安装splint的路径\lib lclimprotdir:安装splint的路径\imports include:安装tc 的路径\include path:安装splint的路径\bin 3.注销当前用户后重新登陆,即可使用。 三.splint的用法 在“开始”-“运行”中输入cmd,进入dos窗口,输入cd\,到C盘根目录,输入splint运行命令。 基本用法: splint *.c Splint输出Warnings的格式: <file>:<line>[,<column>]: message [hint] <file>:<line>,<column>: extra location information, if appropriate 1.使用' +/-<flags>'来自定义其输出格式,如'splint -showcol *c',则Splint不会在输出信息中显示'列'信息。 使用flags控制splint的检查范围和输出格式 '+<flag>' -- 表明某个flag处于打开状态,如'+unixlib'; '-<flag>' -- 表明某个flag处于关闭状态,如'-weak'; 2.使用.splintrc环境文件 如果不想每次使用splint的时候都手工输入一堆'+/-<flags>',那么你可以把这些'+/-<flags>'预先写到.splintrc文件中,当splint执行的时候它会自动加上这些flags的。默认的flags设置在'~/splintrc'文件中,但是如果一旦splint的当前工作路径下也有.splintrc文件,那么这个.splintrc文件中的flag设置会覆盖'~/splintrc'中的flags设置,但是命令行中的flags设置是具备最高优先级的,它会覆盖前面提到的任何一个文件中的flags设置。 3.使用Annotations: 对于'Annotations'的作用,Java程序员并不陌生,但是C程序员则对这个不是那么了解。C代码中的Annotations用来指导Splint生成恰当的代码检查报告。 下面这个例子对比使用和不使用Annotations,Splint的输出的差别: /* testlint.c */ void foo1() { /*@unused@*/int *p = NULL; } void foo2() { int *p = NULL; } splint testlint.c Splint 3.1.1 --- 28 Apr 2003 testlint.c: (in function foo2) testlint.c:6:7: Variable p declared but not used A variable is declared but never used. Use /*@unused@*/ in front of declaration to suppress message. (Use -varuse to inhibit warning) Finished checking --- 1 code warning 可以看出没使用Annotation的函数foo2被给出Warning了。Splint的Annotations繁多,我们在平时做lint时可以多多接触。 '早用lint,勤用lint',这是C专家给我们的建议。'lint-clean'也许离你并不遥远。 四.splint的使用 1.空引用错误 在引用没有指向任何内存地址的指针时,会导致这种错误,也就是使用了一个没有赋值的指针。splint支持一种特别的注释,这种注释写在C程序代码中,用于对程序进行特殊说明。如下面这段程序,使用了/*@null@*/进行了说明,表示说明*s的值可能会是NULL。 //null.c char firstChar1 (/*@null@*/ char *s) { return *s; } char firstChar2 (/*@null@*/ char *s) { if (s ==NULL) return '\0'; return *s; } //END 使用splint扫描这个程序时,会输出: # splint null.c Splint 3.1.1 --- 28 Apr 2005 null.c: (in function firstChar1) null.c:3:11: Dereference of possibly null pointer s: *s null.c:1:35: Storage s may become null Finished checking --- 1 code warning found 由于firstChar1和firstChar2都使用了null说明,表示指针s可能是个NULL值。所以,splint会对s值的使用情况进行检查。因为firstChar2函数中,对s的值进行了NULL的判断。所以,没有对firstChar2函数的指针s输出警告信息。 2.未定义的变量错误 C语言中,要求先定义变量,而后才可使用。所以,当使用一个没有定义的变量时,编译就会出错。如下例,使用/*@in@*/说明的变量,表示必须进行定义。使用/*@out@*/说明的变量,表示在执行过此函数后,这个变量就进行了定义。 // usedef.c extern void setVal (/*@out@*/ int *x); extern int getVal (/*@in@*/ int *x); extern int mysteryVal (int *x); int dumbfunc (/*@out@*/ int *x, int i) { if (i > 3) return *x; else if (i > 1) return getVal (x); else if (i == 0) return mysteryVal (x); else { setVal (x); return *x; } } // END 使用splint检查usedef.c: $ splint usedef.c Splint 3.1.1 --- 28 Apr 2005 usedef.c: (in function dumbfunc) usedef.c:7:19: Value *x used before definition An rvalue is used that may not be initialized to a value on some execution path. (Use -usedef to inhibit warning) usedef.c:9:18: Passed storage x not completely defined (*x is undefined): getVal (x) Storage derivable from a parameter, return value or global is not defined. Use /*@out@*/ to denote passed or returned storage which need not be defined. (Use -compdef to inhibit warning) usedef.c:11:22: Passed storage x not completely defined (*x is undefined): mysteryVal (x) Finished checking --- 3 code warnings //错误原因: 由于程序中没有对x进行定义,所以报未定义错误。但setVal()使用了/*@out@*/说明,所以在setVal(x);和return x;中,没有报未定义错误。 3.类型错误 C语言中的数据类型较多,各个之间有些细微差别。splint也可以对变量类型进行检查。 示例1: //bool.c int f (int i, char *s,bool b1, bool b2) { if (i = 3) return b1; if (!i || s) return i; if (s) return 7; if (b1 == b2) return 3; return 2; } //END 使用splint进行检查: $ splint bool.c Splint 3.1.1 --- 28 Apr 2005 bool.c: (in function f) bool.c:4:5: Test expression for if is assignment expression: i = 3 The condition test is an assignment expression. Probably, you mean to use == instead of =. If an assignment is intended, add an extra parentheses nesting (e.g., if ((a = b)) ...) to suppress this message. (Use -predassign to inhibit warning) // 错误原因: if语句中的条件表达式是一个赋值语句。 bool.c:4:5: Test expression for if not boolean, type int: i = 3 Test expression type is not boolean or int. (Use -predboolint to inhibit warning) // 错误原因: if语句中的条件表达式的返回值,不是布尔型,而是整型。 bool.c:4:8: Return value type bool does not match declared type int: b1 Types are incompatible. (Use -type to inhibit warning) // 错误原因: 返回值是布尔型,而不是整型。 bool.c:5:6: Operand of ! is non-boolean (int): !i The operand of a boolean operator is not a boolean. Use +ptrnegate to allow ! to be used on pointers. (Use -boolops to inhibit warning) // 错误原因: "!"操作符的操作数不是布尔型,而是整型i. bool.c:5:11: Right operand of || is non-boolean (char *): !i || s // 错误原因: "||"操作符的右操作数不是布尔型,而是字符指针. bool.c:7:5: Use of == with boolean variables (risks inconsistency because of multiple true values): b1 == b2 Two bool values are compared directly using a C primitive. This may produce unexpected results since all non-zero values are considered true, so different true values may not be equal. The file bool.h (included in splint/lib) provides bool_equal for safe bool comparisons. (Use -boolcompare to inhibit warning) // 错误原因: 使用c++strcpy函数用法"=="对两个布尔型进行比较.应该使用"&&". Finished checking --- 6 code warnings 示例2: //malloc1.c #include <stdlib.h> #include <stdio.h> int main(void) { char *some_mem; int size1=1048576; some_mem=(char *)malloc(size1); printf("Malloed 1M Memory!\n"); free(some_mem); exit(EXIT_SUCCESS); } //END 使用splint检查malloc1.c $ splint malloc1.c Splint 3.1.1 --- 28 Apr 2005 malloc1.c: (in function main) malloc1.c:9:25: Function malloc expects arg 1 to be size_t gets int: size1 To allow arbitrary integral types to match any integral type, use +matchanyintegral. Finished checking --- 1 code warning 修改变量size1的定义为: size_t size1=1048576; 再使用splint进行检查。 $ splint malloc1.c Splint 3.1.1 --- 28 Apr 2005 Finished checking --- no warnings 没有检查到错误。 4.内存检查 缓冲区溢出错误是一种非常危险的C语言错误,大部分安全漏洞都与它有关。splint可以对缓冲区的使用进行检查,报告溢出或越界错误。 示例1: //over.c int main() { int buf[10]; buf[10] = 3; retrun 0; } //END 使用splint进行检查 $ splint over.c +bounds +showconstraintlocation Splint 3.1.1 --- 21 Apr 2006 Command Line: Setting +showconstraintlocation redundant with current value over.c: (in function main) over.c:6:3: Likely out-of-bounds store: buf[10] Unable to resolve constraint: requires 9 >= 10 needed to satisfy precondition: requires maxSet(buf @ over.c:6:3) >= 10 A memory write may write to an address beyond the allocated buffer. (Use -likely-boundswrite to inhibit warning) Finished checking --- 1 code warning 数组buf的大小是10字节,最大可使用的元素位置为buf[9],但程序中使用了buf[10]。所以报错。 示例2: // bound.c void updateEnv(char *str) { char *tmp; tmp = getenv("MYENV"); if(tmp != NULL) strcpy(str,tmp); } void updateEnvSafe(char *str, size_t strSize) { char *tmp; tmp = getenv("MYENV"); if(tmp != NULL) { strncpy(str, tmp, strSize -1); str[strSize - 1] = '/0'; } } //END 执行splint: $ splint bound.c +bounds +showconstraintlocation Splint 3.1.1 --- 21 Apr 2006 Command Line: Setting +showconstraintlocation redundant with current value bound.c: (in function updateEnv) bound.c:6:19: Possible out-of-bounds store: strcpy(str, tmp) Unable to resolve constraint: requires maxSet(str @ bound.c:6:26) >= maxRead(getenv("MYENV") @ bound.c:5:9) needed to satisfy precondition: requires maxSet(str @ bound.c:6:26) >= maxRead(tmp @ bound.c:6:30) derived from strcpy precondition: requires maxSet(<parameter 1>) >= maxRead(<parameter 2>) A memory write may write to an address beyond the allocated buffer. (Use -boundswrite to inhibit warning) 错误原因: 由于使用strcpy函数时,没有指定复制字符串的长度,所以可能导致缓冲区溢出。后面的updateEnvSafe函数,使用了strncpy进行字符串复制,避免了这种情况。 Splint的使用练习: 1、检测下面程序的错误,并分析改正。 #include<stdio.h> #include<stdlib.h> int main(int argc, char **argv) { printf("Hello,Splint\n"); return 0; } 这段代码大家肯定很熟悉吧,下面用Splint来检测.下面是检测结果: Splint 3.0.1.6 --- 11 Feb 2002 hello.c: (in function main) hello.c(4,14): Parameter argc not used A function parameter is not used in the body of the function. If the argument is needed for type compatibility or future plans, use /*@unused@*/ in the argument declaration. (Use -paramuse to inhibit warning) hello.c(4,27): Parameter argv not used Finished checking --- 2 code warnings 从输出结果中可以看出,有两个警告,提示是变量argc和argv没有使用,并且提示可以加入/*@unused@*/忽略这个警告。 更改后的代码: #include<stdio.h> #include<stdlib.h> int main(/*@unused@*/int argc, /*@unused@*/char **argv) { printf("Hello,Splint\n"); return 0; } 然后我们再来检测: Splint 3.0.1.6 --- 11 Feb 2002 Finished checking --- no warnings 提示没有警告,这样也就是lint clean. 2、看一下下面的代码(当然包括错误,以检验splint的功能): #include int main(int argc,char* argv[]){ int a=100; /*没有使用的变量*/ int b[8]; printf("Hello c\n"); b[9]=100; /*明显数组越界 */ /* 用到了两个为声明的变量c和d*/ c=100; d=10; return 0; } 现在可以用splint来检查一下,为了检验是否可以检测到数组越界,使用+bounds选项。 splint hi.c +bounds 输出结果: hi.c: (in function main) hi.c:9:2: Unrecognized identifier: c Identifier used in code has not been declared. (Use -unrecog to inhibit warning) hi.c:10:2: Unrecognized identifier: d hi.c:4:6: Variable a declared but not used A variable is declared but never used. Use /*@unused@*/ in front of declaration to suppress message. (Use -varuse to inhibit warning) hi.c:7:2: Likely out-of-bounds store: b[9] Unable to resolve constraint: requires 7 >= 9 needed to satisfy precondition: requires maxSet(b @ hi.c:7:2) >= 9 A memory write may write to an address beyond the allocated buffer. (Use -likely-boundswrite to inhibit warning) hi.c:3:14: Parameter argc not used A function parameter is not used in the body of the function. If the argument is needed for type compatibility or future plans, use /*@unused@*/ in the argument declaration. (Use -paramuse to inhibit warning) hi.c:3:25: Parameter argv not used Finished checking --- 6 code warnings 现在详细看一下结果: 检查结果1: hi.c:9:2: Unrecognized identifier: c Identifier used in code has not been declared. (Use -unrecog to inhibit warning) hi.c:10:2: Unrecognized identifier: d hi.c:4:6: Variable a declared but not used A variable is declared but never used. Use /*@unused@*/ in front of declaration to suppress message. (Use -varuse to inhibit warning) 这些应该是splint检测到变量c和d没有声明。 检查结果2: hi.c:7:2: Likely out-of-bounds store: b[9] Unable to resolve constraint: requires 7 >= 9 needed to satisfy precondition: requires maxSet(b @ hi.c:7:2) >= 9 A memory write may write to an address beyond the allocated buffer. (Use -likely-boundswrite to inhibit warning) 这些是检查存在数组越界,因为b[8]的最大数组序号应该是7,而不是9,所以出现requires 7 >= 9; 检查结果3: hi.c:3:14: Parameter argc not used A function parameter is not used in the body of the function. If the argument is needed for type compatibility or future plans, use /*@unused@*/ in the argument declaration. (Use -paramuse to inhibit warning) hi.c:3:25: Parameter argv not used 这些表明argc和argv变量声明了,但是没有使用。这个可以加入/*@unused@*/忽略这个警告。 小心使用splint,应该对于c语言的程序编写有非常大的辅助作用! | |
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论