zoukankan      html  css  js  c++  java
  • C++安全编码摘录

    1原文:

    https://resources.sei.cmu.edu/downloads/secure-coding/assets/sei-cert-cpp-coding-standard-2016-v01.pdf

     

    说明:

    Noncompliant Code ExampleNCE) :不符合安全编码规则的代码示例

    Compliant SolutionCS) :符合安全规则的解决办法

    2 Declarations and Initialization(DCL)

    DCL53 Do not write syntactically ambiguous declarations

    NCE

    #include <iostream>

    struct Widget {

        explicit Widget(int i) {

            std::cout << "Widget constructed" << std::endl;

        }

    };

    struct Gadget {

        explicit Gadget(Widget wid) {

            std::cout << "Gadget constructed" << std::endl;

        }

    };

    void f() {

        int i = 3;

        Gadget g(Widget(i)); //被解析为一个函数声明

        std::cout << i << std::endl;

    }

    int main(){
    f();

        return 0;

    }

    运行结果

    3

    CS

    #include <iostream>

    struct Widget {

        explicit Widget(int i) {

            std::cout << "Widget constructed" << std::endl;

        }

    };

    struct Gadget {

        explicit Gadget(Widget wid) {

            std::cout << "Gadget constructed" << std::endl;

        }

    };

    void f() {

        int i = 3;

        Gadget g1((Widget(i)));  // Use extra parentheses

        Gadget g2{Widget(i)};  // Use direct initialization

        std::cout << i << std::endl;

    }

    int main(){
    f();

        return 0;

    }

    运行结果

    Widget constructed

    Gadget constructed

    Widget constructed

    Gadget constructed

    3

    DCL56 Avoid cycles during initialization of static objects

    NCE

    // file.h

    #ifndef FILE_H

    #define FILE_H

    class Car {

    int numWheels;

    public:

    Car() : numWheels(4) {}

    explicit Car(int numWheels) : numWheels(numWheels) {}

    int get_num_wheels() const { return numWheels; }

    };

    #endif // FILE_H

    // file1.cpp

    #include "file.h"

    #include <iostream>

    extern Car c;

    static int numWheels = c.get_num_wheels();

    int main() {

    std::cout << numWheels  << std::endl;

    }

    // file2.cpp

    #include "file.h"

    Car get_default_car() { return Car(6); }

    Car c = get_default_car();

    运行结果

    1. 即使顺序不对,是不是应该返回4?而不是0?

      如果g++ -std=c++11 -o test file1.cpp file2.cpp,那么结果是0.即调用c.get_num_wheels()时,对象c中的数据成员还没有被初始化。

    如果g++ -std=c++11 -o test  file2.cpp file1.cpp,那么结果是6.结果和编译顺序相关了。

    CS

    The global object c is initialized before execution of main() begins, so by the time get_num_wheels() is called, c is guaranteed to have already been dynamically initialized.

    将调用放到main中,此时外部全局对象已经完成初始化。

    // file.h

    #ifndef FILE_H

    #define FILE_H

    class Car {

    int numWheels;

    public:

    Car() : numWheels(4) {}

    explicit Car(int numWheels) : numWheels(numWheels) {}

    int get_num_wheels() const { return numWheels; }

    };

    #endif // FILE_H

    // file1.cpp

    #include "file.h"

    #include <iostream>

    int &get_num_wheels() {

    extern Car c;

    static int numWheels = c.get_num_wheels();

    return numWheels;

    }

    int main() {

    std::cout << get_num_wheels() << std::endl;

    }

    // file2.cpp

    #include "file.h"

    Car get_default_car() { return Car(6); }

    Car c = get_default_car(); 

    Car c是不是应该默认初始化,然后是赋值操作?

    运行环境中,Car c直接由explicit构造。不是先默认初始化,然后赋值。Car c = get_default_car(); 这个直接由explicit构造函数构造了。

    DCL57 Do not let exceptions escape from destructors or deallocation functions

    However, the C++ Standard, [except.handle], paragraph 15 [ISO/IEC 14882-2014], in part, states

    the following:

    The currently handled exception is rethrown if control reaches the end of a handler of

    the function-try-block of a constructor or destructor.

    C++标准说异常会如果能到达构造、析构函数的最后,那么会重新抛出。

    以下假如Bad类在析构时可能会抛出异常,Bad的代码又改不了,那么我们的类怎么修改这个安全风险。

     NCE

    class SomeClass {

    Bad bad_member;

    public:

    ~SomeClass() {

    try {

    // ...

    } catch(...) {

    // Handle the exception thrown from the Bad destructor.

        }

    }

    };

    CS

    class SomeClass {

    Bad bad_member;

    public:

    ~SomeClass() {

    try {

    // ...

    } catch(...) {

    // Catch exceptions thrown from noncompliant destructors of

    // member objects or base class subobjects.

    // NOTE: Flowing off the end of a destructor function-try-block

          // causes the caught exception to be implicitly rethrown, but

     // an explicit return statement will prevent that from

          // happening.

    return;

    }

    }

    };

    DCL59 Do not define an unnamed namespace in a header file

    在某头文件中定义了匿名的命名空间,且包含变量或函数。那么包含这个头文件的编译单元中,将各自生成这些变量或函数的实例。

    3 Expressions (EXP)

    EXP57 Do not cast or delete pointers to incomplete classes

    前向声明中会引用不完全类型的对象,但是要注意:

    不要尝试删除指向不完整类类型的对象的指针;

    不要尝试转换指向不完整类类型的对象的指针;

    If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.

    NCE

    class Handle {

    class Body *impl; // Declaration of a pointer to an incomplete class

    public:

    ~Handle() { delete impl; } // Deletion of pointer to an incomplete class

    // ...

    };

    CS

    In this compliant solution, the deletion of impl is moved to a part of the code where Body is

    defined.

    如果有前向声明,且有释放不完整类,那么释放的定义在不完整类定义的后面。

    class Handle {

    class Body *impl; // Declaration of a pointer to an incomplete class

    public:

    ~Handle();

    // ...

    };

    // Elsewhere

    class Body { /* ... */ };

    Handle::~Handle() {

    delete impl;

    }

    6 Characters and Strings (STR)

    STR51Do not attempt to create a std::string from a null pointer

    不要用空指针创建string

    NCE

    #include <cstdlib>

    #include <string>

    void f() {

    std::string tmp(std::getenv("TMP"));

    if (!tmp.empty()) {

    // ...

    }

    CS

    #include <cstdlib>

    #include <string>

    void f() {

    const char *tmpPtrVal = std::getenv("TMP");

    std::string tmp(tmpPtrVal ? tmpPtrVal : "");

    if (!tmp.empty()) {

    // ...

    }

    }

    STR52 Use valid references, pointers, and iterators to reference elements of a basic_string

    vector insert一样,指针可能因为扩容而变更。

    NCE

    #include <string>

    void f(const std::string &input) {

    std::string email;

    // Copy input into email converting ";" to " "

    std::string::iterator loc = email.begin();

    for (auto i = input.begin(), e = input.end(); i != e; ++i, ++loc) {

    email.insert(loc, *i != ';' ? *i : ' ');

    }

    }

    CS

    #include <string>

    void f(const std::string &input) {

    std::string email;

    // Copy input into email converting ";" to " "

    std::string::iterator loc = email.begin();

    for (auto i = input.begin(), e = input.end(); i != e; ++i, ++loc) {

    loc = email.insert(loc, *i != ';' ? *i : ' ');

    }

    }

    STR53 Range check element access 

    下标引用需要检查范围

    NCE

    #include <string>

    extern std::size_t get_index();

    void f() {

    std::string s("01234567");

    s[get_index()] = '1';

    }

    CS

    #include <stdexcept>

    #include <string>

    extern std::size_t get_index();

    void f() {

    std::string s("01234567");

    try {

    s.at(get_index()) = '1';

    } catch (std::out_of_range &) {

    // Handle error

    }

    }

    7 Memory Management (MEM)

    MEM50 Do not access freed memory

    局部变量在出if释放了

    NCE

    #include <iostream>

    #include <memory>

    #include <cstring>

    int main(int argc, const char *argv[]) {

    const char *s = "";

    if (argc > 1) {

    enum { BufferSize = 32 };

    try {

    std::unique_ptr<char[]> buff(new char[BufferSize]);

        std::memset(buff.get(), 0, BufferSize); // ...

    s = std::strncpy(buff.get(), argv[1], BufferSize - 1);

    } catch (std::bad_alloc &) {

    // Handle error

    }

      }

    std::cout << s << std::endl;

    }

    CE

    #include <iostream>

    #include <memory>

    #include <cstring>

    int main(int argc, const char *argv[]) {

    std::unique_ptr<char[]> buff;

    const char *s = "";

    if (argc > 1) {

    enum { BufferSize = 32 };

    try {

    buff.reset(new char[BufferSize]);

    std::memset(buff.get(), 0, BufferSize); // ...

    s = std::strncpy(buff.get(), argv[1], BufferSize - 1);

    } catch (std::bad_alloc &) {

           // Handle error

    }

    }

    std::cout << s << std::endl;

    }

    9 Exceptions and Error Handling (ERR)

    ERR51 Handle all exceptions

    CS

    #include <thread>

    void throwing_func() noexcept(false);

    void thread_start(void) {

    try {

    throwing_func();

    } catch (...) {

    // Handle error

    }

    }

    void f() {

    std::thread t(thread_start);

    t.join();

    }

    ERR53 Do not reference base classes or class data members

    When an exception is caught by a function-try-block handler in a constructor, any fully

    constructed base classes and class members of the object are destroyed prior to entering the

    handler.

    构造函数用try-block处理异常之前,类中成员对象已经销毁了。异常处理函数中不要操作这些数据成员。

    NCE

    #include <string>

    class C {

    std::string str;

    public:

    C(const std::string &s) try : str(s) {

    // ...

    } catch (...) {

    if (!str.empty()) {

    // ...

    }

    }

    };

    ERR54 Catch handlers should order their parameter types from most derived to least derived

    CS由深到浅

    class B {};

    class D : public B {};

    void f() {

    try {

    // ...

    } catch (D &d) {

    // ...

    } catch (B &b) {

    // ...

    }

    }

    10 Object Oriented Programming (OOP)

    OOP50 Do not invoke virtual functions from constructors or destructors

    禁止在构造函数或析构函数中使用虚函数,在构造期间尝试从基类中调用子类的函数有风险的。此时,子类还没有完成初始化。

    OOP51 Do not slice derived objects

    子类继续父类,子类可能增加其他变量。如果以值的方式赋值或拷贝子类对象给父类对象,则附加的信息丢失。禁止使用子类对象初始化一个父类对象,除非通过引用,指针。

    NCE

    #include <iostream>

    #include <string>

    class Employee {

    std::string name;

    protected:

    virtual void print(std::ostream &os) const {

    os << "Employee: " << get_name() << std::endl;

    }

    public:

    Employee(const std::string &name) : name(name) {}

    const std::string &get_name() const { return name; }

    friend std::ostream &operator<< (std::ostream &os, const Employee &e) {

    e.print(os);

    return os;

    }

    };

    class Manager : public Employee {

    Employee assistant;

    protected:

    void print(std::ostream &os) const override {

    os << "Manager: " << get_name() << std::endl;

    os << "Assistant: " << std::endl << " " << get_assistant() << std::endl;

    }

    public:

    Manager(const std::string &name, const Employee &assistant) :

      Employee(name), assistant(assistant) {}

    const Employee &get_assistant() const { return assistant; }

    };

    void f(Employee e) {

    std::cout << e;

    }

    int main() {

    Employee coder("Joe Smith");

    Employee typist("Bill Jones");

    Manager designer("Jane Doe", typist);

    f(coder);

    f(typist);

    f(designer);

    }

    CE

    // Remainder of code unchanged...

    void f(const Employee *e) {

    if (e) {

    std::cout << *e;

    }

    }

    int main() {

    Employee coder("Joe Smith");

    Employee typist("Bill Jones");

    Manager designer("Jane Doe", typist);

    f(&coder);

    f(&typist);

    f(&designer);

    }

    OOP52 Do not delete a polymorphic object without a virtual destructor

    当父类没有定义虚析构函数时,尝试使用指向父类的指针删除子类对象将导致未定义行为。

    11 Concurrency (CON)

    CON50-CPP. Do not destroy a mutex while it is locked

    std::mutex m;该对象需要在需要它的周期内不能被销毁。

    以下情形中对象m,在退出start_threads后被销毁,此时线程处理函数do_work可能还需要用,所以存在风险。

    #include <mutex>

    #include <thread>

    const size_t maxThreads = 10;

    void do_work(size_t i, std::mutex *pm) {

    std::lock_guard<std::mutex> lk(*pm); // Access data protected by the lock.

    }

    void start_threads() {

    std::thread threads[maxThreads];

    std::mutex m;

    for (size_t i = 0; i < maxThreads; ++i) {

           threads[i] = std::thread(do_work, i, &m);

       }

    }

    解决方法:扩大mutex对象的作用域;使用join,让线程处理完成后再回到调用处。

    CON51-Ensure actively held locks are released on exceptional conditions

    如果在lock()unlock()之间出现异常,异常处理中要释放锁。

    另外推荐用RAII封装的lock_guardunique_lock用一个互斥对象来初始化。

    CON52-CPP. Prevent data races when accessing bit-fields from multiple threads

    同一字节的不同bit位被多线程访问需要加锁保护。

    但以下一个结构体中两个不同成员,可以分别在两个线程中独立访问而不用加锁。

    struct MultiThreadedFlags {

        unsigned char flag1;

    unsigned char flag2;

    };

    MultiThreadedFlags flags;

    void thread1() {

       flags.flag1 = 1;

    }

    void thread2() {

    flags.flag2 = 2;

    }

    CON53-CPP. Avoid deadlock by locking in a predefined order

    两个互斥对象如何避免死锁

    方法一:每个线程按照顺序加锁

    比如std::mutex m1; std::mutex m2

    每个线程都用先上锁m1,再上锁m2的顺序。

    std::lock_guard<std::mutex> firstLock(m1);

    std::lock_guard<std::mutex> firstLock(m2);

    方法二:unique_lock + lock()

    // Create lock objects but defer locking them until later. std::unique_lock<std::mutex> lk1( from->balanceMutex,std::defer_lock);

    std::unique_lock<std::mutex> lk2( to->balanceMutex, std::defer_lock); // Lock both of the lock objects simultaneously.

    std::lock(lk1, lk2);

    CON54-CPP.Wrap functions that can spuriously wake up in a loop

    对于std::condition_variable类的waitwait_forwait_until,可能受系统伪激活的影响。

    所以对于wait的条件,需要用while循环来检查,而不能用单次if来判断。

    while (until_finish()) { // Predicate does not hold.

    condition.wait(lk);

     }

    或者用lambda表达式

    #include <condition_variable>

    #include <mutex>

    struct Node {

    void *node;

     struct Node *next;

    };

    static Node list;

    static std::mutex m;

    static std::condition_variable condition;

    void consume_list_element() {

     std::unique_lock<std::mutex> lk(m);

     condition.wait(lk, []{ return list.next; }); // Proceed when condition holds.

    }

    CON55-CPP. Preserve thread safety and liveness when using condition variables

    notify_one()时会随机唤醒一个阻塞状态的线程。在每个线程一个唯一条件变量的情况下使用。

    notify_all 唤醒所有线程。

    CON56-CPP. Do not speculatively lock a non-recursive mutex that is already owned by the calling thread

    非递归互斥类(std::mutex,std::timed_mutex,std::shared_timed_mutex)

    在本线程中,试图锁定一个已经加锁的对象,结果是不可预期的。

    NCE

    #include <mutex>

    #include <thread>

    std::mutex m;

    void do_thread_safe_work();

    void do_work() {

    while (!m.try_lock()) {

     // The lock is not owned yet, do other work while waiting. do_thread_safe_work();

    }try { // The mutex is now locked; perform work on shared resources. // ... // Release the mutex.

    }catch (...) {

    m.unlock();

    throw;

    }

    m.unlock();

    }

    void start_func() {

     std::lock_guard<std::mutex> lock(m);

     do_work();

    }

    int main() {

     std::thread t(start_func);

     do_work();

     t.join();

    }

  • 相关阅读:
    Kendo
    过河
    数组分组(简单dp)
    Codeforces Round #604 (Div. 2)(A-E)
    HDU1253
    HDU1026
    linux常用命令(二) --目录操作
    linux常用命令(一)--ls
    hdu 1072
    Codeforces Round #597 (Div. 2)(A-D)
  • 原文地址:https://www.cnblogs.com/sunnypoem/p/12189121.html
Copyright © 2011-2022 走看看