Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1014666
  • 博文数量: 327
  • 博客积分: 9995
  • 博客等级: 中将
  • 技术积分: 4319
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-25 11:21
文章存档

2011年(31)

2010年(139)

2009年(157)

我的朋友

分类: C/C++

2009-10-02 09:28:32

第一章 词法陷阱
为了避免误将==和=写错,最好一开始定义一个宏,如#define EQUAL ==
010不等于10(前者八进制)
 
词法分析中的贪心法:编译器将程序分解成符号的方法是:从左到有一个一个字符的读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符床是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。
 
第二章 语法陷阱
理解函数声明
要求:当计算机启动时,硬件调用首地址为0位置的子程序。
语句:( * ( void (*) ( ) ) 0) ( );
更清晰的语句:
typedef void (*funcptr)();
( * (funcptr) 0 ) ( );
分析:
声明:float (*h)();表示h是一个指向返回值为浮点类型的函数指针
类型转换符:(float (*)())转换一个数或变量为指向返回值为浮点类型的函数指针
如何调用指针指向的函数:(*fp)();
运算符优先级
从高到低:
非真正运算符:() [] -> .
单目运算符:!~ ++ -- - (type) * & sizeof
双目运算符:
1、乘除 * / %
2、加减 + -
3、移位 << >>
比较运算符
1、比较大小 < <= > >=
2、比较是否相等 == !=
逻辑运算符(每个逻辑运算符优先级都不同,从大到小为)
按位(与 异或 或 ),与,或
& ^ | && ||
三目运算符 ? :
赋值运算符 =
逗号
结合顺序:紫色的为从右至左,其它为从左至右
 
第三章 语义陷阱
C语言中真正意义上只有一维数组,对于多维数组,如int p[3][4],p实际上是指向数组的指针组成的一位数组。
对于一个数组,我们只能做两件事:确定数组大小,以及获得指向该数组下标为0的元素的指针。任何一个数组下标运算都等同于一个对应的指针运算。
若定义:int calendar[12] [31];
则sizeof(calendar)的值等于31 * 12 * sizeof(int)
sizeof一个指针,大小是多少?
合并两个字符串到一个新字符串的程序的正确写法:
char *r;
r = malloc(strlen(s) + strlen(t) + 1);
if(!r)
    exit(1);
strcpy(r, s);
strcat(r, t);
free(r);
用malloc显式分配的空间,不会再推出本函数后自动释放掉,而是会等程序员显式释放后才消失。
注意检查,malloc分配的内存可能失败。
C语言中会自动地将作为函数参数的数组声明转换为对应的指针声明,如:
int strlen(char s[ ]){ }等价于int strlen(char *s){ }
但在其他情形下不会自动转换,也就是说不等价,如:
extern char hello[ ];和extern char *hello;完全不同。
——————————————————————————————————————————————
边界计算
自己实现一个memcpy函数:
void
memcpy(char *dest, const char *source, int k)
{
    while( --k >= 0 )
        *dest++ = *source++;
}
可以编一个调用memcpy来一次转移一批字符到一个全局缓冲区的bufwrite函数:
循环中的每次迭代在必要时会刷新缓存,计算需要移动的字符数,移动这些字符,最后恰当的更新计数器。
void
bufwrite(char *p, int n)
{
    while(n > 0)
    {
        int k, rem;
        if(bufptr == &buffer[N])
            flushbuffer();
        rem = N - (bufptr - buffer);
        k = n > rem ? rem : n;
        memcpy(bufptr , p, k);
        bufptr += k;
        p += k;
        n -= k;
    }
}
绿色是全局定义的变量和宏
红色部分是边界判断
紫色部分是计算剩余缓冲区
程序:按一定顺序生成一些整数,并将这些整数按列输出。程序的输出可能包括若干页的证书,每页包括NCOLS列,每列有包括NROWS个元素,每个元素就是一个带输出的整数。
我们假定程序是由两个函数print和flush来实现。
具体见书……
——————————————————————————————————————————————
整数溢出
两个有符号整数相加会发生溢出。
两个无符号整数相加不会发生溢出。
一个有符号和一个无符号整数相加,因为有符号被自动转换成无符号,所以也不会溢出。
在某些机器上,加法运算将设置一个内部寄存器为四个状态之一:正,负,零,溢出。在这种机器上,企图通过以下语句来判断a+b是否溢出了,是行不通的。
if(a + b < 0)
       complain();
