• C安全编码--预处理


    建议和规则

    建议:

    • 用内联函数或静态函数代替与函数相似的宏

    • 在宏参数名两边加上括号

    • 宏替换列表应该加上括号

    • 应该使用typedef定义编码类型

    • 不要复用标准头文件名

    • 理解连接标记或执行字符串化时的宏替换

    • 把头文件放在包含防护条件中

    • 避免使用连续的问号

    • 保证头文件名唯一

    • 不要用不安全的函数替换安全函数

    • 在一个do-while循环中包装多条语句的宏

    规则:

    • 不要通过连接创建统一字符名称

    • 不要在不安全宏的参数中包含赋值、增值、减值、volatile访问或函数调用

    本文地址:http://www.cnblogs.com/archimedes/p/c-security-pretreatment-.html,转载请注明源地址。

    用内联函数或静态函数代替与函数相似的宏

      宏是危险的,用法与真正的函数相似,但是具有不同的语义。C99在C中增加了内联函数,当内联函数和宏可以互换使用时,应该优先选择内联函数,内联替换并不是文本替换,也没有创建函数,决定一个函数是否为内联函数是一个底层的优化细节,编译器应该不依赖程序换做出这个决定,是否使用内联函数取决于目标编译器对它们的支持,它们对系统性能特征所产生的影响以及可移植性问题,静态函数常常具有与内联函数相同的优点。

    下面的例子中,当传递给CUBE宏的参数是一个具有副作用的表达式时,这个宏就具有未定义的行为。

    代码1:

    #define CUBE(x) ((x) * (x) * (x))
    /*...*/
    int i = 2;
    int a = 81 / CUBE(++i);

    在这个例子中,a的初始化表达式展开为: int a = 81/((++i) * (++i) * (++i));

    解决方案:

    inline int cube(int x)
    {
        return x * x *x;
    }
    /*...*/
    int i = 2;
    int a = 81 / cube(++i);

    代码2:

    #include<stdio.h>
    size_t count = 0;
    #define EXEC_BUMP(func) (func(), ++count)
    void g(void) {
        printf("Called g, count = %zu.
    ", count);
    }
    void aFunc(void) {
        size_t count = 0;
        while(count++ <10) {
            EXEC_BUMP(g);
        }
    }
    int main(void){
        aFunc();
        return 0;
    }

    运行结果:

     解决方案:

    #include<stdio.h>
    size_t count = 0;
    void g(void) {
        printf("Called g, count = %zu.
    ", count);
    }
    typedef void(*exec_func)(void);
    inline void exec_bump(exec_func f) {
        f();
        ++count;
    }
    void aFunc(void) {
        size_t count = 0;
        while(count++ <10) {
            exec_bump(g);
        }
    }
    int main(void){
        aFunc();
        return 0;
    }

    运行结果:

    和函数不同,宏的执行可以是交错的,两个宏单独执行时无害,但是它们在同一个表达式中组合在一起时可能导致未定义的行为:

    代码3:

    #define F(x) (++operations, ++calls_to_F, 2 * x)
    #define G(x) (++operations, ++calls_to_G, x + 1)
    /*...*/
    y = F(x) + G(x);

    operations变量在同一个表达式中读取并修改了2次,因此按照某种顺序,可能会接收到错误的值

    解决方案:

    inline int f(int x) {
        ++operations;
        ++calls_to_f;
        return 2 * x;
    }
    inline int g(int x) {
        ++operations;
        ++calls_to_f;
        return 1 + x;
    }
    /*...*/
    y = f(x) + g(x);

    在宏参数名两边加上括号

    代码1:

    #define CUBE(I) (I * I * I)
    int a = 81 / CUBE(2 + 1)

    被展开为: int a = 81 / (2 + 1 * 2 + 1 * 2 + 1);

    解决方案:

    #define CUBE(I) ((I) * (I) * (I))
    int a = 81 / CUBE(2 + 1)

    例外:当替换文本中的参数名由逗号分隔时,不管实际参数如何复杂,不需要对宏参数加上括号,因为逗号操作符的优先级低于其他任何操作符

    #define FOO(a, b, c)   bar(a, b, c)
    /*...*/
    FOO(arg1, arg2, arg3);

    宏替换列表应该加上括号

    宏替换列表应该加上括号,以保护表达式中所有优先级较低的操作符

    代码1:

    #define CUBE(X) (X) * (X) * (X)
    int i = 3;
    int a = 81 / CUBE(i);
    //被展开为: int a = 81 / i * i * i

    解决方案:

    #define CUBE(X) ((X) * (X) * (X))
    int i = 3;
    int a = 81 / CUBE(i);

    这个方案最好实现为内联函数

    应该使用typedef定义编码类型

    如果需要对类型进行编码,应该使用类型定义(typedef)而不是宏定义(#define)。类型定义遵循作用域规则,而宏定义却不遵循

    代码1:

    #define cstring char *
    cstring s1, s2;

    其中s1声明为char *,s2声明为char

    解决方案:

    typedef char * cstring;
    cstring s1, s2;

     不要复用标准头文件名

    如果一个文件与标准头文件同名。并且位于包含源文件的搜索路径中,其行为是未定义的

    建议:不要复用标准头文件名、系统特定的头文件名或其他的头文件名

    把头文件放在包含防护条件中

    防止头文件没多次包含或是忘记包含,通过一种简单的技巧:每个头文件应该用#define指令定义一个符号,表示已经被包含,然后整个头文件出现在一个包含防护条件中:

    #ifndef HEADER_H
    #define HEADER_H
    /*....header的内容*/
    #endif

    避免使用连续的问号

    两个连续的问号表示一个三字符序列,据C99标准,在一个源文件中,下列这些3个字符的连续出现被对应的单个字符所替换

    ??= # ??) ] ??! |
    ??( [ ??' ^ ??> }
    ??/ ??< { ??- ~

    代码1:

    //what is the value of a now ??/
    a++;

    由于??/等价于,a++相当于被注释掉

    解决方案:

    //what is the value of a now? ?/
    a++;

    保证头文件名唯一

    • 文件名中只有前8个字符保证是唯一的

    • 文件名中的点号后面只有1个非数字字符

    • 文件名中字符的大小写并不保证是区分的

    代码1:

    #include<stdio.h>
    #include “Library.h”
    #include "library.h"
    #include "utilities_math.h"
    #include "utilities_physics.h"
    #include "my_library.h"

    Library.h和library.h可能表示同一个文件,并不清楚utilities_math和utilities_physics能否进行区分

    解决方案:

    #include<stdio.h>
    #include “Lib_main.h”
    #include "lib_2.h"
    #include "util_math.h"
    #include "util_physics.h"
    #include "my_library.h"

    不要用不安全的函数替换安全函数

    宏经常用于修补现有的代码,用一个标识符对另一个标识符进行全局替换,但是这种做法存在一些风险,当一个函数被一个不够安全的函数替换时,这种做法就显得特别的危险

    代码:

    #define vsnprintf(buf, size, fmt, list) 
        vsprintf(buf, fmt, list)

    vsprintf函数并不会检查边界,因此size参数将被丢弃,在使用不信任的数据的时候可能会导致潜在的缓冲区溢出问题

    解决方案:

    #include<stdio.h>
    #ifndef __USE_ISOC99
        /* 重新实现 vsnprintf()*/
        #include "my_stdio.h"
    #endif

    在一个do-while循环中包装多条语句的宏

    参见《C语言中do...while(0)用法小结

    参考资料

    《C安全编码标准》

  • 相关阅读:
    基于 HTML5 + WebGL 的 3D 风力发电场
    基于HTML5 WebGL的工业化3D电子围栏
    基于 HTML5 WebGL 和 VR 技术的 3D 机房数据中心可视化
    基于 HTML5 Canvas 的 Web SCADA 组态电机控制面板
    基于 HTML5 WebGL 与 WebVR 3D 虚拟现实的可视化培训系统
    基于 HTML5 WebGL 的 3D 服务器与客户端的通信
    什么是 SUID, SGID 和 Sticky bit
    贝塞尔曲线切割圆角
    iOS-获取当前View所在的控制器
    block(八)作用域
  • 原文地址:https://www.cnblogs.com/wuyudong/p/c-security-pretreatment.html
Copyright © 2020-2023  润新知