使用 Valgrind 检测 CGI 内存泄漏的简易方法

项目中使用了基于CGIEx构建的CGI,并且通过CGI调用Protobuf API来完成一些动态解析proto定义之类的功能,上线前使用Valgrind的memcheck工具检测CGI是否存在内存泄漏的风险。

Valgrind的使用十分简单,通过设置一定的参数启动二进制可执行程序,并且在执行结束之后收集结果输出即可。但是我们的CGI是通过Apache运行的,不能直接使用Valgrind启动,Google一圈之后没有找到相关的实践,只好自己动手。

首先,直接执行CGI二进制可执行程序,可以看到进入了交互模式(Intractive Mode),并且提示等待用户输入(如下图)。不过如何输入参数?如何确定GET和POST的调用方法?以及如何区分两种方法的参数,却仍是未知数。

继续深入研究,全局搜索”cgihtml Interactive Mode”后找到了线索,在cgi-lib.c文件中找到了get_DEBUG函数定义如下:

char *get_DEBUG()
{
    int bufsize = 1024;
    char *buffer;
    int i = 0;
    char ch;

    if ((buffer = (char *)malloc(sizeof(char) * bufsize + 1)) == NULL)
        exit(1);

    fprintf(stderr,"\n--- cgihtml Interactive Mode ---\n");
    fprintf(stderr,"Enter CGI input string.  Remember to encode appropriate ");
    fprintf(stderr,"characters.\nPress ENTER when done:\n\n");
    while ( (i<=bufsize) && ((ch = getc(stdin)) != '\n') ) {
        buffer[i] = ch;
        i++;
        if (i>bufsize) {
            bufsize *= 2;
            if ((buffer = (char *)realloc(buffer,bufsize)) == NULL)
                exit(1);
        }
    }
    buffer[i] = '\0';
    fprintf(stderr,"\n Input string: %s\nString length: %d\n",buffer,i);
    fprintf(stderr,"--- end cgihtml Interactive Mode ---\n\n");
    return buffer;
}

继续搜索get_DEBUG的调用之处,发现只有一处调用,如下:

char* read_cgi_input(llist* entries)
{
    char *input;
    int status;

    /* check for form upload.  this needs to be first, because the
    standard way of checking for POST data is inadequate.  If you
    are uploading a 100 MB file, you are unlikely to have a buffer
    in memory large enough to store the raw data for parsing.
    Instead, parse_form_encoded parses stdin directly.

    In the future, I may modify parse_CGI_encoded so that it also
    parses POST directly from stdin.  I'm undecided on this issue,
    because doing so will make parse_CGI_encoded less general. */
    if ((CONTENT_TYPE != NULL) &&
        (strstr(CONTENT_TYPE,"multipart/form-data") != NULL ))
    {
        parse_form_encoded(entries);
        return NULL;
    }

    /* get the input */
    if (REQUEST_METHOD == NULL)
        input = get_DEBUG();
    else if (!strcmp(REQUEST_METHOD,"POST"))
    {
        input = get_POST();

        if ((input != NULL) && (strstr(input, "application/x-www-form-urlencoded") == NULL ))
        {
            return input;
        }
    }
    else if (!strcmp(REQUEST_METHOD,"GET"))
        input = get_GET();
    else { /* error: invalid request method */
        fprintf(stderr,"caught by cgihtml: REQUEST_METHOD invalid\n");
        exit(1);
    }
    /* parse the input */
    if (input == NULL)
    {
        return NULL;
    }

    status = parse_CGI_encoded(entries,input);
    FREE(input);
    return NULL;
}
  • 查看read_cgi_input函数,可以看到是通过宏REQUEST_METHOD来获取调用模式,宏REQUEST_METHOD的作用是获取环境变量“REQUEST_METHOD”。同时,还看到除了get_DEBUG函数之外,还定义了get_POST以及get_GET函数来分别处理POST和GET请求。
  • 其中get_DEBUG和get_GET函数的输出结果input都直接作为参数传递给parse_CGI_encoded函数进一步处理,所以DEBUG模式应该跟GET模式的输入参数是一致的。
  • 来看POST接口的调用方式,read_cgi_input函数首先通过CONTENT_TYPE环境变量确定输入参数的格式(text/json等),然后通过REQUEST_METHOD环境变量的取值(POST)进入调用get_POST函数的分支,并且在get_POST函数中通过CONTENT_LENGTH环境变量获取输出参数字符串的长度。