那么应该怎么办?书中没有给通用的方法。
于是在网上搜索关于整数溢出的资料,主要参考网站是:,但是这里也是主要列出需要注意的编程情况,没有什么有效的解决方案。整数溢出的避免真的如此棘手吗?
 
下面是一些摘要:
     ISO C99中提到包含无符号整型数的运算不会溢出,因为多出的数值会被取模掉。并且提到证书溢出会引起"undefined behaviour"遵照该规范的编译器有可能忽略错误,也有可能报错并终止程序。但大多数编译器选择忽略溢出。溢出很多时候难以通过检查来避免。
 
几个overflow的类型和例子:
 
1、Widthness overflows
    /* width1.c - exploiting a trivial widthness bug */
    #include
    #include
    int main(int argc, char *argv[]){
            unsigned short s;
            int i;
            char buf[80];
            if(argc < 3){
                    return -1;
            }
            i = atoi(argv[1]);
            s = i;
            if(s >= 80){            /* [w1] */
                    printf("Oh no you don't!\n");
                    return -1;
            }
            printf("s = %d\n", s);
            memcpy(buf, argv[2], i);
            buf[i] = '\0';
            printf("%s\n", buf);
            return 0;
    }
程序运行后的输入和结果的例子:
    #./width1 5 hello
    s = 5
    hello
    #./width1 80 hello
    Oh no you don't!
    #./width1 65536 hello
    s = 0
    Segmentation fault (core dumped)
2、Arithmetic overflows
    int myfunction(int *array, int len){
        int *myarray, i;
        myarray = malloc(len * sizeof(int));    /* [1] */
        if(myarray == NULL){
            return -1;
        }
        for(i = 0; i < len; i++){              /* [2] */
            myarray[i] = array[i];
        }
        return myarray;
    }
因为对len的值没有检查,所以会引起很多错误。比如:
       The multiplication at [1] can be made to overflow by supplying a high enough value for len, so we can force the buffer to be any length we choose.
       By choosing a suitable value for len, we can cause the loop at [2] to write past the end of the myarray buffer, resulting in a heap overflow.
       This could be leveraged into executing arbitrary code on certain implementations by overwriting malloc control structures, but that is beyond the scope of this article.
       上面说的这两种情况有什么区别??具体应该选什么样的len值?
 
3、Signedness Bugs
       当有符号数被诠释为无符号数或无符号数倍诠释为有符号数时,可能发生此类bug。两种数在计算机中的存储没有区别。
       常发生此类bug的情况:有符号数在比较式中或数学计算中被使用,有符号数和无符号数放在一起比较。
一个经典的例子:
    int copy_something(char *buf, int len){
        char kbuf[800];
        if(len > sizeof(kbuf)){         /* [1] */
            return -1;
        }
        return memcpy(kbuf, buf, len); /* [2] */
    }
另一个例子:
Since the line
    table[pos] = val;
is equivalent to
    *(table + (pos * sizeof(int))) = val;
