今天在测试协议的时候发现网关设备老是重启,查看下原因发现是内核对NULL进行解引用了。
出现问题的代码如下:
static int qq_match_prepare(struct app_param *param, struct app_protocol *protocol)
{
struct http_header *hdr = &__get_cpu_var(http_header);
const char *buf = param->buf;
short index;
struct app_conntrack *app=param->app;
const char *tmp;
char *pos = NULL;
char qq[16] = {0};
unsigned int qq_num = 0;
if (hdr->req.len > 104 &&
app_memeql(buf, "GET /gchatpic") &&
http_hdr_match_head(buf, hdr, HTTP_REFERER, "http://im.qq.com/mobileqq") &&
http_url_search2(buf, hdr, "uin=", pos)){
int i;
pos += 5;
for (i = 0; i < 16 && *pos != '&' && *pos != ' '; i++, pos++)
qq[i] = *pos;
qq_num = simple_strtoul(qq, NULL, 10);
memcpy(param->app->protocol_private, &qq_num, 4);
return 1;
}
return 0;
}
其中http_url_search2为宏函数,定义如下
#define http_url_search2(buf, hdr, str, p) \
({ int ret = 0;\
char c;\
char *pos;\
pos = (char *)(buf + (hdr)->req.begin + (hdr)->req.len);\
c = *pos;\
*pos = 0;\
ret = ((hdr)->req.len >= STRSIZE(str) && \
(p = strstr(buf, str)));\
*pos = c;\
ret;\
})
出现怪异的现象有两个:
- 原字符串缓冲区buf内容会有一个字符被修改;
- 解引用NULL指针
看了很久也没看出头绪,最后把注意力集中了在http_url_search2宏函数中,因为这个宏函数在此处的调用和其它地方有点不同。仔细分析之后发现原来是宏替换引发的问题,宏函数外面和里面使用了同样的变量名pos,导致替换后的结果与预期相背。于是预编译下该文件验证自己的想法,代码如下:
static int qq_match_prepare(struct app_param *param, struct app_protocol *protocol)
{
struct http_header *hdr = &__get_cpu_var(http_header);
const char *buf = param->buf;
short index;
struct app_conntrack *app=param->app;
const char *tmp;
char *pos = ((void *)0);
char qq[16] = {0};
unsigned int qq_num = 0;
if (hdr->req.len > 104 &&
app_memeql(buf, "GET /gchatpic") &&
({ short index; int ret; if (HTTP_REFERER >= MAX_HTTP_TYPE || ((index = (hdr)->http_type[HTTP_REFERER]) == -1)) ret = 0; else { ret = ((hdr)->fields[index].value_len >= (sizeof(char [1 - 2 * is_ptr("http://im.qq.com/mobileqq")]) * 0 + sizeof("http://im.qq.com/mobileqq") - 1) && app_memeql((buf) + ((hdr)->fields[index].value_begin), "http://im.qq.com/mobileqq")); } ret; }) &&
({ int ret = 0; char c; char *pos; pos = (char *)(buf + (hdr)->req.begin + (hdr)->req.len); c = *pos; *pos = 0; ret = ((hdr)->req.len >= (sizeof(char [1 - 2 * is_ptr("uin=")]) * 0 + sizeof("uin=") - 1) && (pos = strstr(buf, "uin="))); *pos = c; ret; })){
int i;
pos += 5;
for (i = 0; i < 16 && *pos != '&' && *pos != ' '; i++, pos++)
qq[i] = *pos;
qq_num = simple_strtoul(qq, ((void *)0), 10);
memcpy(param->app->protocol_private, &qq_num, 4);
return 1;
}
return 0;
}
由预处理后的代码可以看出,传入宏函数的参数和宏函数体使用了相同的变量名,由于宏函数仅是简单的文本替换,所以导致本来意义不同的变量被编译器误认为是相同的变量,pos = strstr(buf, "uin=");
之后*pos = c
,所以当strstr调用成功的时候原字符串缓冲区buf内容会有一个字符被修改,当调用失败的时候会对NULL指针解引用。
如果http_url_search2是一个函数的话就不会出现这种问题!
总结:在使用宏函数的时候一定要千万谨慎小点,最重要的一点是记住宏只是简单的文本替换。