分析到这里,现在大致可以确定如何直接启动CGI二进制文件并输入参数了。

GET

CGI Interactive模式下,输入的参数就是通过GET方式调用时,URL后部所带的参数,形如: param1=val1&param2=val2,所以对于GET接口的测试的步骤很简单

  1. 使用valgrind启动CGI二进制文件进入Intractive模式valgrind --tool=memcheck --log-file=./valgrind_report.log --leak-check=full --show-reachable=yes --track-origins=yes ./cgi_get_sample
  2. 输入参数列表,并以回车结束。如果参数中有需要urlencode的字符请自行转换。starttime=2017-07-31%2014%3A59%3A31&endtime=2017-07-31%2015%3A59%3A31&id=1024
  3. 查看valgrind_report.log中的结果输出

POST

POST接口的调用方式稍微复杂一些。

  1. 设置环境变量export REQUEST_METHOD=POST export CONTENT_LENGTH=381 export CONTENT_TYPE="application/json"
  2. valgrind启动cgi,进入get_POST分支,等待用户输入,只是没有提示信息valgrind --tool=memcheck --log-file=./valgrind_report.log --leak-check=full --show-reachable=yes --track-origins=yes ./cgi_post_sample
  3. 输入json格式字符串参数{"id":1024,"name":"calvin"}
  4. 查看valgrind_report.log中的结果输出
  5. 测试完成后,将环境变量恢复 unset REQUEST_METHOD unset CONTENT_LENGTH unset CONTENT_TYPE

最后,贴一个集成POST/GET两种方式的脚本:

#!/bin/bash
# create by calvinshao

if [ $# -lt 3 ]; then
    echo "USAGE: $0 CGI_NAME DATA_LEN METHOD"
    exit 1
fi

if [ "$3" == "POST" ]; then
    #Set Env param
    export REQUEST_METHOD=POST
    export CONTENT_LENGTH=$2
    export CONTENT_TYPE="application/json"

    echo "Input JSON format string:"

    valgrind --tool=memcheck --log-file=./valgrind_report.log --leak-check=full --show-reachable=yes --track-origins=yes ./$1
else
    unset REQUEST_METHOD
    unset CONTENT_LENGTH
    unset CONTENT_TYPE

    valgrind --tool=memcheck --log-file=./valgrind_report.log --leak-check=full --show-reachable=yes --track-origins=yes ./$1
fi

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员的SOD蜜

使用XSD编写具有智能提示的XML文件(以SQL-MAP脚本为实例)

    SQL-MAP是PDF.NET(PWMIS数据开发框架)具有特色的开发技术,它可以将SQL语句映射成DAL代码,而且能够做到无需IDAL和DalFact...

2698
来自专栏平凡文摘

Spring Boot 内嵌容器 Tomcat / Undertow / Jetty 优雅停机实现

1552
来自专栏青枫的专栏

Linux下gdb的安装及使用入门

用root权限的Terminal(或一般权限的Terminal)的vi编辑器编写一个C程序a.c:

381
来自专栏吴老师移动开发

【iOS开发】URL拦截转换成本地路由模块URLRewrite

接手项目前,已经有这个功能,之前也没有引入路由。这一块的做法是:对url进行path匹配或者字符串匹配,成功后再做特殊的操作。所以经常出现这个url没拦截,那个...

732
来自专栏极客猴

Django学习之旅(四)

因为自己看了其他方面的书,所以Django的学习计划暂时搁浅。我这周重新恢复计划,Django学习之旅第四篇文章姗姗来迟。本文主要讲述视图的一些高级用法,包括 ...

682
来自专栏编程坑太多

『中级篇』k8s的NodePort类型Service以及Label的简单实用(68)

687
来自专栏安恒网络空间安全讲武堂

XCTF-赛博地球杯工业互联网安全大赛web部分题解

0x01工控云管理系统项目管理页面解析漏洞 题目首先给出了源码: `http://47.104.156.32:20007/view-source.php` 关键...

2358
来自专栏java闲聊

jdk10与springboot2.1.0尝鲜

1723
来自专栏xdecode

开发安全规约

 所有可以通过web端访问到的页面, 都需要考虑是否加上权限控制. 包括各类service & action. 1 long userID = ...

1919
来自专栏DannyHoo的专栏

XML解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

712

扫码关注云+社区