we can see that the problem here is that the code does not expect a negative operand for the addition: it expects (table + pos) to be greater than table, so providing a negative value for pos causes a situation which the program does not expect and can therefore not deal with.
Signedness bugs caused by integer overflows
Sometimes, it is possible to overflow an integer so that it wraps around toa negative number. Since the application is unlikely to expect such a value, it may be possible to trigger a signedness bug as described above.
    int get_two_vars(int sock, char *out, int len){
        char buf1[512], buf2[512];
        unsigned int size1, size2;
        int size;
        if(recv(sock, buf1, sizeof(buf1), 0) < 0){
            return -1;
        }
        if(recv(sock, buf2, sizeof(buf2), 0) < 0){
            return -1;
        }
        /* packet begins with length information */
        memcpy(&size1, buf1, sizeof(int));
        memcpy(&size2, buf2, sizeof(int));
        size = size1 + size2;       /* [1] */
        if(size > len){             /* [2] */
            return -1;
        }
        memcpy(out, buf1, size1);
        memcpy(out + size1, buf2, size2);
        return size;
    }
This example shows what can sometimes happen in network daemons, especially when length information is passed as part of the packet (in other words, it is supplied by an untrusted user). The addition at [1], used to check that the data does not exceed the bounds of the output buffer, can be abused by setting size1 and size2 to values that will cause the size variable to wrap
around to a negative value. Example values could be:
    size1 = 0x7fffffff
    size2 = 0x7fffffff
    (0x7fffffff + 0x7fffffff = 0xfffffffe (-2)).
When this happens, the bounds check at [2] passes, and a lot more of the out buffer can be written to than was intended (in fact, arbitrary memory can be written to, as the (out + size1) dest parameter in the second memcpy call allows us to get to any location in memory).
These bugs can be exploited in exactly the same way as regular signedness bugs and have the same problems associated with them - i.e. negative values translate to huge positive values, which can easily cause segfaults.
 
第四章 连接
编译器一般每次只处理一个文件。编译器的责任是把C源程序翻译成对连接器有意义的形式。

许多系统中的连接器是独立于C语言实现的,因此如果链接时候错误原因是与C语言相关的,连接器无法判断错误原因。但连接器能够理解机器语言和内存布局。
典型的连接器把由汇编器或编译器生成的若干个目标模块,整合成一个被称为载入模块或可执行文件的实体。

连接器通常把目标模块看成是由一组外部对象组成的。每个外部对象代表着机器内存中的某个部分,并通过一个外部名称来 识别。因此,程序中的每个函数和每个外部变量,如果没有被声明为static,就都是一个外部对象。static的不会与其它源程序文件中的同名函数或同 名变量发生冲突。对于非satatic的函数或变量的名称冲突的解决办法将在后面讨论。除了外部对象外,目标模块中还可能包括了对其他模块中的外部对象的引用,当连接器读入一个目标模块时,它必须解析出这些引用,并作出标记说明这些外部对象不再是未定义的。

连接器的输入是一组目标模块文件和库文件。输出是一个载入模块。
 
避免外部变量的函数的冲突和不一致等问题的办法:
每个外部对象只在一个头文件里声明,需要用到该外部对象的所有模块都应该包括这个头文件。定义该外部对象的模块也应该包括这个头文件。
 
第五章 库函数
主要讲了一些常见的库函数,以及编程者在使用他们过程中可能出错之处。
如果要同时对打开的文件进行读写操作,需要在其中插入fseek函数。如:
FILE *fp;
struct record rec;
……
while( fread( (char *)&rec, sizeof(rec), 1, fp) == 1 )
{
        /* handle rec here*/
        if(/*we have to write rec back to file*/)
        {
                fseek(fp, -(long)sizeof(rec), 1);
                fwrite( (char*)&rec, sizeof(rec), 1, fp);
                fseek(fp, 0L, 1);
        }
}
 
使用errno
/*调用库函数*/
if(返回了错误值)
        检查errno
 
使用signal库函数:
#include
signal( signal type, handler function);
但一个信号可能在C程序执行期间的任何时刻上发生。甚至可能出现在某些复杂库函数(如malloc)的执行过程中。
因此从安全角度考虑,信号函数不应该调用上述类型的库函数。
阅读(1275) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~