1e41f4b71Sopenharmony_ci# OpenHarmony C&C++ 安全编程指南 2e41f4b71Sopenharmony_ci 3e41f4b71Sopenharmony_ci本文档基于C&C++ 语言提供一些安全编程建议,用于指导开发实践。 4e41f4b71Sopenharmony_ci 5e41f4b71Sopenharmony_ci# 函数 6e41f4b71Sopenharmony_ci 7e41f4b71Sopenharmony_ci## 对所有外部数据进行合法性校验 8e41f4b71Sopenharmony_ci 9e41f4b71Sopenharmony_ci**【描述】** 10e41f4b71Sopenharmony_ci外部数据的来源包括但不限于:网络、用户输入、命令行、文件(包括程序的配置文件)、环境变量、用户态数据(对于内核程序)、进程间通信(包括管道、消息、共享内存、socket、RPC等,特别需要注意的是设备内部不同单板间通讯也属于进程间通信)、API参数、全局变量。 11e41f4b71Sopenharmony_ci 12e41f4b71Sopenharmony_ci来自程序外部的数据通常被认为是不可信的,在使用这些数据之前,需要进行合法性校验。 13e41f4b71Sopenharmony_ci如果不对这些外部数据进行校验,将可能导致不可预期的安全风险。 14e41f4b71Sopenharmony_ci 15e41f4b71Sopenharmony_ci注意:不要使用断言检查外部输入数据,断言应该用于防止不正确的程序假设,而不能用在发布版本上检查程序运行过程中发生的错误。 16e41f4b71Sopenharmony_ci 17e41f4b71Sopenharmony_ci对来自程序外部的数据要校验处理后才能使用。典型场景包括: 18e41f4b71Sopenharmony_ci 19e41f4b71Sopenharmony_ci**作为数组索引** 20e41f4b71Sopenharmony_ci将不可信的数据作为数组索引,可能导致超出数组上限,从而造成非法内存访问。 21e41f4b71Sopenharmony_ci**作为内存偏移地址** 22e41f4b71Sopenharmony_ci将不可信数据作为指针偏移访问内存,可能造成非法内存访问,并可以造成进一步的危害,如任意地址读/写。 23e41f4b71Sopenharmony_ci**作为内存分配的尺寸参数** 24e41f4b71Sopenharmony_ci使用0长度分配内存可能造成非法内存访问;未限制分配内存大小会造成过度资源消耗。 25e41f4b71Sopenharmony_ci**作为循环条件** 26e41f4b71Sopenharmony_ci将不可信数据作为循环限定条件,可能会引发缓冲区溢出、内存越界读/写、死循环等问题。 27e41f4b71Sopenharmony_ci**作为除数** 28e41f4b71Sopenharmony_ci可能产生除零错误(被零除)。 29e41f4b71Sopenharmony_ci**作为命令行参数** 30e41f4b71Sopenharmony_ci可能产生命令注入漏洞。 31e41f4b71Sopenharmony_ci**作为数据库查询语句的参数** 32e41f4b71Sopenharmony_ci可能产生SQL注入漏洞。 33e41f4b71Sopenharmony_ci**作为输入/输出格式化字符串** 34e41f4b71Sopenharmony_ci可能产生格式化字符串漏洞。 35e41f4b71Sopenharmony_ci**作为内存复制长度** 36e41f4b71Sopenharmony_ci可能造成缓冲区溢出问题。 37e41f4b71Sopenharmony_ci**作为文件路径** 38e41f4b71Sopenharmony_ci直接打开不可信路径,可能会导致目录遍历攻击,攻击者操作了无权操作的文件,使得系统被攻击者所控制。 39e41f4b71Sopenharmony_ci 40e41f4b71Sopenharmony_ci输入校验包括但不局限于: 41e41f4b71Sopenharmony_ci 42e41f4b71Sopenharmony_ci- API接口参数合法性 43e41f4b71Sopenharmony_ci- 校验数据长度 44e41f4b71Sopenharmony_ci- 校验数据范围 45e41f4b71Sopenharmony_ci- 校验数据类型和格式 46e41f4b71Sopenharmony_ci- 校验输入只包含可接受的字符(“白名单”形式),尤其需要注意一些特殊情况下的特殊字符。 47e41f4b71Sopenharmony_ci 48e41f4b71Sopenharmony_ci**外部数据校验原则** 49e41f4b71Sopenharmony_ci 50e41f4b71Sopenharmony_ci**1.信任边界** 51e41f4b71Sopenharmony_ci 52e41f4b71Sopenharmony_ci由于外部数据不可信,因此系统在运行过程中,如果数据传输与处理跨越不同的信任边界,为了防止攻击蔓延,必须对来自信任边界外的其他模块的数据进行合法性校验。 53e41f4b71Sopenharmony_ci 54e41f4b71Sopenharmony_ci(a)so(或者dll)之间 55e41f4b71Sopenharmony_ci 56e41f4b71Sopenharmony_ciso或dll作为独立的第三方模块,用于对外导出公共的api函数,供其他模块进行函数调用。so/dll无法确定上层调用者是否传递了合法参数,因此so/dll的公共函数需要检查调用者提供参数的合法性。so/dll应该设计成低耦合、高复用性,尽管有些软件的so/dll当前设计成只在本软件中使用,但仍然应该将不同的so/dll模块视为不同的信任边界。 57e41f4b71Sopenharmony_ci 58e41f4b71Sopenharmony_ci(b)进程与进程之间 59e41f4b71Sopenharmony_ci 60e41f4b71Sopenharmony_ci为防止通过高权限进程提权,进程与进程之间的IPC通信(包括单板之间的IPC通信、不同主机间的网络通信),应视为不同信任边界。 61e41f4b71Sopenharmony_ci 62e41f4b71Sopenharmony_ci(c)应用层进程与操作系统内核 63e41f4b71Sopenharmony_ci 64e41f4b71Sopenharmony_ci操作系统内核具有比应用层更高的权限,内核向应用层提供的接口,应该将来自应用层的数据作为不可信数据处理。 65e41f4b71Sopenharmony_ci 66e41f4b71Sopenharmony_ci(d)可信执行环境内外环境 67e41f4b71Sopenharmony_ci 68e41f4b71Sopenharmony_ci为防止攻击蔓延至可信执行环境,TEE、SGX等对外提供的接口,应该将来自外部的数据作为不可信数据处理。 69e41f4b71Sopenharmony_ci 70e41f4b71Sopenharmony_ci**2.外部数据校验** 71e41f4b71Sopenharmony_ci外部数据进入到本模块后,必须经过合法性校验才能使用。被校验后的合法数据,在本模块内,后续传递到内部其他子函数,不需要重复校验。 72e41f4b71Sopenharmony_ci 73e41f4b71Sopenharmony_ci**【反例】** 74e41f4b71Sopenharmony_ci函数Foo处理外部数据,由于buffer不一定是’\0’结尾, strlen 的返回值 nameLen 有可能超过 len,导致越界读取数据。 75e41f4b71Sopenharmony_ci 76e41f4b71Sopenharmony_ci```cpp 77e41f4b71Sopenharmony_civoid Foo(const unsigned char* buffer, size_t len) 78e41f4b71Sopenharmony_ci{ 79e41f4b71Sopenharmony_ci // buffer可能为空指针,不保证以'\0'结尾 80e41f4b71Sopenharmony_ci const char* s = reinterpret_cast<const char*>(buffer); 81e41f4b71Sopenharmony_ci size_t nameLen = strlen(s); 82e41f4b71Sopenharmony_ci std::string name(s, nameLen); 83e41f4b71Sopenharmony_ci Foo2(name); 84e41f4b71Sopenharmony_ci ... 85e41f4b71Sopenharmony_ci} 86e41f4b71Sopenharmony_ci``` 87e41f4b71Sopenharmony_ci 88e41f4b71Sopenharmony_ci**【正例】** 89e41f4b71Sopenharmony_ci对外部参数做合法性校验,本例中使用 strnlen 进行字符串长度计算,缓解读越界风险。 90e41f4b71Sopenharmony_ci 91e41f4b71Sopenharmony_ci```cpp 92e41f4b71Sopenharmony_civoid Foo(const unsigned char* buffer, size_t len) 93e41f4b71Sopenharmony_ci{ 94e41f4b71Sopenharmony_ci // 必须做参数合法性校验 95e41f4b71Sopenharmony_ci if (buffer == nullptr || len == 0 || len >= MAX_BUFFER_LEN) { 96e41f4b71Sopenharmony_ci ... // 错误处理 97e41f4b71Sopenharmony_ci } 98e41f4b71Sopenharmony_ci 99e41f4b71Sopenharmony_ci const char* s = reinterpret_cast<const char*>(buffer); 100e41f4b71Sopenharmony_ci size_t nameLen = strnlen(s, len); // 使用strnlen缓解读越界风险 101e41f4b71Sopenharmony_ci if (nameLen == len) { 102e41f4b71Sopenharmony_ci ... // 错误处理 103e41f4b71Sopenharmony_ci } 104e41f4b71Sopenharmony_ci std::string name(s, nameLen); 105e41f4b71Sopenharmony_ci ... 106e41f4b71Sopenharmony_ci Foo2(name); 107e41f4b71Sopenharmony_ci ... 108e41f4b71Sopenharmony_ci} 109e41f4b71Sopenharmony_ci``` 110e41f4b71Sopenharmony_ci 111e41f4b71Sopenharmony_ci```cpp 112e41f4b71Sopenharmony_cinamespace ModuleA { 113e41f4b71Sopenharmony_ci// Foo2 为模块内部函数,约定为由调用者保证参数的合法性 114e41f4b71Sopenharmony_cistatic void Foo2(const std::string& name) 115e41f4b71Sopenharmony_ci{ 116e41f4b71Sopenharmony_ci ... 117e41f4b71Sopenharmony_ci Bar(name.c_str()); // 调用MODULE_B中的函数 118e41f4b71Sopenharmony_ci} 119e41f4b71Sopenharmony_ci 120e41f4b71Sopenharmony_ci// Foo 为模块的外部接口,需要校验参数的合法性 121e41f4b71Sopenharmony_civoid Foo(const unsigned char* buffer, size_t len) 122e41f4b71Sopenharmony_ci{ 123e41f4b71Sopenharmony_ci // 检查空指针、参数合法范围等 124e41f4b71Sopenharmony_ci if (buffer == nullptr || len <= sizeof(int)) { 125e41f4b71Sopenharmony_ci // 错误处理 126e41f4b71Sopenharmony_ci ... 127e41f4b71Sopenharmony_ci } 128e41f4b71Sopenharmony_ci 129e41f4b71Sopenharmony_ci int nameLen = *(reinterpret_cast<const int*>(buffer)); // 从报文中获取name字符串长度 130e41f4b71Sopenharmony_ci // nameLen 是不可信数据,必须检查合法性 131e41f4b71Sopenharmony_ci if (nameLen <= 0 || static_cast<size_t>(nameLen) > len - sizeof(int)) { 132e41f4b71Sopenharmony_ci // 错误处理 133e41f4b71Sopenharmony_ci ... 134e41f4b71Sopenharmony_ci } 135e41f4b71Sopenharmony_ci 136e41f4b71Sopenharmony_ci std::string name(reinterpret_cast<const char*>(buffer), nameLen); 137e41f4b71Sopenharmony_ci Foo2(name); // 调用本模块内内部函数 138e41f4b71Sopenharmony_ci ... 139e41f4b71Sopenharmony_ci} 140e41f4b71Sopenharmony_ci} 141e41f4b71Sopenharmony_ci``` 142e41f4b71Sopenharmony_ci 143e41f4b71Sopenharmony_ci以下是使用C语言编写的`MODULE_B`模块中的代码: 144e41f4b71Sopenharmony_ci 145e41f4b71Sopenharmony_ci```cpp 146e41f4b71Sopenharmony_ci// Bar 为 MODULE_B 模块的公共函数, 147e41f4b71Sopenharmony_ci// 其约定为,如果参数name不为nullptr,那么必须是一个具有’\0’结尾的合法字符串并且长度大于0 148e41f4b71Sopenharmony_civoid Bar(const char* name) 149e41f4b71Sopenharmony_ci{ 150e41f4b71Sopenharmony_ci // 必须做参数合法性校验 151e41f4b71Sopenharmony_ci if (name == nullptr || name[0] == '\0') { 152e41f4b71Sopenharmony_ci // 错误处理 153e41f4b71Sopenharmony_ci ... 154e41f4b71Sopenharmony_ci } 155e41f4b71Sopenharmony_ci size_t nameLen = strlen(name); // 不需要使用strnlen 156e41f4b71Sopenharmony_ci ... 157e41f4b71Sopenharmony_ci} 158e41f4b71Sopenharmony_ci``` 159e41f4b71Sopenharmony_ci 160e41f4b71Sopenharmony_ci对于模块A来说, buffer 是外部不可信输入,必须做严格的校验,从 buffer 解析出来的 name,在解析过程中进行了合法性校验,在模块A内部属于合法数据,作为参数传递给内部子函数时不需要再做合法性校验(如果要继续对 name 内容进行解析,那么仍然必须对 name 内容进行校验)。 161e41f4b71Sopenharmony_ci如果模块A中的 name 继续跨越信任面传递给其他模块(在本例中是直接调用模块B的公共函数,也可以是通过文件、管道、网络等方式),那么对于B模块来说, name 属于不可信数据,必须做合法性校验。 162e41f4b71Sopenharmony_ci 163e41f4b71Sopenharmony_ci# 类 164e41f4b71Sopenharmony_ci 165e41f4b71Sopenharmony_ci## 类的成员变量必须显式初始化 166e41f4b71Sopenharmony_ci 167e41f4b71Sopenharmony_ci**【描述】** 168e41f4b71Sopenharmony_ci如果没有对类成员变量显示初始化,会使对象处于一种不确定状态。如果类的成员变量具有默认构造函数,那么可以不需要显式初始化。 169e41f4b71Sopenharmony_ci 170e41f4b71Sopenharmony_ci**【反例】** 171e41f4b71Sopenharmony_ci 172e41f4b71Sopenharmony_ci```cpp 173e41f4b71Sopenharmony_ciclass Message { 174e41f4b71Sopenharmony_cipublic: 175e41f4b71Sopenharmony_ci void Process() 176e41f4b71Sopenharmony_ci { 177e41f4b71Sopenharmony_ci ... 178e41f4b71Sopenharmony_ci } 179e41f4b71Sopenharmony_ci 180e41f4b71Sopenharmony_ciprivate: 181e41f4b71Sopenharmony_ci uint32_t msgId; // 不符合:成员变量没有被初始化 182e41f4b71Sopenharmony_ci size_t msgLength; // 不符合:成员变量没有被初始化 183e41f4b71Sopenharmony_ci unsigned char* msgBuffer; // 不符合:成员变量没有被初始化 184e41f4b71Sopenharmony_ci std::string someIdentifier; // 默认构造函数仅会初始化该成员 185e41f4b71Sopenharmony_ci}; 186e41f4b71Sopenharmony_ci 187e41f4b71Sopenharmony_ciMessage message; // message成员变量没有被完全初始化 188e41f4b71Sopenharmony_cimessage.Process(); // 后续使用存在隐患 189e41f4b71Sopenharmony_ci``` 190e41f4b71Sopenharmony_ci 191e41f4b71Sopenharmony_ci**【正例】** 192e41f4b71Sopenharmony_ci一种做法是在类成员变量声明时显示初始化。 193e41f4b71Sopenharmony_ci 194e41f4b71Sopenharmony_ci```cpp 195e41f4b71Sopenharmony_ciclass Message { 196e41f4b71Sopenharmony_cipublic: 197e41f4b71Sopenharmony_ci void Process() 198e41f4b71Sopenharmony_ci { 199e41f4b71Sopenharmony_ci ... 200e41f4b71Sopenharmony_ci } 201e41f4b71Sopenharmony_ci 202e41f4b71Sopenharmony_ciprivate: 203e41f4b71Sopenharmony_ci uint32_t msgId{0}; 204e41f4b71Sopenharmony_ci size_t msgLength{0}; 205e41f4b71Sopenharmony_ci unsigned char* msgBuffer{nullptr}; 206e41f4b71Sopenharmony_ci std::string someIdentifier; // 具有默认构造函数,不需要显式初始化 207e41f4b71Sopenharmony_ci}; 208e41f4b71Sopenharmony_ci``` 209e41f4b71Sopenharmony_ci 210e41f4b71Sopenharmony_ci另一种做法是使用构造函数初始化列表初始化。 211e41f4b71Sopenharmony_ci 212e41f4b71Sopenharmony_ci```cpp 213e41f4b71Sopenharmony_ciclass Message { 214e41f4b71Sopenharmony_cipublic: 215e41f4b71Sopenharmony_ci Message() : msgId(0), msgLength(0), msgBuffer(nullptr) {} 216e41f4b71Sopenharmony_ci void Process() 217e41f4b71Sopenharmony_ci { 218e41f4b71Sopenharmony_ci ... 219e41f4b71Sopenharmony_ci } 220e41f4b71Sopenharmony_ci 221e41f4b71Sopenharmony_ciprivate: 222e41f4b71Sopenharmony_ci uint32_t msgId; 223e41f4b71Sopenharmony_ci size_t msgLength; 224e41f4b71Sopenharmony_ci unsigned char* msgBuffer; 225e41f4b71Sopenharmony_ci std::string someIdentifier; // 具有默认构造函数,不需要显式初始化 226e41f4b71Sopenharmony_ci}; 227e41f4b71Sopenharmony_ci``` 228e41f4b71Sopenharmony_ci 229e41f4b71Sopenharmony_ci## 明确需要实现哪些特殊成员函数 230e41f4b71Sopenharmony_ci 231e41f4b71Sopenharmony_ci**【描述】** 232e41f4b71Sopenharmony_ci**三之法则(Rule of three):** 233e41f4b71Sopenharmony_ci若某个类需要用户定义的析构函数、用户定义的拷贝构造函或拷贝赋值操作符,则它基本三者全部都需要。 234e41f4b71Sopenharmony_ci 235e41f4b71Sopenharmony_ci```cpp 236e41f4b71Sopenharmony_ciclass Foo { 237e41f4b71Sopenharmony_cipublic: 238e41f4b71Sopenharmony_ci Foo(const char* buffer, size_t size) { Init(buffer, size); } 239e41f4b71Sopenharmony_ci Foo(const Foo& other) { Init(other.buf, other.size); } 240e41f4b71Sopenharmony_ci 241e41f4b71Sopenharmony_ci Foo& operator=(const Foo& other) 242e41f4b71Sopenharmony_ci { 243e41f4b71Sopenharmony_ci Foo tmp(other); 244e41f4b71Sopenharmony_ci Swap(tmp); 245e41f4b71Sopenharmony_ci return *this; 246e41f4b71Sopenharmony_ci } 247e41f4b71Sopenharmony_ci 248e41f4b71Sopenharmony_ci ~Foo() { delete[] buf; } 249e41f4b71Sopenharmony_ci 250e41f4b71Sopenharmony_ci void Swap(Foo& other) noexcept 251e41f4b71Sopenharmony_ci { 252e41f4b71Sopenharmony_ci using std::swap; 253e41f4b71Sopenharmony_ci swap(buf, other.buf); 254e41f4b71Sopenharmony_ci swap(size, other.size); 255e41f4b71Sopenharmony_ci } 256e41f4b71Sopenharmony_ci 257e41f4b71Sopenharmony_ciprivate: 258e41f4b71Sopenharmony_ci void Init(const char* buffer, size_t size) 259e41f4b71Sopenharmony_ci { 260e41f4b71Sopenharmony_ci this->buf = new char[size]; 261e41f4b71Sopenharmony_ci memcpy(this->buf, buffer, size); 262e41f4b71Sopenharmony_ci this->size = size; 263e41f4b71Sopenharmony_ci } 264e41f4b71Sopenharmony_ci 265e41f4b71Sopenharmony_ci char* buf; 266e41f4b71Sopenharmony_ci size_t size; 267e41f4b71Sopenharmony_ci}; 268e41f4b71Sopenharmony_ci``` 269e41f4b71Sopenharmony_ci 270e41f4b71Sopenharmony_ci如果类对某种资源进行管理,而资源句柄是非类类型的对象(裸指针、文件描述符等),则这些隐式定义的成员函数通常都不正确,其析构函数不做任何事,而拷贝构造函数/拷贝赋值操作符则进行“浅拷贝”。 271e41f4b71Sopenharmony_ci 272e41f4b71Sopenharmony_ci通过可复制句柄来管理不可复制资源的类,可能必须将其拷贝赋值和拷贝构造函数声明为私有的并且不提供其定义,或将它们定义为delete的。 273e41f4b71Sopenharmony_ci 274e41f4b71Sopenharmony_ci**五之法则(Rule of five):** 275e41f4b71Sopenharmony_ci如果定义了析构函数、拷贝构造函数或拷贝赋值操作符,会阻止移动构造函数和移动赋值操作符的隐式定义,所以任何想要移动语义的类必须声明全部五个特殊成员函数。 276e41f4b71Sopenharmony_ci 277e41f4b71Sopenharmony_ci```cpp 278e41f4b71Sopenharmony_ciclass Foo { 279e41f4b71Sopenharmony_cipublic: 280e41f4b71Sopenharmony_ci Foo(const char* buffer, size_t size) { Init(buffer, size); } 281e41f4b71Sopenharmony_ci Foo(const Foo& other) { Init(other.buf, other.size); } 282e41f4b71Sopenharmony_ci 283e41f4b71Sopenharmony_ci Foo& operator=(const Foo& other) 284e41f4b71Sopenharmony_ci { 285e41f4b71Sopenharmony_ci Foo tmp(other); 286e41f4b71Sopenharmony_ci Swap(tmp); 287e41f4b71Sopenharmony_ci return *this; 288e41f4b71Sopenharmony_ci } 289e41f4b71Sopenharmony_ci 290e41f4b71Sopenharmony_ci Foo(Foo&& other) noexcept : buf(std::move(other.buf)), size(std::move(other.size)) 291e41f4b71Sopenharmony_ci { 292e41f4b71Sopenharmony_ci other.buf = nullptr; 293e41f4b71Sopenharmony_ci other.size = 0; 294e41f4b71Sopenharmony_ci } 295e41f4b71Sopenharmony_ci 296e41f4b71Sopenharmony_ci Foo& operator=(Foo&& other) noexcept 297e41f4b71Sopenharmony_ci { 298e41f4b71Sopenharmony_ci Foo tmp(std::move(other)); 299e41f4b71Sopenharmony_ci Swap(tmp); 300e41f4b71Sopenharmony_ci return *this; 301e41f4b71Sopenharmony_ci } 302e41f4b71Sopenharmony_ci 303e41f4b71Sopenharmony_ci ~Foo() { delete[] buf; } 304e41f4b71Sopenharmony_ci 305e41f4b71Sopenharmony_ci void Swap(Foo& other) noexcept 306e41f4b71Sopenharmony_ci { 307e41f4b71Sopenharmony_ci using std::swap; 308e41f4b71Sopenharmony_ci swap(buf, other.buf); 309e41f4b71Sopenharmony_ci swap(size, other.size); 310e41f4b71Sopenharmony_ci } 311e41f4b71Sopenharmony_ci 312e41f4b71Sopenharmony_ciprivate: 313e41f4b71Sopenharmony_ci void Init(const char* buffer, size_t size) 314e41f4b71Sopenharmony_ci { 315e41f4b71Sopenharmony_ci this->buf = new char[size]; 316e41f4b71Sopenharmony_ci memcpy(this->buf, buffer, size); 317e41f4b71Sopenharmony_ci this->size = size; 318e41f4b71Sopenharmony_ci } 319e41f4b71Sopenharmony_ci 320e41f4b71Sopenharmony_ci char* buf; 321e41f4b71Sopenharmony_ci size_t size; 322e41f4b71Sopenharmony_ci}; 323e41f4b71Sopenharmony_ci``` 324e41f4b71Sopenharmony_ci 325e41f4b71Sopenharmony_ci但是如果不提供移动构造函数和移动赋值操作符通常不会发生错误,但会导致失去优化机会。 326e41f4b71Sopenharmony_ci 327e41f4b71Sopenharmony_ci**零之法则(Rule of zero):** 328e41f4b71Sopenharmony_ci如果类不需要专门处理资源的所有权,那么就不应该有自定义的析构函数、拷贝/移动构造函数或拷贝/移动赋值操作符。 329e41f4b71Sopenharmony_ci 330e41f4b71Sopenharmony_ci```cpp 331e41f4b71Sopenharmony_ciclass Foo { 332e41f4b71Sopenharmony_cipublic: 333e41f4b71Sopenharmony_ci Foo(const std::string& text) : text(text) {} 334e41f4b71Sopenharmony_ci 335e41f4b71Sopenharmony_ciprivate: 336e41f4b71Sopenharmony_ci std::string text; 337e41f4b71Sopenharmony_ci}; 338e41f4b71Sopenharmony_ci``` 339e41f4b71Sopenharmony_ci 340e41f4b71Sopenharmony_ci只要声明了拷贝构造函数、拷贝赋值操作符或析构函数,编译器将不会隐式生成移动构造函数和移动赋值操作符,导致该类的移动操作都变成了代价更高的复制操作。 341e41f4b71Sopenharmony_ci只要声明了移动构造函数或移动赋值操作符,编译器会将隐式生成的拷贝构造函数或拷贝赋值操作符定义为delete的,导致改类只能被移动、不能被复制。 342e41f4b71Sopenharmony_ci因此,只要声明了其中的任何一个函数,就应当声明其他全部函数,避免出现非预期的结果。 343e41f4b71Sopenharmony_ci 344e41f4b71Sopenharmony_ci类似地,如果基类需要定义public的虚析构函数,那么需要显示定义全部相关的特殊成员函数: 345e41f4b71Sopenharmony_ci 346e41f4b71Sopenharmony_ci```cpp 347e41f4b71Sopenharmony_ciclass Base { 348e41f4b71Sopenharmony_cipublic: 349e41f4b71Sopenharmony_ci ... 350e41f4b71Sopenharmony_ci Base(const Base&) = default; 351e41f4b71Sopenharmony_ci Base& operator=(const Base&) = default; 352e41f4b71Sopenharmony_ci Base(Base&&) = default; 353e41f4b71Sopenharmony_ci Base& operator=(Base&&) = default; 354e41f4b71Sopenharmony_ci virtual ~Base() = default; 355e41f4b71Sopenharmony_ci ... 356e41f4b71Sopenharmony_ci}; 357e41f4b71Sopenharmony_ci``` 358e41f4b71Sopenharmony_ci 359e41f4b71Sopenharmony_ci但是,如果基类声明了拷贝构造/拷贝赋值操作符,可能会发生切片,所以经常会将基类中的拷贝构造/拷贝赋值操作符显式定义为delete, 并且同时将其他的特殊成员函数也显式定义为delete: 360e41f4b71Sopenharmony_ci 361e41f4b71Sopenharmony_ci```cpp 362e41f4b71Sopenharmony_ciclass Base { 363e41f4b71Sopenharmony_cipublic: 364e41f4b71Sopenharmony_ci ... 365e41f4b71Sopenharmony_ci Base(const Base&) = delete; 366e41f4b71Sopenharmony_ci Base& operator=(const Base&) = delete; 367e41f4b71Sopenharmony_ci Base(Base&&) = delete; 368e41f4b71Sopenharmony_ci Base& operator=(Base&&) = delete; 369e41f4b71Sopenharmony_ci virtual ~Base() = default; 370e41f4b71Sopenharmony_ci ... 371e41f4b71Sopenharmony_ci}; 372e41f4b71Sopenharmony_ci``` 373e41f4b71Sopenharmony_ci 374e41f4b71Sopenharmony_ci## 基类中的拷贝构造函数、拷贝赋值操作符、移动构造函数、移动赋值操作符必须为非public函数或者为delete函数 375e41f4b71Sopenharmony_ci 376e41f4b71Sopenharmony_ci**【描述】** 377e41f4b71Sopenharmony_ci如果把一个派生类对象直接赋值给基类对象,会发生切片,只拷贝或者移动了基类部分,损害了多态行为。 378e41f4b71Sopenharmony_ci 379e41f4b71Sopenharmony_ci**【反例】** 380e41f4b71Sopenharmony_ci如下代码中,基类的拷贝构造函数和拷贝赋值操作符为default,如果派生类对象赋值给基类对象时会发生切片。 381e41f4b71Sopenharmony_ci可以将此例中的拷贝构造函数和拷贝赋值操作符声明为delete,编译器可检查出此类赋值行为。 382e41f4b71Sopenharmony_ci 383e41f4b71Sopenharmony_ci```cpp 384e41f4b71Sopenharmony_ciclass Base { 385e41f4b71Sopenharmony_cipublic: 386e41f4b71Sopenharmony_ci Base() = default; 387e41f4b71Sopenharmony_ci Base(const Base&) = default; 388e41f4b71Sopenharmony_ci Base& operator=(const Base&) = default; 389e41f4b71Sopenharmony_ci ... 390e41f4b71Sopenharmony_ci virtual void Fun() { std::cout << "Base" << std::endl; } 391e41f4b71Sopenharmony_ci}; 392e41f4b71Sopenharmony_ci 393e41f4b71Sopenharmony_ciclass Derived : public Base { 394e41f4b71Sopenharmony_ci ... 395e41f4b71Sopenharmony_ci void Fun() override { std::cout << "Derived" << std::endl; } 396e41f4b71Sopenharmony_ci}; 397e41f4b71Sopenharmony_ci 398e41f4b71Sopenharmony_civoid Foo(const Base& base) 399e41f4b71Sopenharmony_ci{ 400e41f4b71Sopenharmony_ci Base other = base; // 不符合:发生切片 401e41f4b71Sopenharmony_ci other.Fun(); // 调用的是Base类的Fun函数 402e41f4b71Sopenharmony_ci} 403e41f4b71Sopenharmony_ciDerived d; 404e41f4b71Sopenharmony_ciFoo(d); 405e41f4b71Sopenharmony_ci``` 406e41f4b71Sopenharmony_ci 407e41f4b71Sopenharmony_ci## 在移动构造函数和移动赋值操作符中必须将源对象的资源正确重置 408e41f4b71Sopenharmony_ci 409e41f4b71Sopenharmony_ci**【描述】** 410e41f4b71Sopenharmony_ci移动构造函数和移动赋值操作符将资源的所有权从一个对象移动到另外一个资源。一旦资源被移动,则应将源对象的资源正确重置。这样可以防止源对象在析构函数中释放了被移动的资源。 411e41f4b71Sopenharmony_ci 412e41f4b71Sopenharmony_ci在被移动的对象中允许保留部分非资源相关数据,但必须保证被移动的对象处于可被正常析构的状态。 413e41f4b71Sopenharmony_ci因此,当一个对象被move以后,除非该对象处于明确指定的状态,否则不要依赖已move对象的值,否则可能产生非预期行为。 414e41f4b71Sopenharmony_ci 415e41f4b71Sopenharmony_ci**【反例】** 416e41f4b71Sopenharmony_ci 417e41f4b71Sopenharmony_ci```cpp 418e41f4b71Sopenharmony_ciclass Foo { 419e41f4b71Sopenharmony_cipublic: 420e41f4b71Sopenharmony_ci ... 421e41f4b71Sopenharmony_ci Foo(Foo&& foo) noexcept : data(foo.data) 422e41f4b71Sopenharmony_ci { 423e41f4b71Sopenharmony_ci } 424e41f4b71Sopenharmony_ci 425e41f4b71Sopenharmony_ci Foo& operator=(Foo&& foo) 426e41f4b71Sopenharmony_ci { 427e41f4b71Sopenharmony_ci data = foo.data; 428e41f4b71Sopenharmony_ci return *this; 429e41f4b71Sopenharmony_ci } 430e41f4b71Sopenharmony_ci 431e41f4b71Sopenharmony_ci ~Foo() 432e41f4b71Sopenharmony_ci { 433e41f4b71Sopenharmony_ci delete[] data; 434e41f4b71Sopenharmony_ci } 435e41f4b71Sopenharmony_ci 436e41f4b71Sopenharmony_ciprivate: 437e41f4b71Sopenharmony_ci char* data = nullptr; 438e41f4b71Sopenharmony_ci}; 439e41f4b71Sopenharmony_ci``` 440e41f4b71Sopenharmony_ci 441e41f4b71Sopenharmony_ci上述Foo的移动构造函数和移动赋值操作符没有正确将源对象的资源重置,源对象析构的时候会将资源释放,导致新创建的对象中接管的资源成为无效资源。 442e41f4b71Sopenharmony_ci 443e41f4b71Sopenharmony_ci**【正例】** 444e41f4b71Sopenharmony_ci 445e41f4b71Sopenharmony_ci```cpp 446e41f4b71Sopenharmony_ciclass Foo { 447e41f4b71Sopenharmony_cipublic: 448e41f4b71Sopenharmony_ci ... 449e41f4b71Sopenharmony_ci Foo(Foo&& foo) noexcept : data(foo.data) 450e41f4b71Sopenharmony_ci { 451e41f4b71Sopenharmony_ci foo.data = nullptr; 452e41f4b71Sopenharmony_ci } 453e41f4b71Sopenharmony_ci 454e41f4b71Sopenharmony_ci Foo& operator=(Foo&& foo) 455e41f4b71Sopenharmony_ci { 456e41f4b71Sopenharmony_ci if (this == &foo) { 457e41f4b71Sopenharmony_ci return *this; 458e41f4b71Sopenharmony_ci } 459e41f4b71Sopenharmony_ci delete[] data; 460e41f4b71Sopenharmony_ci data = foo.data; 461e41f4b71Sopenharmony_ci foo.data = nullptr; 462e41f4b71Sopenharmony_ci return *this; 463e41f4b71Sopenharmony_ci } 464e41f4b71Sopenharmony_ci 465e41f4b71Sopenharmony_ci ~Foo() 466e41f4b71Sopenharmony_ci { 467e41f4b71Sopenharmony_ci delete[] data; 468e41f4b71Sopenharmony_ci } 469e41f4b71Sopenharmony_ci 470e41f4b71Sopenharmony_ciprivate: 471e41f4b71Sopenharmony_ci char* data = nullptr; 472e41f4b71Sopenharmony_ci}; 473e41f4b71Sopenharmony_ci``` 474e41f4b71Sopenharmony_ci 475e41f4b71Sopenharmony_ci此外,不要依赖已经被move对象的值。 476e41f4b71Sopenharmony_ci某些标准库std::string的实现可能对短字节做优化,在实现移动语义时可能不会修改被移动字符串的内容,导致如下代码输出不一定是预期的b, 有可能输出为ab,存在兼容性问题。 477e41f4b71Sopenharmony_ci 478e41f4b71Sopenharmony_ci```cpp 479e41f4b71Sopenharmony_cistd::string str{"a"}; 480e41f4b71Sopenharmony_cistd::string other = std::move(str); 481e41f4b71Sopenharmony_ci 482e41f4b71Sopenharmony_cistr.append(1, 'b'); 483e41f4b71Sopenharmony_cistd::cout << str << std::endl; 484e41f4b71Sopenharmony_ci``` 485e41f4b71Sopenharmony_ci 486e41f4b71Sopenharmony_ci## 通过基类指针释放派生类时,必须将基类中析构函数声明为虚函数 487e41f4b71Sopenharmony_ci 488e41f4b71Sopenharmony_ci**【描述】** 489e41f4b71Sopenharmony_ci只有基类析构函数是虚函数时,才能保证通过多态调用的时候调用到派生类的析构函数。 490e41f4b71Sopenharmony_ci如果没有将基类的析构函数声明为虚函数,当通过基类指针释放派生类时,只会调用基类的析构函数,不会调用派生类的析构函数,导致内存泄漏。 491e41f4b71Sopenharmony_ci 492e41f4b71Sopenharmony_ci**【反例】** 493e41f4b71Sopenharmony_ci没有将基类的析构函数声明为虚函数,导致了内存泄漏。 494e41f4b71Sopenharmony_ci 495e41f4b71Sopenharmony_ci```cpp 496e41f4b71Sopenharmony_ciclass Base { 497e41f4b71Sopenharmony_cipublic: 498e41f4b71Sopenharmony_ci Base() = default; 499e41f4b71Sopenharmony_ci ~Base() { std::cout << "~Base" << std::endl; } 500e41f4b71Sopenharmony_ci virtual std::string GetVersion() = 0; 501e41f4b71Sopenharmony_ci}; 502e41f4b71Sopenharmony_ciclass Derived : public Base { 503e41f4b71Sopenharmony_cipublic: 504e41f4b71Sopenharmony_ci Derived() 505e41f4b71Sopenharmony_ci { 506e41f4b71Sopenharmony_ci const size_t numberCount = 100; 507e41f4b71Sopenharmony_ci numbers = new int[numberCount]; 508e41f4b71Sopenharmony_ci } 509e41f4b71Sopenharmony_ci 510e41f4b71Sopenharmony_ci ~Derived() 511e41f4b71Sopenharmony_ci { 512e41f4b71Sopenharmony_ci delete[] numbers; 513e41f4b71Sopenharmony_ci std::cout << "~Derived" << std::endl; 514e41f4b71Sopenharmony_ci } 515e41f4b71Sopenharmony_ci 516e41f4b71Sopenharmony_ci std::string GetVersion() 517e41f4b71Sopenharmony_ci { 518e41f4b71Sopenharmony_ci return std::string("hello!"); 519e41f4b71Sopenharmony_ci } 520e41f4b71Sopenharmony_ci 521e41f4b71Sopenharmony_ciprivate: 522e41f4b71Sopenharmony_ci int* numbers; 523e41f4b71Sopenharmony_ci}; 524e41f4b71Sopenharmony_civoid Foo() 525e41f4b71Sopenharmony_ci{ 526e41f4b71Sopenharmony_ci Base* base = new Derived(); 527e41f4b71Sopenharmony_ci delete base; // 调用的是 Base 的析构函数,造成资源泄漏 528e41f4b71Sopenharmony_ci} 529e41f4b71Sopenharmony_ci``` 530e41f4b71Sopenharmony_ci 531e41f4b71Sopenharmony_ci## 对象赋值或初始化避免切片操作 532e41f4b71Sopenharmony_ci 533e41f4b71Sopenharmony_ci**【描述】** 534e41f4b71Sopenharmony_ci 535e41f4b71Sopenharmony_ci将派生类对象按值赋值给基类对象时会发生切片,损害了多态行为。 536e41f4b71Sopenharmony_ci 537e41f4b71Sopenharmony_ci如果确实需要将对象切片处理,建议定义一个显式操作完成这个功能,以避免理解错误,增加可维护性。 538e41f4b71Sopenharmony_ci 539e41f4b71Sopenharmony_ci**【反例】** 540e41f4b71Sopenharmony_ci 541e41f4b71Sopenharmony_ci```cpp 542e41f4b71Sopenharmony_ciclass Base { 543e41f4b71Sopenharmony_ci virtual void Fun(); 544e41f4b71Sopenharmony_ci}; 545e41f4b71Sopenharmony_ci 546e41f4b71Sopenharmony_ciclass Derived : public Base { 547e41f4b71Sopenharmony_ci ... 548e41f4b71Sopenharmony_ci}; 549e41f4b71Sopenharmony_civoid Foo(const Base& base) 550e41f4b71Sopenharmony_ci{ 551e41f4b71Sopenharmony_ci Base other = base; // 不符合:发生切片 552e41f4b71Sopenharmony_ci other.Fun(); // 调用的是Base类的Fun函数 553e41f4b71Sopenharmony_ci} 554e41f4b71Sopenharmony_ciDerived d; 555e41f4b71Sopenharmony_ciBase b{d}; // 不符合:仅构造了Base部分 556e41f4b71Sopenharmony_cib = d; // 不符合:仅赋值Base部分 557e41f4b71Sopenharmony_ci 558e41f4b71Sopenharmony_ciFoo(d); 559e41f4b71Sopenharmony_ci``` 560e41f4b71Sopenharmony_ci 561e41f4b71Sopenharmony_ci# 表达式与语句 562e41f4b71Sopenharmony_ci 563e41f4b71Sopenharmony_ci## 确保对象在使用之前已被初始化 564e41f4b71Sopenharmony_ci 565e41f4b71Sopenharmony_ci**【描述】** 566e41f4b71Sopenharmony_ci本条款中的“初始化”指的是通过定义时显示初始化、默认构造初始化、赋值等方式使对象拥有期望的值。 567e41f4b71Sopenharmony_ci读取一个未初始化的值时,程序可能产生未定义行为,因此需要确保对象在使用之前已被初始化。 568e41f4b71Sopenharmony_ci 569e41f4b71Sopenharmony_ci**【反例】** 570e41f4b71Sopenharmony_ci 571e41f4b71Sopenharmony_ci```cpp 572e41f4b71Sopenharmony_civoid Bar(int data); 573e41f4b71Sopenharmony_ci... 574e41f4b71Sopenharmony_civoid Foo() 575e41f4b71Sopenharmony_ci{ 576e41f4b71Sopenharmony_ci int data; 577e41f4b71Sopenharmony_ci Bar(data); // 不符合:未初始化就使用 578e41f4b71Sopenharmony_ci ... 579e41f4b71Sopenharmony_ci} 580e41f4b71Sopenharmony_ci``` 581e41f4b71Sopenharmony_ci 582e41f4b71Sopenharmony_ci如果有不同分支,要确保所有分支都得到初始化后才能使用。 583e41f4b71Sopenharmony_ci 584e41f4b71Sopenharmony_ci```cpp 585e41f4b71Sopenharmony_civoid Bar(int data); 586e41f4b71Sopenharmony_ci... 587e41f4b71Sopenharmony_civoid Foo(int condition) 588e41f4b71Sopenharmony_ci{ 589e41f4b71Sopenharmony_ci int data; 590e41f4b71Sopenharmony_ci if (condition > 0) { 591e41f4b71Sopenharmony_ci data = CUSTOMIZED_SIZE; 592e41f4b71Sopenharmony_ci } 593e41f4b71Sopenharmony_ci Bar(data); // 不符合:部分分支该值未初始化 594e41f4b71Sopenharmony_ci ... 595e41f4b71Sopenharmony_ci} 596e41f4b71Sopenharmony_ci``` 597e41f4b71Sopenharmony_ci 598e41f4b71Sopenharmony_ci**【正例】** 599e41f4b71Sopenharmony_ci 600e41f4b71Sopenharmony_ci```cpp 601e41f4b71Sopenharmony_civoid Bar(int data); 602e41f4b71Sopenharmony_ci... 603e41f4b71Sopenharmony_civoid Foo() 604e41f4b71Sopenharmony_ci{ 605e41f4b71Sopenharmony_ci int data{0}; // 符合:显示初始化 606e41f4b71Sopenharmony_ci Bar(data); 607e41f4b71Sopenharmony_ci ... 608e41f4b71Sopenharmony_ci} 609e41f4b71Sopenharmony_civoid InitData(int& data); 610e41f4b71Sopenharmony_ci... 611e41f4b71Sopenharmony_civoid Foo() 612e41f4b71Sopenharmony_ci{ 613e41f4b71Sopenharmony_ci int data; 614e41f4b71Sopenharmony_ci InitData(data); // 符合:通过函数初始化 615e41f4b71Sopenharmony_ci ... 616e41f4b71Sopenharmony_ci} 617e41f4b71Sopenharmony_cistd::string data; // 符合:默认构造函数初始化 618e41f4b71Sopenharmony_ci... 619e41f4b71Sopenharmony_ci``` 620e41f4b71Sopenharmony_ci 621e41f4b71Sopenharmony_ci## 避免使用reinterpret_cast 622e41f4b71Sopenharmony_ci 623e41f4b71Sopenharmony_ci**【描述】** 624e41f4b71Sopenharmony_ci`reinterpret_cast`用于转换不相关类型。尝试用`reinterpret_cast`将一种类型强制转换另一种类型,这破坏了类型的安全性与可靠性,是一种不安全的转换。不同类型之间尽量避免转换。 625e41f4b71Sopenharmony_ci 626e41f4b71Sopenharmony_ci## 避免使用const_cast 627e41f4b71Sopenharmony_ci 628e41f4b71Sopenharmony_ci**【描述】** 629e41f4b71Sopenharmony_ci`const_cast`用于移除对象的`const`和`volatile`性质。 630e41f4b71Sopenharmony_ci 631e41f4b71Sopenharmony_ci使用const_cast转换后的指针或者引用来修改const对象或volatile对象,程序会产生未定义行为。 632e41f4b71Sopenharmony_ci 633e41f4b71Sopenharmony_ci**【反例】** 634e41f4b71Sopenharmony_ci 635e41f4b71Sopenharmony_ci```cpp 636e41f4b71Sopenharmony_ciconst int i = 1024; 637e41f4b71Sopenharmony_ciint* p = const_cast<int*>(&i); 638e41f4b71Sopenharmony_ci*p = 2048; // 未定义行为 639e41f4b71Sopenharmony_ciclass Foo { 640e41f4b71Sopenharmony_cipublic: 641e41f4b71Sopenharmony_ci void SetValue(int v) { value = v; } 642e41f4b71Sopenharmony_ci 643e41f4b71Sopenharmony_ciprivate: 644e41f4b71Sopenharmony_ci int value{0}; 645e41f4b71Sopenharmony_ci}; 646e41f4b71Sopenharmony_ci 647e41f4b71Sopenharmony_ciint main() 648e41f4b71Sopenharmony_ci{ 649e41f4b71Sopenharmony_ci const Foo foo; 650e41f4b71Sopenharmony_ci Foo* p = const_cast<Foo*>(&foo); 651e41f4b71Sopenharmony_ci p->SetValue(2); // 未定义行为 652e41f4b71Sopenharmony_ci return 0; 653e41f4b71Sopenharmony_ci} 654e41f4b71Sopenharmony_ci``` 655e41f4b71Sopenharmony_ci 656e41f4b71Sopenharmony_ci## 确保有符号整数运算不溢出 657e41f4b71Sopenharmony_ci 658e41f4b71Sopenharmony_ci**【描述】** 659e41f4b71Sopenharmony_ci在C++标准中,有符号整数溢出会使程序产生未定义行为。 660e41f4b71Sopenharmony_ci因此,不同的实现可以自由处理有符号整数溢出。例如:在将有符号整数类型定义为模数的实现中,编译器可以不检测整数溢出。 661e41f4b71Sopenharmony_ci 662e41f4b71Sopenharmony_ci使用溢出后的数值可能导致程序缓冲区读写越界等风险。出于安全考虑,对外部数据中的有符号整数值在如下场景中使用时,需要确保运算不会导致溢出: 663e41f4b71Sopenharmony_ci 664e41f4b71Sopenharmony_ci- 指针运算的整数操作数(指针偏移值) 665e41f4b71Sopenharmony_ci- 数组索引 666e41f4b71Sopenharmony_ci- 变长数组的长度(及长度运算表达式) 667e41f4b71Sopenharmony_ci- 内存复制长度 668e41f4b71Sopenharmony_ci- 内存分配函数的参数 669e41f4b71Sopenharmony_ci- 循环判断条件 670e41f4b71Sopenharmony_ci 671e41f4b71Sopenharmony_ci在精度低于int的整数类型上进行运算时,需要考虑整数提升。程序员还需要掌握整数转换规则,包括隐式转换规则,以便设计安全的算术运算。 672e41f4b71Sopenharmony_ci 673e41f4b71Sopenharmony_ci**【反例】** 674e41f4b71Sopenharmony_ci如下代码示例中,参与减法运算的整数是外部数据,在使用前未做校验,可能出现整数溢出,进而造成后续的内存复制操作出现缓冲区溢出。 675e41f4b71Sopenharmony_ci 676e41f4b71Sopenharmony_ci```cpp 677e41f4b71Sopenharmony_ciunsigned char* content = ... // 指向报文头的指针 678e41f4b71Sopenharmony_cisize_t contentSize = ... // 缓冲区的总长度 679e41f4b71Sopenharmony_ciint totalLen = ... // 报文总长度 680e41f4b71Sopenharmony_ciint skipLen = ... // 从消息中解析出来的需要忽略的数据长度 681e41f4b71Sopenharmony_ci 682e41f4b71Sopenharmony_cistd::vector<unsigned char> dest; 683e41f4b71Sopenharmony_ci 684e41f4b71Sopenharmony_ci// 用 totalLen - skipLen 计算剩余数据长度,可能出现整数溢出 685e41f4b71Sopenharmony_cistd::copy_n(&content[skipLen], totalLen - skipLen, std::back_inserter(dest)); 686e41f4b71Sopenharmony_ci... 687e41f4b71Sopenharmony_ci``` 688e41f4b71Sopenharmony_ci 689e41f4b71Sopenharmony_ci**【正例】** 690e41f4b71Sopenharmony_ci如下代码示例中,重构为使用`size_t`类型的变量表示数据长度,并校验外部数据长度是否在合法范围内。 691e41f4b71Sopenharmony_ci 692e41f4b71Sopenharmony_ci```cpp 693e41f4b71Sopenharmony_ciunsigned char* content = ... //指向报文头的指针 694e41f4b71Sopenharmony_cisize_t contentSize = ... // 缓冲区的总长度 695e41f4b71Sopenharmony_cisize_t totalLen = ... // 报文总长度 696e41f4b71Sopenharmony_cisize_t skipLen = ... // 从消息中解析出来的需要忽略的数据长度 697e41f4b71Sopenharmony_ci 698e41f4b71Sopenharmony_ciif (skipLen >= totalLen || totalLen > contentSize) { 699e41f4b71Sopenharmony_ci ... // 错误处理 700e41f4b71Sopenharmony_ci} 701e41f4b71Sopenharmony_ci 702e41f4b71Sopenharmony_cistd::vector<unsigned char> dest; 703e41f4b71Sopenharmony_cistd::copy_n(&content[skipLen], totalLen - skipLen, std::back_inserter(dest)); 704e41f4b71Sopenharmony_ci... 705e41f4b71Sopenharmony_ci``` 706e41f4b71Sopenharmony_ci 707e41f4b71Sopenharmony_ci**【反例】** 708e41f4b71Sopenharmony_ci如下代码示例中,对来自外部数据的数值范围做了校验,但是由于second是`int`类型,而校验条件中错误的使用了`std::numeric_limits<unsigned long>::max()`进行限制,导致整数溢出。 709e41f4b71Sopenharmony_ci 710e41f4b71Sopenharmony_ci```cpp 711e41f4b71Sopenharmony_ciint second = ... // 来自外部数据 712e41f4b71Sopenharmony_ci 713e41f4b71Sopenharmony_ci // 错误的使用了unsigned long的取值范围做上限校验 714e41f4b71Sopenharmony_ciif (second < 0 || second > (std::numeric_limits<unsigned long>::max() / 1000)) { 715e41f4b71Sopenharmony_ci return -1; 716e41f4b71Sopenharmony_ci} 717e41f4b71Sopenharmony_ciint millisecond = second * 1000; // 可能出现整数溢出 718e41f4b71Sopenharmony_ci... 719e41f4b71Sopenharmony_ci``` 720e41f4b71Sopenharmony_ci 721e41f4b71Sopenharmony_ci**【正例】** 722e41f4b71Sopenharmony_ci一种改进方案是将second的类型修改为`unsigned long`类型,这种方案适用于修改了变量类型更符合业务逻辑的场景。 723e41f4b71Sopenharmony_ci 724e41f4b71Sopenharmony_ci```cpp 725e41f4b71Sopenharmony_ciunsigned long second = ... // 将类型重构为 unsigned long 类型 726e41f4b71Sopenharmony_ci 727e41f4b71Sopenharmony_ciif (second > (std::numeric_limits<unsigned long>::max() / 1000)) { 728e41f4b71Sopenharmony_ci return -1; 729e41f4b71Sopenharmony_ci} 730e41f4b71Sopenharmony_ciint millisecond = second * 1000; 731e41f4b71Sopenharmony_ci... 732e41f4b71Sopenharmony_ci``` 733e41f4b71Sopenharmony_ci 734e41f4b71Sopenharmony_ci另一种改进方案是将数值上限修改为`std::numeric_limits<int>::max()`。 735e41f4b71Sopenharmony_ci 736e41f4b71Sopenharmony_ci```cpp 737e41f4b71Sopenharmony_ciint second = ... // 来自外部数据 738e41f4b71Sopenharmony_ci 739e41f4b71Sopenharmony_ciif (second < 0 || second > (std::numeric_limits<int>::max() / 1000)) { 740e41f4b71Sopenharmony_ci return -1; 741e41f4b71Sopenharmony_ci} 742e41f4b71Sopenharmony_ciint millisecond = second * 1000; 743e41f4b71Sopenharmony_ci``` 744e41f4b71Sopenharmony_ci 745e41f4b71Sopenharmony_ci**【影响】** 746e41f4b71Sopenharmony_ci整数溢出可能导致程序缓冲区溢出以及执行任意代码。 747e41f4b71Sopenharmony_ci 748e41f4b71Sopenharmony_ci## 确保无符号整数运算不回绕 749e41f4b71Sopenharmony_ci 750e41f4b71Sopenharmony_ci**【描述】** 751e41f4b71Sopenharmony_ci无符号整数的算术运算结果可能会发生整数回绕,使用回绕后的数值其可能导致程序缓冲区读写越界等风险。 752e41f4b71Sopenharmony_ci出于安全考虑,对外部数据中的无符号整数值在如下场景中使用时,需要确保运算不会导致回绕: 753e41f4b71Sopenharmony_ci 754e41f4b71Sopenharmony_ci- 指针偏移值(指针算术运算的整数操作数) 755e41f4b71Sopenharmony_ci- 数组索引值 756e41f4b71Sopenharmony_ci- 内存拷贝的长度 757e41f4b71Sopenharmony_ci- 内存分配函数的参数 758e41f4b71Sopenharmony_ci- 循环判断条件 759e41f4b71Sopenharmony_ci 760e41f4b71Sopenharmony_ci**【反例】** 761e41f4b71Sopenharmony_ci如下代码示例中,校验下一个子报文的长度加上已处理报文的长度是否超过了整体报文的最大长度,在校验条件中的加法运算可能会出现整数回绕,造成绕过该校验的问题。 762e41f4b71Sopenharmony_ci 763e41f4b71Sopenharmony_ci```cpp 764e41f4b71Sopenharmony_cisize_t totalLen = ... // 报文的总长度 765e41f4b71Sopenharmony_cisize_t readLen = 0; // 记录已经处理报文的长度 766e41f4b71Sopenharmony_ci... 767e41f4b71Sopenharmony_cisize_t pktLen = ParsePktLen(); // 从网络报文中解析出来的下一个子报文的长度 768e41f4b71Sopenharmony_ciif (readLen + pktLen > totalLen) { // 可能出现整数回绕 769e41f4b71Sopenharmony_ci ... // 错误处理 770e41f4b71Sopenharmony_ci} 771e41f4b71Sopenharmony_ci... 772e41f4b71Sopenharmony_cireadLen += pktLen; 773e41f4b71Sopenharmony_ci... 774e41f4b71Sopenharmony_ci``` 775e41f4b71Sopenharmony_ci 776e41f4b71Sopenharmony_ci**【正例】** 777e41f4b71Sopenharmony_ci由于readLen变量记录的是已经处理报文的长度,必然会小于totalLen,因此将代码中的加法运算修改为减法运算,不会导致条件绕过。 778e41f4b71Sopenharmony_ci 779e41f4b71Sopenharmony_ci```cpp 780e41f4b71Sopenharmony_cisize_t totalLen = ... // 报文的总长度 781e41f4b71Sopenharmony_cisize_t readLen = 0; // 记录已经处理报文的长度 782e41f4b71Sopenharmony_ci... 783e41f4b71Sopenharmony_cisize_t pktLen = ParsePktLen(); // 来自网络报文 784e41f4b71Sopenharmony_ciif (pktLen > totalLen - readLen) { 785e41f4b71Sopenharmony_ci ... // 错误处理 786e41f4b71Sopenharmony_ci} 787e41f4b71Sopenharmony_ci... 788e41f4b71Sopenharmony_cireadLen += pktLen; 789e41f4b71Sopenharmony_ci... 790e41f4b71Sopenharmony_ci``` 791e41f4b71Sopenharmony_ci 792e41f4b71Sopenharmony_ci**【反例】** 793e41f4b71Sopenharmony_ci如下代码示例中,校验len合法范围的运算可能会出现整数回绕,导致条件绕过。 794e41f4b71Sopenharmony_ci 795e41f4b71Sopenharmony_ci```cpp 796e41f4b71Sopenharmony_cisize_t len = ... // 来自用户态输入 797e41f4b71Sopenharmony_ci 798e41f4b71Sopenharmony_ciif (SCTP_SIZE_MAX - len < sizeof(SctpAuthBytes)) { // 减法操作可能出现整数回绕 799e41f4b71Sopenharmony_ci ... // 错误处理 800e41f4b71Sopenharmony_ci} 801e41f4b71Sopenharmony_ci... = kmalloc(sizeof(SctpAuthBytes) + len, gfp); // 可能出现整数回绕 802e41f4b71Sopenharmony_ci... 803e41f4b71Sopenharmony_ci``` 804e41f4b71Sopenharmony_ci 805e41f4b71Sopenharmony_ci**【正例】** 806e41f4b71Sopenharmony_ci如下代码示例中,调整减法运算的位置(需要确保在编译期间减法表达式的值不回绕),避免整数回绕问题。 807e41f4b71Sopenharmony_ci 808e41f4b71Sopenharmony_ci```cpp 809e41f4b71Sopenharmony_cisize_t len = ... // 来自用户态输入 810e41f4b71Sopenharmony_ci 811e41f4b71Sopenharmony_ciif (len > SCTP_SIZE_MAX - sizeof(SctpAuthBytes)) { // 确保在编译期间减法表达式的值不翻转 812e41f4b71Sopenharmony_ci ... // 错误处理 813e41f4b71Sopenharmony_ci} 814e41f4b71Sopenharmony_ci... = kmalloc(sizeof(SctpAuthBytes) + len, gfp); 815e41f4b71Sopenharmony_ci... 816e41f4b71Sopenharmony_ci``` 817e41f4b71Sopenharmony_ci 818e41f4b71Sopenharmony_ci**【例外】** 819e41f4b71Sopenharmony_ci为正确执行程序,必要时无符号整数可能表现出模态(回绕)。建议将变量声明明确注释为支持模数行为,并且对该整数的每个操作也应明确注释为支持模数行为。 820e41f4b71Sopenharmony_ci 821e41f4b71Sopenharmony_ci**【影响】** 822e41f4b71Sopenharmony_ci整数回绕可能导致程序缓冲区溢出以及执行任意代码。 823e41f4b71Sopenharmony_ci 824e41f4b71Sopenharmony_ci## 确保除法和余数运算不会导致除零错误(被零除) 825e41f4b71Sopenharmony_ci 826e41f4b71Sopenharmony_ci**【描述】** 827e41f4b71Sopenharmony_ci整数的除法运算或取余运算的除数为0会导致程序产生未定义的行为。如果涉及到除法或者取余运算,必须确保除数不为0。 828e41f4b71Sopenharmony_ci 829e41f4b71Sopenharmony_ci在二进制浮点数算数标准ISO/IEEE Std 754-1985中规定了浮点数被零除的行为及结果,但是仍然取决于程序所运行的软硬件环境是否遵循该标准。 830e41f4b71Sopenharmony_ci因此,在做浮点数被零除的运算前,应确保软硬件环境已遵循二进制浮点数算数标准,否则仍然存在未定义行为。 831e41f4b71Sopenharmony_ci 832e41f4b71Sopenharmony_ci**【反例】** 833e41f4b71Sopenharmony_ci 834e41f4b71Sopenharmony_ci```c 835e41f4b71Sopenharmony_cisize_t a = ReadSize(); // 来自外部数据 836e41f4b71Sopenharmony_cisize_t b = 1000 / a; // 不符合:a可能是0 837e41f4b71Sopenharmony_cisize_t c = 1000 % a; // 不符合:a可能是0 838e41f4b71Sopenharmony_ci... 839e41f4b71Sopenharmony_ci``` 840e41f4b71Sopenharmony_ci 841e41f4b71Sopenharmony_ci**【正例】** 842e41f4b71Sopenharmony_ci如下代码示例中,添加a是否为0的校验,防止除零错误。 843e41f4b71Sopenharmony_ci 844e41f4b71Sopenharmony_ci```c 845e41f4b71Sopenharmony_cisize_t a = ReadSize(); // 来自外部数据 846e41f4b71Sopenharmony_ciif (a == 0) { 847e41f4b71Sopenharmony_ci ... // 错误处理 848e41f4b71Sopenharmony_ci} 849e41f4b71Sopenharmony_cisize_t b = 1000 / a; // 符合:确保a不为0 850e41f4b71Sopenharmony_cisize_t c = 1000 % a; // 符合:确保a不为0 851e41f4b71Sopenharmony_ci... 852e41f4b71Sopenharmony_ci``` 853e41f4b71Sopenharmony_ci 854e41f4b71Sopenharmony_ci**【影响】** 855e41f4b71Sopenharmony_ci除零错误可能导致拒绝服务。 856e41f4b71Sopenharmony_ci 857e41f4b71Sopenharmony_ci## 只能对无符号整数进行位运算 858e41f4b71Sopenharmony_ci 859e41f4b71Sopenharmony_ci**【描述】** 860e41f4b71Sopenharmony_ci对有符号整数进行位运算时可能产生未定义行为,本条款要求只能对无符号整数进行位运算,避免出现未定义行为。 861e41f4b71Sopenharmony_ci此外,对精度低于int类型的无符号整数进行位运算时,编译器会进行整数提升,再对提升后的整数进行位运算,因此要特别注意对于这类无符号整数的位运算,避免出现非预期的结果。 862e41f4b71Sopenharmony_ci本条款涉及的位操作符包括: 863e41f4b71Sopenharmony_ci 864e41f4b71Sopenharmony_ci- `~`(求反) 865e41f4b71Sopenharmony_ci- `&`(与) 866e41f4b71Sopenharmony_ci- `|`(或) 867e41f4b71Sopenharmony_ci- `^`(异或) 868e41f4b71Sopenharmony_ci- `>>`(右移位) 869e41f4b71Sopenharmony_ci- `<<`(左移位) 870e41f4b71Sopenharmony_ci- `&=` 871e41f4b71Sopenharmony_ci- `^=` 872e41f4b71Sopenharmony_ci- `|=` 873e41f4b71Sopenharmony_ci- `>>=` 874e41f4b71Sopenharmony_ci- `<<=` 875e41f4b71Sopenharmony_ci 876e41f4b71Sopenharmony_ci在C++20中有符号整数的移位操作具有良好的定义,可以对有符号整数进行移位运算。 877e41f4b71Sopenharmony_ci 878e41f4b71Sopenharmony_ci**【反例】** 879e41f4b71Sopenharmony_ci在C++20之前,如下代码中的右移操作`data >> 24`可以实现为算术(有符号)移位或逻辑(无符号)移位;在左移操作`value << data`中,如果value为负数或者左移后的结果超出其整数提升后类型的表示范围,会导致程序产生未定义行为。 880e41f4b71Sopenharmony_ci 881e41f4b71Sopenharmony_ci```cpp 882e41f4b71Sopenharmony_ciint32_t data = ReadByte(); 883e41f4b71Sopenharmony_ciint32_t value = data >> 24; // 对有符号整数进行右移运算,其结果是实现定义的 884e41f4b71Sopenharmony_ci 885e41f4b71Sopenharmony_ci... // 检查 data 的合法范围 886e41f4b71Sopenharmony_ci 887e41f4b71Sopenharmony_ciint32_t mask = value << data; // 对有符号整数进行左移运算,程序可能产生未定义行为 888e41f4b71Sopenharmony_ci``` 889e41f4b71Sopenharmony_ci 890e41f4b71Sopenharmony_ci**【正例】** 891e41f4b71Sopenharmony_ci 892e41f4b71Sopenharmony_ci```cpp 893e41f4b71Sopenharmony_ciuint32_t data = static_cast<uint32_t>(ReadByte()); 894e41f4b71Sopenharmony_ciuint32_t value = data >> 24; // 只对无符号整数进行位运算 895e41f4b71Sopenharmony_ci 896e41f4b71Sopenharmony_ci... // 检查 data 的合法范围 897e41f4b71Sopenharmony_ci 898e41f4b71Sopenharmony_ciuint32_t mask = value << data; 899e41f4b71Sopenharmony_ci``` 900e41f4b71Sopenharmony_ci 901e41f4b71Sopenharmony_ci对于精度低于`int`的无符号整数进行位运算,由于整数提升,其结果可能是非预期的,应将操作结果立即转换为期望的类型, 避免因整数提升而导致非预期结果。 902e41f4b71Sopenharmony_ci 903e41f4b71Sopenharmony_ci**【反例】** 904e41f4b71Sopenharmony_ci 905e41f4b71Sopenharmony_ci```cpp 906e41f4b71Sopenharmony_ciuint8_t mask = 1; 907e41f4b71Sopenharmony_ciuint8_t value = (~mask) >> 4; // 不符合:~运算的结果会包含高位数据,可能不符合预期 908e41f4b71Sopenharmony_ci``` 909e41f4b71Sopenharmony_ci 910e41f4b71Sopenharmony_ci**【正例】** 911e41f4b71Sopenharmony_ci 912e41f4b71Sopenharmony_ci```cpp 913e41f4b71Sopenharmony_ciuint8_t mask = 1; 914e41f4b71Sopenharmony_ciuint8_t value = (static_cast<uint8_t>(~mask)) >> 4; // 符合:~运算后立即转换为期望的类型 915e41f4b71Sopenharmony_ci``` 916e41f4b71Sopenharmony_ci 917e41f4b71Sopenharmony_ci**【例外】** 918e41f4b71Sopenharmony_ci 919e41f4b71Sopenharmony_ci- 作为位标志使用的有符号整数常量或枚举值,可以作为 & 和 | 操作符的操作数。 920e41f4b71Sopenharmony_ci 921e41f4b71Sopenharmony_ci```cpp 922e41f4b71Sopenharmony_ciint fd = open(fileName, O_CREAT | O_EXCL, S_IRWXU | S_IRUSR); 923e41f4b71Sopenharmony_ci``` 924e41f4b71Sopenharmony_ci 925e41f4b71Sopenharmony_ci- 一个在编译时就可以确定的有符号正整数,可以作为移位操作符的右操作数。 926e41f4b71Sopenharmony_ci 927e41f4b71Sopenharmony_ci```cpp 928e41f4b71Sopenharmony_ciconstexpr int SHIFT_BITS = 3; 929e41f4b71Sopenharmony_ci... 930e41f4b71Sopenharmony_ciuint32_t id = ...; 931e41f4b71Sopenharmony_ciuint32_t type = id >> SHIFT_BITS; 932e41f4b71Sopenharmony_ci``` 933e41f4b71Sopenharmony_ci 934e41f4b71Sopenharmony_ci# 资源管理 935e41f4b71Sopenharmony_ci 936e41f4b71Sopenharmony_ci## 外部数据作为数组索引或者内存操作长度时,需要校验其合法性 937e41f4b71Sopenharmony_ci 938e41f4b71Sopenharmony_ci**【描述】** 939e41f4b71Sopenharmony_ci外部数据作为数组索引对内存进行访问时,必须对数据的大小进行严格的校验,确保数组索引在有效范围内,否则会导致严重的错误。 940e41f4b71Sopenharmony_ci将数据复制到容量不足以容纳该数据的内存中会导致缓冲区溢出。为了防止此类错误,必须根据目标容量的大小限制被复制的数据大小,或者必须确保目标容量足够大以容纳要复制的数据。 941e41f4b71Sopenharmony_ci 942e41f4b71Sopenharmony_ci**【反例】** 943e41f4b71Sopenharmony_ci如下代码示例中,SetDevId()函数存在差一错误,当 index 等于 `DEV_NUM` 时,恰好越界写一个元素。 944e41f4b71Sopenharmony_ci 945e41f4b71Sopenharmony_ci```cpp 946e41f4b71Sopenharmony_cistruct Dev { 947e41f4b71Sopenharmony_ci int id; 948e41f4b71Sopenharmony_ci char name[MAX_NAME_LEN]; 949e41f4b71Sopenharmony_ci}; 950e41f4b71Sopenharmony_ci 951e41f4b71Sopenharmony_cistatic Dev devs[DEV_NUM]; 952e41f4b71Sopenharmony_ci 953e41f4b71Sopenharmony_ciint SetDevId(size_t index, int id) 954e41f4b71Sopenharmony_ci{ 955e41f4b71Sopenharmony_ci if (index > DEV_NUM) { // 存在差一错误 956e41f4b71Sopenharmony_ci ... // 错误处理 957e41f4b71Sopenharmony_ci } 958e41f4b71Sopenharmony_ci 959e41f4b71Sopenharmony_ci devs[index].id = id; 960e41f4b71Sopenharmony_ci return 0; 961e41f4b71Sopenharmony_ci} 962e41f4b71Sopenharmony_ci``` 963e41f4b71Sopenharmony_ci 964e41f4b71Sopenharmony_ci**【正例】** 965e41f4b71Sopenharmony_ci如下代码示例中,修改校验索引的条件,避免差一错误。 966e41f4b71Sopenharmony_ci 967e41f4b71Sopenharmony_ci```cpp 968e41f4b71Sopenharmony_cistruct Dev { 969e41f4b71Sopenharmony_ci int id; 970e41f4b71Sopenharmony_ci char name[MAX_NAME_LEN]; 971e41f4b71Sopenharmony_ci}; 972e41f4b71Sopenharmony_ci 973e41f4b71Sopenharmony_cistatic Dev devs[DEV_NUM]; 974e41f4b71Sopenharmony_ci 975e41f4b71Sopenharmony_ciint SetDevId(size_t index, int id) 976e41f4b71Sopenharmony_ci{ 977e41f4b71Sopenharmony_ci if (index >= DEV_NUM) { 978e41f4b71Sopenharmony_ci ... // 错误处理 979e41f4b71Sopenharmony_ci } 980e41f4b71Sopenharmony_ci devs[index].id = id; 981e41f4b71Sopenharmony_ci return 0; 982e41f4b71Sopenharmony_ci} 983e41f4b71Sopenharmony_ci``` 984e41f4b71Sopenharmony_ci 985e41f4b71Sopenharmony_ci**【反例】** 986e41f4b71Sopenharmony_ci外部输入的数据不一定会直接作为内存复制长度使用,还可能会间接参与内存复制操作。 987e41f4b71Sopenharmony_ci如下代码示例中,inputTable.count来自设备外部报文,虽然没有直接作为内存复制长度使用,而是作为for循环体的上限使用,间接参与了内存复制操作。由于没有校验其大小,可造成缓冲区溢出: 988e41f4b71Sopenharmony_ci 989e41f4b71Sopenharmony_ci```cpp 990e41f4b71Sopenharmony_cistruct ValueTable { 991e41f4b71Sopenharmony_ci size_t count; 992e41f4b71Sopenharmony_ci int val[MAX_NUMBERS]; 993e41f4b71Sopenharmony_ci}; 994e41f4b71Sopenharmony_ci 995e41f4b71Sopenharmony_civoid ValueTableDup(const ValueTable& inputTable) 996e41f4b71Sopenharmony_ci{ 997e41f4b71Sopenharmony_ci ValueTable outputTable = {0, {0}}; 998e41f4b71Sopenharmony_ci ... 999e41f4b71Sopenharmony_ci for (size_t i = 0; i < inputTable.count; i++) { 1000e41f4b71Sopenharmony_ci outputTable.val[i] = inputTable.val[i]; 1001e41f4b71Sopenharmony_ci } 1002e41f4b71Sopenharmony_ci ... 1003e41f4b71Sopenharmony_ci} 1004e41f4b71Sopenharmony_ci``` 1005e41f4b71Sopenharmony_ci 1006e41f4b71Sopenharmony_ci**【正例】** 1007e41f4b71Sopenharmony_ci如下代码示例中,对inputTable.count做了校验。 1008e41f4b71Sopenharmony_ci 1009e41f4b71Sopenharmony_ci```cpp 1010e41f4b71Sopenharmony_cistruct ValueTable { 1011e41f4b71Sopenharmony_ci size_t count; 1012e41f4b71Sopenharmony_ci int val[MAX_NUMBERS]; 1013e41f4b71Sopenharmony_ci}; 1014e41f4b71Sopenharmony_ci 1015e41f4b71Sopenharmony_civoid ValueTableDup(const ValueTable& inputTable) 1016e41f4b71Sopenharmony_ci{ 1017e41f4b71Sopenharmony_ci ValueTable outputTable = {0, {0}}; 1018e41f4b71Sopenharmony_ci ... 1019e41f4b71Sopenharmony_ci // 根据业务场景,对来自外部报文的循环长度inputTable.count 1020e41f4b71Sopenharmony_ci // 与outputTable.val数组大小做校验,避免造成缓冲区溢出 1021e41f4b71Sopenharmony_ci if (inputTable->count > 1022e41f4b71Sopenharmony_ci sizeof(outputTable.val) / sizeof(outputTable.val[0])) { 1023e41f4b71Sopenharmony_ci ... // 错误处理 1024e41f4b71Sopenharmony_ci } 1025e41f4b71Sopenharmony_ci for (size_t i = 0; i < inputTable.count; i++) { 1026e41f4b71Sopenharmony_ci outputTable.val[i] = inputTable.val[i]; 1027e41f4b71Sopenharmony_ci } 1028e41f4b71Sopenharmony_ci ... 1029e41f4b71Sopenharmony_ci} 1030e41f4b71Sopenharmony_ci``` 1031e41f4b71Sopenharmony_ci 1032e41f4b71Sopenharmony_ci**【影响】** 1033e41f4b71Sopenharmony_ci如果复制数据的长度是外部可控的,则复制数据的过程中可能出现缓冲区溢出,在某些情况下可以造成任意代码执行漏洞。 1034e41f4b71Sopenharmony_ci 1035e41f4b71Sopenharmony_ci## 内存申请前,必须对申请内存大小进行合法性校验 1036e41f4b71Sopenharmony_ci 1037e41f4b71Sopenharmony_ci**【描述】** 1038e41f4b71Sopenharmony_ci当申请内存大小由程序外部输入时,内存申请前,要求对申请内存大小进行合法性校验,防止申请0长度内存,或者过多地、非法地申请内存。 1039e41f4b71Sopenharmony_ci因为内存的资源是有限的,是可以被耗尽的。当申请内存的数值过大(可能一次就申请了非常大的超预期的内存;也可能循环中多次申请内存),很可能会造成非预期的资源耗尽。 1040e41f4b71Sopenharmony_ci大小不正确的参数、不当的范围检查、整数溢出或者截断都可能造成实际分配的缓冲区不符合预期。如果申请内存受攻击者控制,还可能会发生缓冲区溢出等安全问题。 1041e41f4b71Sopenharmony_ci 1042e41f4b71Sopenharmony_ci**【反例】** 1043e41f4b71Sopenharmony_ci如下代码示例中,将动态分配size大小的内存。但是未对size做合法性校验。 1044e41f4b71Sopenharmony_ci 1045e41f4b71Sopenharmony_ci```c 1046e41f4b71Sopenharmony_ci// 这里的size在传入DoSomething()函数之前还未做过合法性校验 1047e41f4b71Sopenharmony_ciint DoSomething(size_t size) 1048e41f4b71Sopenharmony_ci{ 1049e41f4b71Sopenharmony_ci ... 1050e41f4b71Sopenharmony_ci char* buffer = new char[size]; // 本函数内,size使用前未做校验 1051e41f4b71Sopenharmony_ci ... 1052e41f4b71Sopenharmony_ci delete[] buffer; 1053e41f4b71Sopenharmony_ci} 1054e41f4b71Sopenharmony_ci``` 1055e41f4b71Sopenharmony_ci 1056e41f4b71Sopenharmony_ci**【正例】** 1057e41f4b71Sopenharmony_ci如下代码示例中,动态分配size大小的内存前,进行了符合程序需要的合法性校验。 1058e41f4b71Sopenharmony_ci 1059e41f4b71Sopenharmony_ci```c 1060e41f4b71Sopenharmony_ci// 这里的size在传入DoSomething()函数之前还未做过合法性校验 1061e41f4b71Sopenharmony_ciint DoSomething(size_t size) 1062e41f4b71Sopenharmony_ci{ 1063e41f4b71Sopenharmony_ci // 本函数内,对size做合法性校验,FOO_MAX_LEN被定义为符合程序设计预期的最大内存空间 1064e41f4b71Sopenharmony_ci if (size == 0 || size > FOO_MAX_LEN) { 1065e41f4b71Sopenharmony_ci ... // 错误处理 1066e41f4b71Sopenharmony_ci } 1067e41f4b71Sopenharmony_ci char* buffer = new char[size]; 1068e41f4b71Sopenharmony_ci ... 1069e41f4b71Sopenharmony_ci delete[] buffer; 1070e41f4b71Sopenharmony_ci} 1071e41f4b71Sopenharmony_ci``` 1072e41f4b71Sopenharmony_ci 1073e41f4b71Sopenharmony_ci**【影响】** 1074e41f4b71Sopenharmony_ci如果申请内存的大小是外部可控的,可能导致资源耗尽,造成拒绝服务。 1075e41f4b71Sopenharmony_ci 1076e41f4b71Sopenharmony_ci## 在传递数组参数时,不应单独传递指针 1077e41f4b71Sopenharmony_ci 1078e41f4b71Sopenharmony_ci**【描述】** 1079e41f4b71Sopenharmony_ci当函数参数类型为数组(不是数组的引用)或者指针时,若调用者传入数组,则在参数传递时数组会退化为指针,其数组长度信息会丢失,容易引发越界读写等问题。 1080e41f4b71Sopenharmony_ci如果函数只接收固定长度的数组为参数,可以定义参数类型为数组引用或者`std::array`。 1081e41f4b71Sopenharmony_ci如果函数接受的是不带长度的指针,那么应该把长度作为另外一个参数也传递进去。 1082e41f4b71Sopenharmony_ci 1083e41f4b71Sopenharmony_ci**【反例】** 1084e41f4b71Sopenharmony_ci 1085e41f4b71Sopenharmony_ci```cpp 1086e41f4b71Sopenharmony_ciconstexpr int MAX_LEN = 1024; 1087e41f4b71Sopenharmony_ciconstexpr int SIZE = 10; 1088e41f4b71Sopenharmony_ci 1089e41f4b71Sopenharmony_civoid UseArr(int arr[]) 1090e41f4b71Sopenharmony_ci{ 1091e41f4b71Sopenharmony_ci for (int i = 0; i < MAX_LEN; i++) { 1092e41f4b71Sopenharmony_ci std::cout << arr[i] << std::endl; 1093e41f4b71Sopenharmony_ci } 1094e41f4b71Sopenharmony_ci} 1095e41f4b71Sopenharmony_ci 1096e41f4b71Sopenharmony_civoid Test() 1097e41f4b71Sopenharmony_ci{ 1098e41f4b71Sopenharmony_ci int arr[SIZE] = {0}; 1099e41f4b71Sopenharmony_ci UseArr(arr); 1100e41f4b71Sopenharmony_ci} 1101e41f4b71Sopenharmony_ci``` 1102e41f4b71Sopenharmony_ci 1103e41f4b71Sopenharmony_ci**【正例】** 1104e41f4b71Sopenharmony_ci 1105e41f4b71Sopenharmony_ci可以把指针和长度合起来做成一个类型,方便使用。例如:类似下面的简单封装: 1106e41f4b71Sopenharmony_ci 1107e41f4b71Sopenharmony_ci```cpp 1108e41f4b71Sopenharmony_citemplate <typename T> 1109e41f4b71Sopenharmony_ciclass Slice { 1110e41f4b71Sopenharmony_cipublic: 1111e41f4b71Sopenharmony_ci template <size_t N> 1112e41f4b71Sopenharmony_ci Slice(T (&arr)[N]) : data(arr), len(N) {} 1113e41f4b71Sopenharmony_ci 1114e41f4b71Sopenharmony_ci template <size_t N> 1115e41f4b71Sopenharmony_ci Slice(std::array<T, N> arr) : data(arr.data()), len(N) {} 1116e41f4b71Sopenharmony_ci 1117e41f4b71Sopenharmony_ci Slice(T* arr, size_t n) : data(arr), len(n) {} 1118e41f4b71Sopenharmony_ci ... 1119e41f4b71Sopenharmony_ci 1120e41f4b71Sopenharmony_ciprivate: 1121e41f4b71Sopenharmony_ci T* data; 1122e41f4b71Sopenharmony_ci size_t len; 1123e41f4b71Sopenharmony_ci}; 1124e41f4b71Sopenharmony_ci 1125e41f4b71Sopenharmony_civoid UseArr(Slice<int> arr) 1126e41f4b71Sopenharmony_ci{ 1127e41f4b71Sopenharmony_ci for (int i = 0; i < arr.size(); i++) { 1128e41f4b71Sopenharmony_ci std::cout << arr[i] << std::endl; 1129e41f4b71Sopenharmony_ci } 1130e41f4b71Sopenharmony_ci} 1131e41f4b71Sopenharmony_ci 1132e41f4b71Sopenharmony_ciconstexpr int SIZE = 10; 1133e41f4b71Sopenharmony_ci 1134e41f4b71Sopenharmony_civoid Test() 1135e41f4b71Sopenharmony_ci{ 1136e41f4b71Sopenharmony_ci int arr[SIZE] = {0}; 1137e41f4b71Sopenharmony_ci Slice<int> s{arr}; 1138e41f4b71Sopenharmony_ci UseArr(s); 1139e41f4b71Sopenharmony_ci} 1140e41f4b71Sopenharmony_ci``` 1141e41f4b71Sopenharmony_ci 1142e41f4b71Sopenharmony_ci如果项目允许的话,推荐使用成熟的库来做这个事情,例如C++20中的`std::span`类型。 1143e41f4b71Sopenharmony_ci 1144e41f4b71Sopenharmony_ci在不使用这些工具类的情况下,可以把指针和长度作为两个参数传递。 1145e41f4b71Sopenharmony_ci 1146e41f4b71Sopenharmony_ci```cpp 1147e41f4b71Sopenharmony_civoid UseArr(int arr[], size_t len) 1148e41f4b71Sopenharmony_ci{ 1149e41f4b71Sopenharmony_ci for (int i = 0; i < len; i++) { 1150e41f4b71Sopenharmony_ci std::cout << arr[i] << std::endl; 1151e41f4b71Sopenharmony_ci } 1152e41f4b71Sopenharmony_ci} 1153e41f4b71Sopenharmony_ci 1154e41f4b71Sopenharmony_ciconstexpr int SIZE = 10; 1155e41f4b71Sopenharmony_ci 1156e41f4b71Sopenharmony_civoid Test() 1157e41f4b71Sopenharmony_ci{ 1158e41f4b71Sopenharmony_ci int arr[SIZE] = {0}; 1159e41f4b71Sopenharmony_ci UseArr(arr, sizeof(arr)); 1160e41f4b71Sopenharmony_ci} 1161e41f4b71Sopenharmony_ci``` 1162e41f4b71Sopenharmony_ci 1163e41f4b71Sopenharmony_ci## 当lambda会逃逸出函数外面时,禁止按引用捕获局部变量 1164e41f4b71Sopenharmony_ci 1165e41f4b71Sopenharmony_ci**【描述】** 1166e41f4b71Sopenharmony_ci如果一个 lambda 不止在局部范围内使用,禁止按引用捕获局部变量,比如它被传递到了函数的外部,或者被传递给了其他线程的时候。lambda按引用捕获就是把局部对象的引用存储起来。如果 lambda 的生命周期会超过局部变量生命周期,则可能导致内存不安全。 1167e41f4b71Sopenharmony_ci 1168e41f4b71Sopenharmony_ci**【反例】** 1169e41f4b71Sopenharmony_ci 1170e41f4b71Sopenharmony_ci```cpp 1171e41f4b71Sopenharmony_civoid Foo() 1172e41f4b71Sopenharmony_ci{ 1173e41f4b71Sopenharmony_ci int local = 0; 1174e41f4b71Sopenharmony_ci // 按引用捕获 local,当函数返回后,local 不再存在,因此 Process() 的行为未定义 1175e41f4b71Sopenharmony_ci threadPool.QueueWork([&] { Process(local); }); 1176e41f4b71Sopenharmony_ci} 1177e41f4b71Sopenharmony_ci``` 1178e41f4b71Sopenharmony_ci 1179e41f4b71Sopenharmony_ci**【正例】** 1180e41f4b71Sopenharmony_ci 1181e41f4b71Sopenharmony_ci```cpp 1182e41f4b71Sopenharmony_civoid Foo() 1183e41f4b71Sopenharmony_ci{ 1184e41f4b71Sopenharmony_ci int local = 0; 1185e41f4b71Sopenharmony_ci // 按值捕获 local, 在Process() 调用过程中,local 总是有效的 1186e41f4b71Sopenharmony_ci threadPool.QueueWork([local] { Process(local); }); 1187e41f4b71Sopenharmony_ci} 1188e41f4b71Sopenharmony_ci``` 1189e41f4b71Sopenharmony_ci 1190e41f4b71Sopenharmony_ci## 指向资源句柄或描述符的变量,在资源释放后立即赋予新值 1191e41f4b71Sopenharmony_ci 1192e41f4b71Sopenharmony_ci**【描述】** 1193e41f4b71Sopenharmony_ci指向资源句柄或描述符的变量包括指针、文件描述符、socket描述符以及其他指向资源的变量。 1194e41f4b71Sopenharmony_ci以指针为例,当指针成功申请了一段内存之后,在这段内存释放以后,如果其指针未立即设置为nullptr,也未分配一个新的对象,那这个指针就是一个悬空指针。 1195e41f4b71Sopenharmony_ci如果再对悬空指针操作,可能会发生重复释放或访问已释放内存的问题,造成安全漏洞。 1196e41f4b71Sopenharmony_ci消减该漏洞的有效方法是将释放后的指针立即设置为一个确定的新值,例如设置为nullptr。对于全局性的资源句柄或描述符,在资源释放后,应该马上设置新值,以避免使用其已释放的无效值;对于只在单个函数内使用的资源句柄或描述符,应确保资源释放后其无效值不被再次使用。 1197e41f4b71Sopenharmony_ci 1198e41f4b71Sopenharmony_ci**【反例】** 1199e41f4b71Sopenharmony_ci如下代码示例中,根据消息类型处理消息,处理完后释放掉body指向的内存,但是释放后未将指针设置为nullptr。如果还有其他函数再次处理该消息结构体时,可能出现重复释放内存或访问已释放内存的问题。 1200e41f4b71Sopenharmony_ci 1201e41f4b71Sopenharmony_ci```c 1202e41f4b71Sopenharmony_ciint Fun() 1203e41f4b71Sopenharmony_ci{ 1204e41f4b71Sopenharmony_ci SomeStruct *msg = nullptr; 1205e41f4b71Sopenharmony_ci 1206e41f4b71Sopenharmony_ci ... // 使用new分配msg、msg->body的内存空间并初始化msg 1207e41f4b71Sopenharmony_ci 1208e41f4b71Sopenharmony_ci if (msg->type == MESSAGE_A) { 1209e41f4b71Sopenharmony_ci ... 1210e41f4b71Sopenharmony_ci delete msg->body; // 不符合:释放内存后,未置空 1211e41f4b71Sopenharmony_ci } 1212e41f4b71Sopenharmony_ci 1213e41f4b71Sopenharmony_ci ... 1214e41f4b71Sopenharmony_ci 1215e41f4b71Sopenharmony_ci // 将msg存入全局队列,后续可能使用已释放的body成员 1216e41f4b71Sopenharmony_ci if (!InsertMsgToQueue(msg)) { 1217e41f4b71Sopenharmony_ci delete msg->body; // 可能再次释放了body的内存 1218e41f4b71Sopenharmony_ci delete msg; 1219e41f4b71Sopenharmony_ci return -1; 1220e41f4b71Sopenharmony_ci } 1221e41f4b71Sopenharmony_ci return 0; 1222e41f4b71Sopenharmony_ci} 1223e41f4b71Sopenharmony_ci``` 1224e41f4b71Sopenharmony_ci 1225e41f4b71Sopenharmony_ci**【正例】** 1226e41f4b71Sopenharmony_ci如下代码示例中,立即对释放后的指针设置为nullptr,避免重复释放指针。 1227e41f4b71Sopenharmony_ci 1228e41f4b71Sopenharmony_ci```c 1229e41f4b71Sopenharmony_ciint Fun() 1230e41f4b71Sopenharmony_ci{ 1231e41f4b71Sopenharmony_ci SomeStruct *msg = nullptr; 1232e41f4b71Sopenharmony_ci 1233e41f4b71Sopenharmony_ci ... // 使用new分配msg、msg->body的内存空间并初始化msg 1234e41f4b71Sopenharmony_ci 1235e41f4b71Sopenharmony_ci if (msg->type == MESSAGE_A) { 1236e41f4b71Sopenharmony_ci ... 1237e41f4b71Sopenharmony_ci delete msg->body; 1238e41f4b71Sopenharmony_ci msg->body = nullptr; 1239e41f4b71Sopenharmony_ci } 1240e41f4b71Sopenharmony_ci 1241e41f4b71Sopenharmony_ci ... 1242e41f4b71Sopenharmony_ci 1243e41f4b71Sopenharmony_ci // 将msg存入全局队列 1244e41f4b71Sopenharmony_ci if (!InsertMsgToQueue(msg)) { 1245e41f4b71Sopenharmony_ci delete msg->body; // 马上离开作用域,不必赋值 nullptr 1246e41f4b71Sopenharmony_ci delete msg; // 马上离开作用域,不必赋值 nullptr 1247e41f4b71Sopenharmony_ci return -1; 1248e41f4b71Sopenharmony_ci } 1249e41f4b71Sopenharmony_ci return 0; 1250e41f4b71Sopenharmony_ci} 1251e41f4b71Sopenharmony_ci``` 1252e41f4b71Sopenharmony_ci 1253e41f4b71Sopenharmony_ci默认的内存释放函数针对空指针不执行任何动作。 1254e41f4b71Sopenharmony_ci 1255e41f4b71Sopenharmony_ci**【反例】** 1256e41f4b71Sopenharmony_ci如下代码示例中文件描述符关闭后未赋新值。 1257e41f4b71Sopenharmony_ci 1258e41f4b71Sopenharmony_ci```c 1259e41f4b71Sopenharmony_ciSOCKET s = INVALID_SOCKET; 1260e41f4b71Sopenharmony_ciint fd = -1; 1261e41f4b71Sopenharmony_ci... 1262e41f4b71Sopenharmony_ciclosesocket(s); 1263e41f4b71Sopenharmony_ci... 1264e41f4b71Sopenharmony_ciclose(fd); 1265e41f4b71Sopenharmony_ci... 1266e41f4b71Sopenharmony_ci``` 1267e41f4b71Sopenharmony_ci 1268e41f4b71Sopenharmony_ci**【正例】** 1269e41f4b71Sopenharmony_ci如下代码示例中,在资源释放后,对应的变量应该立即赋予新值。 1270e41f4b71Sopenharmony_ci 1271e41f4b71Sopenharmony_ci```c 1272e41f4b71Sopenharmony_ciSOCKET s = INVALID_SOCKET; 1273e41f4b71Sopenharmony_ciint fd = -1; 1274e41f4b71Sopenharmony_ci... 1275e41f4b71Sopenharmony_ciclosesocket(s); 1276e41f4b71Sopenharmony_cis = INVALID_SOCKET; 1277e41f4b71Sopenharmony_ci... 1278e41f4b71Sopenharmony_ciclose(fd); 1279e41f4b71Sopenharmony_cifd = -1; 1280e41f4b71Sopenharmony_ci... 1281e41f4b71Sopenharmony_ci``` 1282e41f4b71Sopenharmony_ci 1283e41f4b71Sopenharmony_ci**【影响】** 1284e41f4b71Sopenharmony_ci再次使用已经释放的内存,或者再次释放已经释放的内存,或其他使用已释放资源的行为,可能导致拒绝服务或执行任意代码。 1285e41f4b71Sopenharmony_ci 1286e41f4b71Sopenharmony_ci## new和delete配对使用,new[]和delete[]配对使用 1287e41f4b71Sopenharmony_ci 1288e41f4b71Sopenharmony_ci**【描述】** 1289e41f4b71Sopenharmony_ci使用 new 操作符创造的对象,只能使用 delete 操作符来销毁。 1290e41f4b71Sopenharmony_ci使用 new[] 创造的对象数组只能由 delete[] 操作符来销毁。 1291e41f4b71Sopenharmony_ci 1292e41f4b71Sopenharmony_ci**【反例】** 1293e41f4b71Sopenharmony_ci 1294e41f4b71Sopenharmony_ci```cpp 1295e41f4b71Sopenharmony_ciclass C { 1296e41f4b71Sopenharmony_cipublic: 1297e41f4b71Sopenharmony_ci C(size_t len) : arr(new int[len]) {} 1298e41f4b71Sopenharmony_ci ~C() 1299e41f4b71Sopenharmony_ci { 1300e41f4b71Sopenharmony_ci delete arr; // 此处应该是 delete[] arr; 1301e41f4b71Sopenharmony_ci } 1302e41f4b71Sopenharmony_ci 1303e41f4b71Sopenharmony_ciprivate: 1304e41f4b71Sopenharmony_ci int* arr; 1305e41f4b71Sopenharmony_ci}; 1306e41f4b71Sopenharmony_ci``` 1307e41f4b71Sopenharmony_ci 1308e41f4b71Sopenharmony_ci**【正例】** 1309e41f4b71Sopenharmony_ci 1310e41f4b71Sopenharmony_ci```cpp 1311e41f4b71Sopenharmony_ciclass C { 1312e41f4b71Sopenharmony_cipublic: 1313e41f4b71Sopenharmony_ci C(size_t len) : arr(new int[len]) {} 1314e41f4b71Sopenharmony_ci ~C() { delete[] arr; } 1315e41f4b71Sopenharmony_ci 1316e41f4b71Sopenharmony_ciprivate: 1317e41f4b71Sopenharmony_ci int* arr; 1318e41f4b71Sopenharmony_ci}; 1319e41f4b71Sopenharmony_ci``` 1320e41f4b71Sopenharmony_ci 1321e41f4b71Sopenharmony_ci## 自定义new/delete操作符需要配对定义,且行为与被替换的操作符一致 1322e41f4b71Sopenharmony_ci 1323e41f4b71Sopenharmony_ci**【描述】** 1324e41f4b71Sopenharmony_ci自定义操作符的时候,new 和 delete 要配对定义,new[] 和 delete[] 要配对定义。 1325e41f4b71Sopenharmony_ci自定义 new/delete 操作符的行为要与被替换的 new/delete 的行为一致。 1326e41f4b71Sopenharmony_ci 1327e41f4b71Sopenharmony_ci**【反例】** 1328e41f4b71Sopenharmony_ci 1329e41f4b71Sopenharmony_ci```cpp 1330e41f4b71Sopenharmony_ci// 如果自定义了 operator new,必须同时自定义对应的 operator delete 1331e41f4b71Sopenharmony_cistruct S { 1332e41f4b71Sopenharmony_ci static void* operator new(size_t sz) 1333e41f4b71Sopenharmony_ci { 1334e41f4b71Sopenharmony_ci ... // 自定义操作 1335e41f4b71Sopenharmony_ci return ::operator new(sz); 1336e41f4b71Sopenharmony_ci } 1337e41f4b71Sopenharmony_ci}; 1338e41f4b71Sopenharmony_ci``` 1339e41f4b71Sopenharmony_ci 1340e41f4b71Sopenharmony_ci**【正例】** 1341e41f4b71Sopenharmony_ci 1342e41f4b71Sopenharmony_ci```cpp 1343e41f4b71Sopenharmony_cistruct S { 1344e41f4b71Sopenharmony_ci static void* operator new(size_t sz) 1345e41f4b71Sopenharmony_ci { 1346e41f4b71Sopenharmony_ci ... // 自定义操作 1347e41f4b71Sopenharmony_ci return ::operator new(sz); 1348e41f4b71Sopenharmony_ci } 1349e41f4b71Sopenharmony_ci static void operator delete(void* ptr, size_t sz) 1350e41f4b71Sopenharmony_ci { 1351e41f4b71Sopenharmony_ci ... // 自定义操作 1352e41f4b71Sopenharmony_ci ::operator delete(ptr); 1353e41f4b71Sopenharmony_ci } 1354e41f4b71Sopenharmony_ci}; 1355e41f4b71Sopenharmony_ci``` 1356e41f4b71Sopenharmony_ci 1357e41f4b71Sopenharmony_ci默认的 new 操作符在内存分配失败时,会抛出`std::bad_alloc`异常,而使用了`std::nothrow`参数的 new 操作符在内存分配失败时,会返回 nullptr。 1358e41f4b71Sopenharmony_ci自定义的 new/delete 操作符要和内置的操作符行为保持一致。 1359e41f4b71Sopenharmony_ci 1360e41f4b71Sopenharmony_ci**【反例】** 1361e41f4b71Sopenharmony_ci 1362e41f4b71Sopenharmony_ci```cpp 1363e41f4b71Sopenharmony_ci// 在内存管理模块头文件中声明的函数 1364e41f4b71Sopenharmony_ciextern void* AllocMemory(size_t size); // 分配失败返回 nullptr 1365e41f4b71Sopenharmony_civoid* operator new(size_t size) 1366e41f4b71Sopenharmony_ci{ 1367e41f4b71Sopenharmony_ci return AllocMemory(size); 1368e41f4b71Sopenharmony_ci} 1369e41f4b71Sopenharmony_ci``` 1370e41f4b71Sopenharmony_ci 1371e41f4b71Sopenharmony_ci**【正例】** 1372e41f4b71Sopenharmony_ci 1373e41f4b71Sopenharmony_ci```cpp 1374e41f4b71Sopenharmony_ci// 在内存管理模块头文件中声明的函数 1375e41f4b71Sopenharmony_ciextern void* AllocMemory(size_t size); // 分配失败返回 nullptr 1376e41f4b71Sopenharmony_civoid* operator new(size_t size) 1377e41f4b71Sopenharmony_ci{ 1378e41f4b71Sopenharmony_ci void* ret = AllocMemory(size); 1379e41f4b71Sopenharmony_ci if (ret != nullptr) { 1380e41f4b71Sopenharmony_ci return ret; 1381e41f4b71Sopenharmony_ci } 1382e41f4b71Sopenharmony_ci throw std::bad_alloc(); // 分配失败抛出异常 1383e41f4b71Sopenharmony_ci} 1384e41f4b71Sopenharmony_ci 1385e41f4b71Sopenharmony_civoid* operator new(size_t size, const std::nothrow_t& tag) 1386e41f4b71Sopenharmony_ci{ 1387e41f4b71Sopenharmony_ci return AllocMemory(size); 1388e41f4b71Sopenharmony_ci} 1389e41f4b71Sopenharmony_ci``` 1390e41f4b71Sopenharmony_ci 1391e41f4b71Sopenharmony_ci# 错误处理 1392e41f4b71Sopenharmony_ci 1393e41f4b71Sopenharmony_ci## 抛异常时,抛对象本身,而不是指向对象的指针 1394e41f4b71Sopenharmony_ci 1395e41f4b71Sopenharmony_ci**【描述】** 1396e41f4b71Sopenharmony_ciC++中推荐的抛异常方式是抛对象本身,而不是指向对象的指针。 1397e41f4b71Sopenharmony_ci 1398e41f4b71Sopenharmony_ci用throw语句抛出异常的时候,会构造一个临时对象,称为“异常对象(exception object)”。这个异常对象的生命周期在C++语言中很明确:异常对象在throw时被构造;在某个捕获它的catch语句以`throw`以外的方式结束(即没有重新抛出)时,或者指向这个异常的`std::exception_ptr`对象被析构时析构。 1399e41f4b71Sopenharmony_ci 1400e41f4b71Sopenharmony_ci抛出指针,会使回收被抛出对象的责任不明确。捕获异常的地方是否有义务对该指针进行`delete`操作,取决于该对象是如何分配的(例如静态变量,或者用`new`分配),以及这个对象是否被共享了。但是指针类型本身并不能表明这个对象的生命周期以及所有权,也就无法判断是否应该`delete`。如果应该`delete`却没有做,会造成内存泄露;如果不该`delete`却做了,会造成重复释放。 1401e41f4b71Sopenharmony_ci 1402e41f4b71Sopenharmony_ci**【反例】** 1403e41f4b71Sopenharmony_ci 1404e41f4b71Sopenharmony_ci不要抛指针。 1405e41f4b71Sopenharmony_ci 1406e41f4b71Sopenharmony_ci```cpp 1407e41f4b71Sopenharmony_cistatic SomeException exc1("reason 1"); 1408e41f4b71Sopenharmony_ci 1409e41f4b71Sopenharmony_citry { 1410e41f4b71Sopenharmony_ci if (SomeFunction()) { 1411e41f4b71Sopenharmony_ci throw &exc1; // 不符合:这是静态对象的指针,不应该delete 1412e41f4b71Sopenharmony_ci } else { 1413e41f4b71Sopenharmony_ci throw new SomeException("reason 2"); // 不符合:这是动态分配的,应该delete 1414e41f4b71Sopenharmony_ci } 1415e41f4b71Sopenharmony_ci} catch (const SomeException* e) { 1416e41f4b71Sopenharmony_ci delete e; // 不符合:这里不能确定是否需要delete 1417e41f4b71Sopenharmony_ci} 1418e41f4b71Sopenharmony_ci``` 1419e41f4b71Sopenharmony_ci 1420e41f4b71Sopenharmony_ci**【正例】** 1421e41f4b71Sopenharmony_ci 1422e41f4b71Sopenharmony_ci永远抛异常对象本身。 1423e41f4b71Sopenharmony_ci 1424e41f4b71Sopenharmony_ci```cpp 1425e41f4b71Sopenharmony_citry { 1426e41f4b71Sopenharmony_ci if (SomeFunction()) { 1427e41f4b71Sopenharmony_ci throw SomeException("reason 1"); 1428e41f4b71Sopenharmony_ci } else { 1429e41f4b71Sopenharmony_ci throw SomeException("reason 2"); 1430e41f4b71Sopenharmony_ci } 1431e41f4b71Sopenharmony_ci} catch (const SomeException& e) { 1432e41f4b71Sopenharmony_ci ... // 符合:这里可以确定不需要delete 1433e41f4b71Sopenharmony_ci} 1434e41f4b71Sopenharmony_ci``` 1435e41f4b71Sopenharmony_ci 1436e41f4b71Sopenharmony_ci## 禁止从析构函数中抛出异常 1437e41f4b71Sopenharmony_ci 1438e41f4b71Sopenharmony_ci**【描述】** 1439e41f4b71Sopenharmony_ci 1440e41f4b71Sopenharmony_ci析构函数默认自带`noexcept`属性,如果析构函数抛出异常,会直接导致`std::terminate`。自C++11起,允许析构函数被标记为`noexcept(false)`,但即便如此,如果析构函数在stack unwinding的过程中被调用(例如另一个异常抛出,导致栈上的局部变量被析构),结果也是`std::terminate`,而析构函数最大的作用就是在不论正常返回还是抛出异常的情况下都能清理局部变量。因此,让析构函数抛出异常一般都是不好的。 1441e41f4b71Sopenharmony_ci 1442e41f4b71Sopenharmony_ci# 标准库 1443e41f4b71Sopenharmony_ci 1444e41f4b71Sopenharmony_ci## 禁止从空指针创建std::string 1445e41f4b71Sopenharmony_ci 1446e41f4b71Sopenharmony_ci**【描述】** 1447e41f4b71Sopenharmony_ci将空指针传递给std::string构造函数,会解引用空指针,从而导致程序产生未定义行为。 1448e41f4b71Sopenharmony_ci 1449e41f4b71Sopenharmony_ci**【反例】** 1450e41f4b71Sopenharmony_ci 1451e41f4b71Sopenharmony_ci```cpp 1452e41f4b71Sopenharmony_civoid Foo() 1453e41f4b71Sopenharmony_ci{ 1454e41f4b71Sopenharmony_ci const char* path = std::getenv("PATH"); 1455e41f4b71Sopenharmony_ci std::string str(path); // 错误:这里没有判断getenv的返回值是否为nullptr 1456e41f4b71Sopenharmony_ci std::cout << str << std::endl; 1457e41f4b71Sopenharmony_ci} 1458e41f4b71Sopenharmony_ci``` 1459e41f4b71Sopenharmony_ci 1460e41f4b71Sopenharmony_ci**【正例】** 1461e41f4b71Sopenharmony_ci 1462e41f4b71Sopenharmony_ci```cpp 1463e41f4b71Sopenharmony_civoid Foo() 1464e41f4b71Sopenharmony_ci{ 1465e41f4b71Sopenharmony_ci const char* path = std::getenv("PATH"); 1466e41f4b71Sopenharmony_ci if (path == nullptr) { 1467e41f4b71Sopenharmony_ci ... // 报告错误 1468e41f4b71Sopenharmony_ci return; 1469e41f4b71Sopenharmony_ci } 1470e41f4b71Sopenharmony_ci std::string str(path); 1471e41f4b71Sopenharmony_ci ... 1472e41f4b71Sopenharmony_ci std::cout << str << std::endl; 1473e41f4b71Sopenharmony_ci} 1474e41f4b71Sopenharmony_civoid Foo() 1475e41f4b71Sopenharmony_ci{ 1476e41f4b71Sopenharmony_ci const char* path = std::getenv("PATH"); 1477e41f4b71Sopenharmony_ci std::string str(path == nullptr ? path : ""); 1478e41f4b71Sopenharmony_ci ... // 判断空字符串 1479e41f4b71Sopenharmony_ci std::cout << str << std::endl; // 必要时判断空字符串 1480e41f4b71Sopenharmony_ci} 1481e41f4b71Sopenharmony_ci``` 1482e41f4b71Sopenharmony_ci 1483e41f4b71Sopenharmony_ci## 不要保存std::string类型的c_str和data成员函数返回的指针 1484e41f4b71Sopenharmony_ci 1485e41f4b71Sopenharmony_ci**【描述】** 1486e41f4b71Sopenharmony_ci为保证调用std::string对象的c_str()和data()成员函数返回的引用值结果的有效性,不应保存std::string类型的c_str()和data()的结果,而是在每次需要时直接调用(调用的开销会被编译器内联优化)。否则,当调用此std::string对象的修改方法修改对象后,或超出std::string对象作用域时,之前存储的指针将会失效。使用失效的指针将导致未定义行为。 1487e41f4b71Sopenharmony_ci 1488e41f4b71Sopenharmony_ci**【反例】** 1489e41f4b71Sopenharmony_ci 1490e41f4b71Sopenharmony_ci```cpp 1491e41f4b71Sopenharmony_civoid Bar(const char* data) 1492e41f4b71Sopenharmony_ci{ 1493e41f4b71Sopenharmony_ci ... 1494e41f4b71Sopenharmony_ci} 1495e41f4b71Sopenharmony_ci 1496e41f4b71Sopenharmony_civoid Foo1() 1497e41f4b71Sopenharmony_ci{ 1498e41f4b71Sopenharmony_ci std::string name{"demo"}; 1499e41f4b71Sopenharmony_ci const char* text = name.c_str(); // 表达式结束以后,name的生命周期还在,指针有效 1500e41f4b71Sopenharmony_ci 1501e41f4b71Sopenharmony_ci // 如果中间调用了std::string的非const成员函数,导致name被修改,例如operator[], begin()等, 1502e41f4b71Sopenharmony_ci // 可能会导致text的内容不可用,或者不是原来的字符串 1503e41f4b71Sopenharmony_ci name = "test"; 1504e41f4b71Sopenharmony_ci name[1] = '2'; 1505e41f4b71Sopenharmony_ci ... 1506e41f4b71Sopenharmony_ci Bar(text); // 此处text已不再指向合法内存空间 1507e41f4b71Sopenharmony_ci} 1508e41f4b71Sopenharmony_ci 1509e41f4b71Sopenharmony_civoid Foo2() 1510e41f4b71Sopenharmony_ci{ 1511e41f4b71Sopenharmony_ci std::string name{"demo"}; 1512e41f4b71Sopenharmony_ci std::string test{"test"}; 1513e41f4b71Sopenharmony_ci const char* text = (name + test).c_str(); // 表达式结束以后,+号产生的临时对象被销毁 1514e41f4b71Sopenharmony_ci ... 1515e41f4b71Sopenharmony_ci Bar(text); // 此处text已不再指向合法内存空间 1516e41f4b71Sopenharmony_ci} 1517e41f4b71Sopenharmony_ci 1518e41f4b71Sopenharmony_civoid Foo3(std::string& s) 1519e41f4b71Sopenharmony_ci{ 1520e41f4b71Sopenharmony_ci const char* data = s.data(); 1521e41f4b71Sopenharmony_ci ... 1522e41f4b71Sopenharmony_ci s.replace(0, 3, "***"); 1523e41f4b71Sopenharmony_ci ... 1524e41f4b71Sopenharmony_ci Bar(data); // 此处text已不再指向合法内存空间 1525e41f4b71Sopenharmony_ci} 1526e41f4b71Sopenharmony_ci``` 1527e41f4b71Sopenharmony_ci 1528e41f4b71Sopenharmony_ci**【正例】** 1529e41f4b71Sopenharmony_ci 1530e41f4b71Sopenharmony_ci```cpp 1531e41f4b71Sopenharmony_civoid Foo1() 1532e41f4b71Sopenharmony_ci{ 1533e41f4b71Sopenharmony_ci std::string name{"demo"}; 1534e41f4b71Sopenharmony_ci 1535e41f4b71Sopenharmony_ci name = "test"; 1536e41f4b71Sopenharmony_ci name[1] = '2'; 1537e41f4b71Sopenharmony_ci ... 1538e41f4b71Sopenharmony_ci Bar(name.c_str()); 1539e41f4b71Sopenharmony_ci} 1540e41f4b71Sopenharmony_ci 1541e41f4b71Sopenharmony_civoid Foo2() 1542e41f4b71Sopenharmony_ci{ 1543e41f4b71Sopenharmony_ci std::string name{"demo"}; 1544e41f4b71Sopenharmony_ci std::string test{"test"}; 1545e41f4b71Sopenharmony_ci name += test; 1546e41f4b71Sopenharmony_ci ... 1547e41f4b71Sopenharmony_ci Bar(name.c_str()); 1548e41f4b71Sopenharmony_ci} 1549e41f4b71Sopenharmony_ci 1550e41f4b71Sopenharmony_civoid Foo3(std::string& s) 1551e41f4b71Sopenharmony_ci{ 1552e41f4b71Sopenharmony_ci ... 1553e41f4b71Sopenharmony_ci s.replace(0, 3, "***"); 1554e41f4b71Sopenharmony_ci ... 1555e41f4b71Sopenharmony_ci Bar(s.data()); 1556e41f4b71Sopenharmony_ci} 1557e41f4b71Sopenharmony_ci``` 1558e41f4b71Sopenharmony_ci 1559e41f4b71Sopenharmony_ci**【例外】** 1560e41f4b71Sopenharmony_ci在少数对性能要求非常高的代码中,为了适配已有的只接受`const char*`类型入参的函数,可以临时保存std::string对象的c_str()方法返回的指针。但是必须严格保证std::string对象的生命周期长于所保存指针的生命周期,并且保证在所保存指针的生命周期内,std::string对象不会被修改。 1561e41f4b71Sopenharmony_ci 1562e41f4b71Sopenharmony_ci## 确保用于字符串操作的缓冲区有足够的空间容纳字符数据和结束符,并且字符串以null结束符结束 1563e41f4b71Sopenharmony_ci 1564e41f4b71Sopenharmony_ci**【描述】** 1565e41f4b71Sopenharmony_ciC风格字符串是一个连续的字符序列,由字符序列中的第一个出现的null字符终止并包含该null字符。 1566e41f4b71Sopenharmony_ci 1567e41f4b71Sopenharmony_ci复制或存储C风格字符串时,必须确保缓冲区有足够的空间容纳字符序列包括null结束符,并且字符串以null结束符结束,否则可能会导致缓冲区溢出问题: 1568e41f4b71Sopenharmony_ci 1569e41f4b71Sopenharmony_ci- 优先使用std::string表示字符串,std::string表示字符串操作更简便,更容易被正确的使用,避免由于C风格字符串使用不当而导致溢出、没有null结束符的问题。 1570e41f4b71Sopenharmony_ci- 使用C/C++标准库提供的C风格字符串操作函数时,需要确保输入的字符串以null结束符结束、不能超出字符串缓冲区的范围读写字符串、确保进存储操作后的字符串以null结束符结束。 1571e41f4b71Sopenharmony_ci 1572e41f4b71Sopenharmony_ci**【反例】** 1573e41f4b71Sopenharmony_ci 1574e41f4b71Sopenharmony_ci```cpp 1575e41f4b71Sopenharmony_cichar buf[BUFFER_SIZE]; 1576e41f4b71Sopenharmony_cistd::cin >> buf; 1577e41f4b71Sopenharmony_civoid Foo(std::istream& in) 1578e41f4b71Sopenharmony_ci{ 1579e41f4b71Sopenharmony_ci char buffer[BUFFER_SIZE]; 1580e41f4b71Sopenharmony_ci if (!in.read(buffer, sizeof(buffer))) { // 注意:in.read()不能保证'\0'结尾 1581e41f4b71Sopenharmony_ci ... // 错误处理 1582e41f4b71Sopenharmony_ci return; 1583e41f4b71Sopenharmony_ci } 1584e41f4b71Sopenharmony_ci 1585e41f4b71Sopenharmony_ci std::string str(buffer); // 不符合:字符串没有结尾的'\0' 1586e41f4b71Sopenharmony_ci ... 1587e41f4b71Sopenharmony_ci} 1588e41f4b71Sopenharmony_civoid Foo(std::istream& in) 1589e41f4b71Sopenharmony_ci{ 1590e41f4b71Sopenharmony_ci std::string s; 1591e41f4b71Sopenharmony_ci in >> s; // 不符合:没有限制待读取的长度,可能导致资源消耗或攻击 1592e41f4b71Sopenharmony_ci ... 1593e41f4b71Sopenharmony_ci} 1594e41f4b71Sopenharmony_ci``` 1595e41f4b71Sopenharmony_ci 1596e41f4b71Sopenharmony_ci**【正例】** 1597e41f4b71Sopenharmony_ci 1598e41f4b71Sopenharmony_ci```cpp 1599e41f4b71Sopenharmony_cichar buf[BUFFER_SIZE] = {0}; 1600e41f4b71Sopenharmony_cistd::cin.width(sizeof(buf) - 1); // 注意需要缓冲区长度-1,以留出字符串末尾'\0'的空间 1601e41f4b71Sopenharmony_cistd::cin >> buf; 1602e41f4b71Sopenharmony_civoid Foo(std::istream& in) 1603e41f4b71Sopenharmony_ci{ 1604e41f4b71Sopenharmony_ci char buffer[BUFFER_SIZE]; 1605e41f4b71Sopenharmony_ci 1606e41f4b71Sopenharmony_ci if (!in.read(buffer, sizeof(buffer)) { // 注意in.read()不能保证'\0'结尾 1607e41f4b71Sopenharmony_ci ... // 错误处理 1608e41f4b71Sopenharmony_ci return; 1609e41f4b71Sopenharmony_ci } 1610e41f4b71Sopenharmony_ci 1611e41f4b71Sopenharmony_ci std::string str(buffer, in.gcount()); // 让std::string构造函数,只读取指定长度的字符内容 1612e41f4b71Sopenharmony_ci ... 1613e41f4b71Sopenharmony_ci} 1614e41f4b71Sopenharmony_civoid Foo(std::istream& in) 1615e41f4b71Sopenharmony_ci{ 1616e41f4b71Sopenharmony_ci std::string s; 1617e41f4b71Sopenharmony_ci in.width(MAX_NEED_SIZE); 1618e41f4b71Sopenharmony_ci in >> s; // 符合:已经限制读取的最大长度 1619e41f4b71Sopenharmony_ci ... 1620e41f4b71Sopenharmony_ci} 1621e41f4b71Sopenharmony_ci``` 1622e41f4b71Sopenharmony_ci 1623e41f4b71Sopenharmony_ci**【影响】** 1624e41f4b71Sopenharmony_ci未对外部数据中的整数值进行限制可能导致拒绝服务,缓冲区溢出,信息泄露,或执行任意代码。 1625e41f4b71Sopenharmony_ci 1626e41f4b71Sopenharmony_ci## 禁止使用std::string存储敏感信息 1627e41f4b71Sopenharmony_ci 1628e41f4b71Sopenharmony_ci**【描述】** 1629e41f4b71Sopenharmony_cistd::string类是C++内部定义的字符串管理类,如果口令等敏感信息通过std::string进行操作,在程序运行过程中,敏感信息可能会散落到内存的各个地方,并且无法清除。 1630e41f4b71Sopenharmony_ci 1631e41f4b71Sopenharmony_ci**【反例】** 1632e41f4b71Sopenharmony_ci如下代码中,Foo函数中获取密码,保存到std::string变量password中,随后传递给VerifyPassword函数,在这个过程中,password实际上在内存中出现了两份。 1633e41f4b71Sopenharmony_ci 1634e41f4b71Sopenharmony_ci```cpp 1635e41f4b71Sopenharmony_cibool VerifyPassword(std::string password) 1636e41f4b71Sopenharmony_ci{ 1637e41f4b71Sopenharmony_ci ... 1638e41f4b71Sopenharmony_ci} 1639e41f4b71Sopenharmony_ci 1640e41f4b71Sopenharmony_civoid Foo() 1641e41f4b71Sopenharmony_ci{ 1642e41f4b71Sopenharmony_ci std::string password = GetPassword(); 1643e41f4b71Sopenharmony_ci VerifyPassword(password); 1644e41f4b71Sopenharmony_ci} 1645e41f4b71Sopenharmony_ci``` 1646e41f4b71Sopenharmony_ci 1647e41f4b71Sopenharmony_ci**【影响】** 1648e41f4b71Sopenharmony_ci未及时清理敏感信息,可能导致信息泄露。 1649e41f4b71Sopenharmony_ci 1650e41f4b71Sopenharmony_ci## 外部数据用于容器索引或迭代器时必须确保在有效范围内 1651e41f4b71Sopenharmony_ci 1652e41f4b71Sopenharmony_ci**【描述】** 1653e41f4b71Sopenharmony_ci外部数据是不可信数据,当将外部数据用于容器或数组的索引时,应确保其值在容器或数组可被访问元素的有效范围内;当将外部数据用于迭代器偏移时,应确保偏移后的迭代器值在与迭代器关联容器(从容器对象c的begin()方法创建)的[begin(), end())之间(即大于等于c.begin(),小于等于c.end())。 1654e41f4b71Sopenharmony_ci 1655e41f4b71Sopenharmony_ci对于具有at()方法的容器(如std::vector, std::set, std::map),对应索引越界或键值内容不存在时,方法将抛出异常;而其对应的operator[]出现索引越界时,将导致未定义行为;或者因键值内容不存在而构造对应键值的默认值不成功时,也将导致未定义行为。 1656e41f4b71Sopenharmony_ci 1657e41f4b71Sopenharmony_ci**【反例】** 1658e41f4b71Sopenharmony_ci 1659e41f4b71Sopenharmony_ci```cpp 1660e41f4b71Sopenharmony_ciint main() 1661e41f4b71Sopenharmony_ci{ 1662e41f4b71Sopenharmony_ci // 得到一个来自外部输入的整数 (index) 1663e41f4b71Sopenharmony_ci int index; 1664e41f4b71Sopenharmony_ci if (!(std::cin >> index)) { 1665e41f4b71Sopenharmony_ci ... // 错误处理 1666e41f4b71Sopenharmony_ci return -1; 1667e41f4b71Sopenharmony_ci } 1668e41f4b71Sopenharmony_ci 1669e41f4b71Sopenharmony_ci std::vector<char> c{'A', 'B', 'C', 'D'}; 1670e41f4b71Sopenharmony_ci 1671e41f4b71Sopenharmony_ci // 不符合:没有正确校验index的范围,溢出读取:需要确保index在容器元素的位置范围 1672e41f4b71Sopenharmony_ci std::cout << c[index] << std::endl; 1673e41f4b71Sopenharmony_ci 1674e41f4b71Sopenharmony_ci // 不符合:需要确保index在容器/数组元素的位置范围 1675e41f4b71Sopenharmony_ci for (auto pos = std::cbegin(c) + index; pos < std::cend(c); ++pos) { 1676e41f4b71Sopenharmony_ci std::cout << *pos << std::endl; 1677e41f4b71Sopenharmony_ci } 1678e41f4b71Sopenharmony_ci return 0; 1679e41f4b71Sopenharmony_ci} 1680e41f4b71Sopenharmony_civoid Foo(size_t n) 1681e41f4b71Sopenharmony_ci{ 1682e41f4b71Sopenharmony_ci std::vector<int> v{0, 1, 2, 3}; 1683e41f4b71Sopenharmony_ci 1684e41f4b71Sopenharmony_ci // n为外部的API传入的索引,可能导致越界访问 1685e41f4b71Sopenharmony_ci for_each_n(v.cbegin(), n, [](int x) { std::cout << x; }); 1686e41f4b71Sopenharmony_ci} 1687e41f4b71Sopenharmony_ci``` 1688e41f4b71Sopenharmony_ci 1689e41f4b71Sopenharmony_ci**【正例】** 1690e41f4b71Sopenharmony_ci 1691e41f4b71Sopenharmony_ci```cpp 1692e41f4b71Sopenharmony_ciint main() 1693e41f4b71Sopenharmony_ci{ 1694e41f4b71Sopenharmony_ci // 得到一个来自外部输入的整数 (index) 1695e41f4b71Sopenharmony_ci int index; 1696e41f4b71Sopenharmony_ci if (!(std::cin >> index)) { 1697e41f4b71Sopenharmony_ci ... // 错误处理 1698e41f4b71Sopenharmony_ci return -1; 1699e41f4b71Sopenharmony_ci } 1700e41f4b71Sopenharmony_ci 1701e41f4b71Sopenharmony_ci // 这里仅以std::vector来举例,std::cbegin(c)等代码也适用于std::string字符串、 1702e41f4b71Sopenharmony_ci // 和C数组(但不适应于char*变量以及char*表示的静态字符串) 1703e41f4b71Sopenharmony_ci std::vector<char> c{'A', 'B', 'C', 'D'}; 1704e41f4b71Sopenharmony_ci 1705e41f4b71Sopenharmony_ci try { 1706e41f4b71Sopenharmony_ci std::cout << c.at(index) << std::endl; // 符合:索引越界时,at函数将抛出异常 1707e41f4b71Sopenharmony_ci } catch (const std::out_of_range& e) { 1708e41f4b71Sopenharmony_ci ... // 越界异常处理 1709e41f4b71Sopenharmony_ci } 1710e41f4b71Sopenharmony_ci 1711e41f4b71Sopenharmony_ci // 后续代码必须使用检验合法的 index 进行容器元素索引或迭代器偏移 1712e41f4b71Sopenharmony_ci // 正确校验index的范围:已确保index在容器元素的位置范围 1713e41f4b71Sopenharmony_ci if (index < 0 || index >= c.size()) { 1714e41f4b71Sopenharmony_ci ... // 错误处理 1715e41f4b71Sopenharmony_ci return -1; 1716e41f4b71Sopenharmony_ci } 1717e41f4b71Sopenharmony_ci 1718e41f4b71Sopenharmony_ci std::cout << c[index] << std::endl; // 符合:已检验index的范围 1719e41f4b71Sopenharmony_ci 1720e41f4b71Sopenharmony_ci // 符合:已检验index 1721e41f4b71Sopenharmony_ci for (auto pos = std::cbegin(c) + index; pos < std::cend(c); ++pos) { 1722e41f4b71Sopenharmony_ci std::cout << *pos << std::endl; 1723e41f4b71Sopenharmony_ci } 1724e41f4b71Sopenharmony_ci return 0; 1725e41f4b71Sopenharmony_ci} 1726e41f4b71Sopenharmony_civoid Foo(size_t n) 1727e41f4b71Sopenharmony_ci{ 1728e41f4b71Sopenharmony_ci std::vector<int> v{0, 1, 2, 3}; 1729e41f4b71Sopenharmony_ci 1730e41f4b71Sopenharmony_ci // 必须确保for_each_n的迭代范围[first, first + count)有效 1731e41f4b71Sopenharmony_ci if (n > v.size()) { 1732e41f4b71Sopenharmony_ci ... // 错误处理 1733e41f4b71Sopenharmony_ci return; 1734e41f4b71Sopenharmony_ci } 1735e41f4b71Sopenharmony_ci for_each_n(v.cbegin(), n, [](int x) { std::cout << x; }); 1736e41f4b71Sopenharmony_ci} 1737e41f4b71Sopenharmony_ci``` 1738e41f4b71Sopenharmony_ci 1739e41f4b71Sopenharmony_ci## 调用格式化输入/输出函数时,使用有效的格式字符串 1740e41f4b71Sopenharmony_ci 1741e41f4b71Sopenharmony_ci**【描述】** 1742e41f4b71Sopenharmony_ci使用C风格的格式化输入/输出函数时,需要确保格式串是合法有效的,并且格式串与相应的实参类型是严格匹配的,否则会使程序产生非预期行为。 1743e41f4b71Sopenharmony_ci 1744e41f4b71Sopenharmony_ci除C风格的格式化输入/输出函数以外,C++中类似的函数也需要确保使用有效的格式串,如C++20的std::format()函数。 1745e41f4b71Sopenharmony_ci 1746e41f4b71Sopenharmony_ci对于自定义C风格的格式化函数,可以使用编译器支持的属性自动检查使用自定义格式化函数的正确性。 1747e41f4b71Sopenharmony_ci例如:GCC支持自动检测类似printf, scanf, strftime, strfmon的自定义格式化函数,参考GCC手册的Common Function Attributes: 1748e41f4b71Sopenharmony_ci 1749e41f4b71Sopenharmony_ci```c 1750e41f4b71Sopenharmony_ciextern int CustomPrintf(void* obj, const char* format, ...) 1751e41f4b71Sopenharmony_ci __attribute__ ((format (printf, 2, 3))); 1752e41f4b71Sopenharmony_ci``` 1753e41f4b71Sopenharmony_ci 1754e41f4b71Sopenharmony_ci**【反例】** 1755e41f4b71Sopenharmony_ci如下代码示例中,格式化输入一个整数到macAddr变量中,但是macAddr为unsigned char类型,而%x对应的是int类型参数,函数执行完成后会发生写越界。 1756e41f4b71Sopenharmony_ci 1757e41f4b71Sopenharmony_ci```c 1758e41f4b71Sopenharmony_ciunsigned char macAddr[6]; 1759e41f4b71Sopenharmony_ci... 1760e41f4b71Sopenharmony_ci// macStr中的数据格式为 e2:42:a4:52:1e:33 1761e41f4b71Sopenharmony_ciint ret = sscanf(macStr, "%x:%x:%x:%x:%x:%x\n", 1762e41f4b71Sopenharmony_ci &macAddr[0], &macAddr[1], 1763e41f4b71Sopenharmony_ci &macAddr[2], &macAddr[3], 1764e41f4b71Sopenharmony_ci &macAddr[4], &macAddr[5]); 1765e41f4b71Sopenharmony_ci... 1766e41f4b71Sopenharmony_ci``` 1767e41f4b71Sopenharmony_ci 1768e41f4b71Sopenharmony_ci**【正例】** 1769e41f4b71Sopenharmony_ci如下代码中,使用%hhx确保格式串与相应的实参类型严格匹配。 1770e41f4b71Sopenharmony_ci 1771e41f4b71Sopenharmony_ci```c 1772e41f4b71Sopenharmony_ciunsigned char macAddr[6]; 1773e41f4b71Sopenharmony_ci... 1774e41f4b71Sopenharmony_ci// macStr中的数据格式为 e2:42:a4:52:1e:33 1775e41f4b71Sopenharmony_ciint ret = sscanf(macStr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx\n", 1776e41f4b71Sopenharmony_ci &macAddr[0], &macAddr[1], 1777e41f4b71Sopenharmony_ci &macAddr[2], &macAddr[3], 1778e41f4b71Sopenharmony_ci &macAddr[4], &macAddr[5]); 1779e41f4b71Sopenharmony_ci... 1780e41f4b71Sopenharmony_ci``` 1781e41f4b71Sopenharmony_ci 1782e41f4b71Sopenharmony_ci注:在C++中不推荐使用sscanf, sprintf等C库函数,可以替换为:std::istringstream, std::ostringstream, std::stringstream等。 1783e41f4b71Sopenharmony_ci 1784e41f4b71Sopenharmony_ci**【影响】** 1785e41f4b71Sopenharmony_ci错误的格式串可能造成内存破坏或者程序异常终止。 1786e41f4b71Sopenharmony_ci 1787e41f4b71Sopenharmony_ci## 调用格式化输入/输出函数时,禁止format参数受外部数据控制 1788e41f4b71Sopenharmony_ci 1789e41f4b71Sopenharmony_ci**【描述】** 1790e41f4b71Sopenharmony_ci调用格式化函数时,如果format参数由外部数据提供,或由外部数据拼接而来,会造成字符串格式化漏洞。 1791e41f4b71Sopenharmony_ci以C标准库的格式化输出函数为例,当其format参数外部可控时,攻击者可以使用%n转换符向指定地址写入一个整数值、使用%x或%d转换符查看栈或寄存器内容、使用%s转换符造成进程崩溃等。 1792e41f4b71Sopenharmony_ci 1793e41f4b71Sopenharmony_ci常见格式化函数有: 1794e41f4b71Sopenharmony_ci 1795e41f4b71Sopenharmony_ci- 格式化输出函数: sprintf, vsprintf, snprintf, vsnprintf等等 1796e41f4b71Sopenharmony_ci- 格式化输入函数: sscanf, vsscanf, fscanf, vscanf等等 1797e41f4b71Sopenharmony_ci- 格式化错误消息函数: err(), verr(), errx(), verrx(), warn(), vwarn(), warnx(), vwarnx(), error(), error_at_line() 1798e41f4b71Sopenharmony_ci- 格式化日志函数: syslog(), vsyslog() 1799e41f4b71Sopenharmony_ci- C++20提供的std::format() 1800e41f4b71Sopenharmony_ci 1801e41f4b71Sopenharmony_ci调用格式化函数时,应使用常量字符串作为格式串,禁止格式串外部可控: 1802e41f4b71Sopenharmony_ci 1803e41f4b71Sopenharmony_ci```cpp 1804e41f4b71Sopenharmony_ciBox<int> v{MAX_COUNT}; 1805e41f4b71Sopenharmony_cistd::cout << std::format("{:#x}", v); 1806e41f4b71Sopenharmony_ci``` 1807e41f4b71Sopenharmony_ci 1808e41f4b71Sopenharmony_ci**【反例】** 1809e41f4b71Sopenharmony_ci如下代码示例中,使用Log()函数直接打印外部数据,可能出现格式化字符串漏洞。 1810e41f4b71Sopenharmony_ci 1811e41f4b71Sopenharmony_ci```c 1812e41f4b71Sopenharmony_civoid Foo() 1813e41f4b71Sopenharmony_ci{ 1814e41f4b71Sopenharmony_ci std::string msg = GetMsg(); 1815e41f4b71Sopenharmony_ci ... 1816e41f4b71Sopenharmony_ci syslog(priority, msg.c_str()); // 不符合:存在格式化字符串漏洞 1817e41f4b71Sopenharmony_ci} 1818e41f4b71Sopenharmony_ci``` 1819e41f4b71Sopenharmony_ci 1820e41f4b71Sopenharmony_ci**【正例】** 1821e41f4b71Sopenharmony_ci下面是推荐做法,使用%s转换符打印外部数据,避免格式化字符串漏洞。 1822e41f4b71Sopenharmony_ci 1823e41f4b71Sopenharmony_ci```c 1824e41f4b71Sopenharmony_civoid Foo() 1825e41f4b71Sopenharmony_ci{ 1826e41f4b71Sopenharmony_ci std::string msg = GetMsg(); 1827e41f4b71Sopenharmony_ci ... 1828e41f4b71Sopenharmony_ci syslog(priority, "%s", msg.c_str()); // 符合:这里没有格式化字符串漏洞 1829e41f4b71Sopenharmony_ci} 1830e41f4b71Sopenharmony_ci``` 1831e41f4b71Sopenharmony_ci 1832e41f4b71Sopenharmony_ci**【影响】** 1833e41f4b71Sopenharmony_ci如果格式串被外部可控,攻击者可以使进程崩溃、查看栈内容、查看内存内容或者在任意内存位置写入数据,进而以被攻击进程的权限执行任意代码。 1834e41f4b71Sopenharmony_ci 1835e41f4b71Sopenharmony_ci## 禁止外部可控数据作为进程启动函数的参数或者作为dlopen/LoadLibrary等模块加载函数的参数 1836e41f4b71Sopenharmony_ci 1837e41f4b71Sopenharmony_ci**【描述】** 1838e41f4b71Sopenharmony_ci本条款中进程启动函数包括system、popen、execl、execlp、execle、execv、execvp等。 1839e41f4b71Sopenharmony_cisystem()、popen()等函数会创建一个新的进程,如果外部可控数据作为这些函数的参数,会导致注入漏洞。 1840e41f4b71Sopenharmony_ci使用execl()等函数执行新进程时,如果使用shell启动新进程,则同样存在命令注入风险。 1841e41f4b71Sopenharmony_ci使用execlp()、execvp()、execvpe()函数依赖于系统的环境变量PATH来搜索程序路径,使用它们时应充分考虑外部环境变量的风险,或避免使用这些函数。 1842e41f4b71Sopenharmony_ci 1843e41f4b71Sopenharmony_ci因此,总是优先考虑使用C标准函数实现需要的功能。如果确实需要使用这些函数,应使用白名单机制确保这些函数的参数不受任何外来数据的影响。 1844e41f4b71Sopenharmony_ci 1845e41f4b71Sopenharmony_cidlopen、LoadLibrary函数会加载外部模块,如果外部可控数据作为这些函数的参数,有可能会加载攻击者事先预制的模块。如果要使用这些函数,可以采用如下措施之一: 1846e41f4b71Sopenharmony_ci 1847e41f4b71Sopenharmony_ci- 使用白名单机制,确保这些函数的参数不受任何外来数据的影响。 1848e41f4b71Sopenharmony_ci- 使用数字签名机制保护要加载的模块,充分保证其完整性。 1849e41f4b71Sopenharmony_ci- 在设备本地加载的动态库通过权限与访问控制措施保证了本身安全性后,通过特定目录自动被程序加载。 1850e41f4b71Sopenharmony_ci- 在设备本地的配置文件通过权限与访问控制措施保证了本身安全性后,自动加载配置文件中指定的动态库。 1851e41f4b71Sopenharmony_ci 1852e41f4b71Sopenharmony_ci**【反例】** 1853e41f4b71Sopenharmony_ci如下代码从外部获取数据后直接作为LoadLibrary函数的参数,有可能导致程序被植入木马。 1854e41f4b71Sopenharmony_ci 1855e41f4b71Sopenharmony_ci```c 1856e41f4b71Sopenharmony_cichar* msg = GetMsgFromRemote(); 1857e41f4b71Sopenharmony_ciLoadLibrary(msg); 1858e41f4b71Sopenharmony_ci``` 1859e41f4b71Sopenharmony_ci 1860e41f4b71Sopenharmony_ci如下代码示例中,使用 system() 函数执行 cmd 命令串来自外部,攻击者可以执行任意命令: 1861e41f4b71Sopenharmony_ci 1862e41f4b71Sopenharmony_ci```c 1863e41f4b71Sopenharmony_cistd::string cmd = GetCmdFromRemote(); 1864e41f4b71Sopenharmony_cisystem(cmd.c_str()); 1865e41f4b71Sopenharmony_ci``` 1866e41f4b71Sopenharmony_ci 1867e41f4b71Sopenharmony_ci如下代码示例中,使用 system() 函数执行 cmd 命令串的一部分来自外部,攻击者可能输入 `some dir;reboot`字符串,创造成系统重启: 1868e41f4b71Sopenharmony_ci 1869e41f4b71Sopenharmony_ci```cpp 1870e41f4b71Sopenharmony_cistd::string name = GetDirNameFromRemote(); 1871e41f4b71Sopenharmony_cistd::string cmd{"ls " + name}; 1872e41f4b71Sopenharmony_cisystem(cmd.c_str()); 1873e41f4b71Sopenharmony_ci``` 1874e41f4b71Sopenharmony_ci 1875e41f4b71Sopenharmony_ci使用exec系列函数来避免命令注入时,注意exec系列函数中的path、file参数禁止使用命令解析器(如/bin/sh)。 1876e41f4b71Sopenharmony_ci 1877e41f4b71Sopenharmony_ci```c 1878e41f4b71Sopenharmony_ciint execl(const char* path, const char* arg, ...); 1879e41f4b71Sopenharmony_ciint execlp(const char* file, const char* arg, ...); 1880e41f4b71Sopenharmony_ciint execle(const char* path, const char* arg, ...); 1881e41f4b71Sopenharmony_ciint execv(const char* path, char* const argv[]); 1882e41f4b71Sopenharmony_ciint execvp(const char* file, char* const argv[]); 1883e41f4b71Sopenharmony_ciint execvpe(const char* file, char* const argv[], char* const envp[]); 1884e41f4b71Sopenharmony_ci``` 1885e41f4b71Sopenharmony_ci 1886e41f4b71Sopenharmony_ci例如,禁止如下使用方式: 1887e41f4b71Sopenharmony_ci 1888e41f4b71Sopenharmony_ci```c 1889e41f4b71Sopenharmony_cistd::string cmd = GetDirNameFromRemote(); 1890e41f4b71Sopenharmony_ciexecl("/bin/sh", "sh", "-c", cmd.c_str(), nullptr); 1891e41f4b71Sopenharmony_ci``` 1892e41f4b71Sopenharmony_ci 1893e41f4b71Sopenharmony_ci可以使用库函数,或者可以通过编写少量的代码来避免使用system函数调用命令,如`mkdir()`函数可以实现`mkdir`命令的功能。 1894e41f4b71Sopenharmony_ci如下代码中,应该避免使用`cat`命令实现文件内容复制的功能。 1895e41f4b71Sopenharmony_ci 1896e41f4b71Sopenharmony_ci```c 1897e41f4b71Sopenharmony_ciint WriteDataToFile(const char* dstFile, const char* srcFile) 1898e41f4b71Sopenharmony_ci{ 1899e41f4b71Sopenharmony_ci ... // 入参的合法性校验 1900e41f4b71Sopenharmony_ci std::ostringstream oss; 1901e41f4b71Sopenharmony_ci oss << "cat " << srcFile << " > " << dstFile; 1902e41f4b71Sopenharmony_ci 1903e41f4b71Sopenharmony_ci std::string cmd{oss.str()}; 1904e41f4b71Sopenharmony_ci system(cmd.c_str()); 1905e41f4b71Sopenharmony_ci ... 1906e41f4b71Sopenharmony_ci} 1907e41f4b71Sopenharmony_ci``` 1908e41f4b71Sopenharmony_ci 1909e41f4b71Sopenharmony_ci**【正例】** 1910e41f4b71Sopenharmony_ci 1911e41f4b71Sopenharmony_ci如下代码中,通过少量的代码来实现。如下代码实现了文件复制的功能,避免了对`cat`或`cp`命令的调用。需要注意的是,为简化描述,下面代码未考虑信号中断的影响。 1912e41f4b71Sopenharmony_ci 1913e41f4b71Sopenharmony_ci```cpp 1914e41f4b71Sopenharmony_cibool WriteDataToFile(const std::string& dstFilePath, const std::string& srcFilePath) 1915e41f4b71Sopenharmony_ci{ 1916e41f4b71Sopenharmony_ci const int bufferSize = 1024; 1917e41f4b71Sopenharmony_ci std::vector<char> buffer (bufferSize + 1, 0); 1918e41f4b71Sopenharmony_ci 1919e41f4b71Sopenharmony_ci std::ifstream srcFile(srcFilePath, std::ios::binary); 1920e41f4b71Sopenharmony_ci std::ofstream dstFile(dstFilePath, std::ios::binary); 1921e41f4b71Sopenharmony_ci 1922e41f4b71Sopenharmony_ci if (!dstFile || !dstFile) { 1923e41f4b71Sopenharmony_ci ... // 错误处理 1924e41f4b71Sopenharmony_ci return false; 1925e41f4b71Sopenharmony_ci } 1926e41f4b71Sopenharmony_ci 1927e41f4b71Sopenharmony_ci while (true) { 1928e41f4b71Sopenharmony_ci // 从srcFile读取内容分块 1929e41f4b71Sopenharmony_ci srcFile.read(buffer.data(), bufferSize); 1930e41f4b71Sopenharmony_ci std::streamsize size = srcFile ? bufferSize : srcFile.gcount(); 1931e41f4b71Sopenharmony_ci 1932e41f4b71Sopenharmony_ci // 写入分块内容到dstFile 1933e41f4b71Sopenharmony_ci if (size > 0 && !dstFile.write(buffer.data(), size)) { 1934e41f4b71Sopenharmony_ci ... // 错误处理 1935e41f4b71Sopenharmony_ci break; 1936e41f4b71Sopenharmony_ci } 1937e41f4b71Sopenharmony_ci 1938e41f4b71Sopenharmony_ci if (!srcFile) { 1939e41f4b71Sopenharmony_ci ... // 检查错误:当不是eof()时记录错误 1940e41f4b71Sopenharmony_ci break; 1941e41f4b71Sopenharmony_ci } 1942e41f4b71Sopenharmony_ci } 1943e41f4b71Sopenharmony_ci // srcFile 和 dstFile 在退出作用域时会自动被关闭 1944e41f4b71Sopenharmony_ci return true; 1945e41f4b71Sopenharmony_ci} 1946e41f4b71Sopenharmony_ci``` 1947e41f4b71Sopenharmony_ci 1948e41f4b71Sopenharmony_ci可以通过库函数简单实现的功能(如上例),需要避免调用命令处理器来执行外部命令。 1949e41f4b71Sopenharmony_ci如果确实需要调用单个命令,应使用exec*函数来实现参数化调用,并对调用的命令实施白名单管理。同时应避免使用execlp、execvp、execvpe函数,因为这几个函数依赖外部的PATH环境变量。 1950e41f4b71Sopenharmony_ci此时,外部输入的fileName仅作为some_tool命令的参数,没有命令注入的风险。 1951e41f4b71Sopenharmony_ci 1952e41f4b71Sopenharmony_ci```cpp 1953e41f4b71Sopenharmony_cipid_t pid; 1954e41f4b71Sopenharmony_cichar* const envp[] = {nullptr}; 1955e41f4b71Sopenharmony_ci... 1956e41f4b71Sopenharmony_cistd::string fileName = GetDirNameFromRemote(); 1957e41f4b71Sopenharmony_ci... 1958e41f4b71Sopenharmony_cipid = fork(); 1959e41f4b71Sopenharmony_ciif (pid < 0) { 1960e41f4b71Sopenharmony_ci ... 1961e41f4b71Sopenharmony_ci} else if (pid == 0) { 1962e41f4b71Sopenharmony_ci // 使用some_tool对指定文件进行加工 1963e41f4b71Sopenharmony_ci execle("/bin/some_tool", "some_tool", fileName.c_str(), nullptr, envp); 1964e41f4b71Sopenharmony_ci _Exit(-1); 1965e41f4b71Sopenharmony_ci} 1966e41f4b71Sopenharmony_ci... 1967e41f4b71Sopenharmony_ciint status; 1968e41f4b71Sopenharmony_ciwaitpid(pid, &status, 0); 1969e41f4b71Sopenharmony_cistd::ofstream ofs(fileName, std::ios::in); 1970e41f4b71Sopenharmony_ci... 1971e41f4b71Sopenharmony_ci``` 1972e41f4b71Sopenharmony_ci 1973e41f4b71Sopenharmony_ci在必须使用system等命令解析器执行命令时,应对输入的命令字符串基于合理的白名单检查,避免命令注入。 1974e41f4b71Sopenharmony_ci 1975e41f4b71Sopenharmony_ci```cpp 1976e41f4b71Sopenharmony_cistd::string cmd = GetCmdFromRemote(); 1977e41f4b71Sopenharmony_ci 1978e41f4b71Sopenharmony_ci// 使用白名单检查命令是否合法,仅允许"some_tool_a", "some_tool_b"命令,外部无法随意控制 1979e41f4b71Sopenharmony_ciif (!IsValidCmd(cmd.c_str())) { 1980e41f4b71Sopenharmony_ci ... // 错误处理 1981e41f4b71Sopenharmony_ci} 1982e41f4b71Sopenharmony_cisystem(cmd.c_str()); 1983e41f4b71Sopenharmony_ci... 1984e41f4b71Sopenharmony_ci``` 1985e41f4b71Sopenharmony_ci 1986e41f4b71Sopenharmony_ci**【影响】** 1987e41f4b71Sopenharmony_ci 1988e41f4b71Sopenharmony_ci- 如果传递给system()、popen()或其他命令处理函数的命令字符串是外部可控的,则攻击者可能会以被攻击进程的权限执行系统上存在的任意命令。 1989e41f4b71Sopenharmony_ci- 如果动态库文件是外部可控的,则攻击者可替换该库文件,在某些情况下可以造成任意代码执行漏洞。 1990e41f4b71Sopenharmony_ci 1991e41f4b71Sopenharmony_ci# 其他C语言编程规范 1992e41f4b71Sopenharmony_ci 1993e41f4b71Sopenharmony_ci## 禁止通过对数组类型的函数参数变量进行sizeof来获取数组大小 1994e41f4b71Sopenharmony_ci 1995e41f4b71Sopenharmony_ci**【描述】** 1996e41f4b71Sopenharmony_ci 1997e41f4b71Sopenharmony_ci使用sizeof操作符求其操作数的大小(以字节为单位),其操作数可以是一个表达式或者加上括号的类型名称,例如:`sizeof(int)`或`sizeof(int *)`。 1998e41f4b71Sopenharmony_ci参考C11标准6.5.3.4中的脚注103: 1999e41f4b71Sopenharmony_ci 2000e41f4b71Sopenharmony_ci> 当将sizeof应用于具有数组或函数类型的参数时,sizeof操作符将得出调整后的(指针)类型的大小。 2001e41f4b71Sopenharmony_ci 2002e41f4b71Sopenharmony_ci函数参数列表中声明为数组的参数会被调整为相应类型的指针。例如:`void Func(int inArray[LEN])`函数参数列表中的inArray虽然被声明为数组,但是实际上会被调整为指向int类型的指针,即调整为`void Func(int *inArray)`。 2003e41f4b71Sopenharmony_ci在这个函数内使用`sizeof(inArray)`等同于`sizeof(int *)`,得到的结果通常与预期不相符。例如:在IA-32架构上,`sizeof(inArray)` 的值是 4,并不是inArray数组的大小。 2004e41f4b71Sopenharmony_ci 2005e41f4b71Sopenharmony_ci**【反例】** 2006e41f4b71Sopenharmony_ci如下代码示例中,函数ArrayInit的功能是初始化数组元素。该函数有一个声明为`int inArray[]`的参数,被调用时传递了一个长度为256的int类型数组data。 2007e41f4b71Sopenharmony_ciArrayInit函数实现中使用`sizeof(inArray) / sizeof(inArray[0])`方法来计算入参数组中元素的数量。 2008e41f4b71Sopenharmony_ci但由于inArray是函数参数,所以具有指针类型,结果,`sizeof(inArray)`等同于`sizeof(int *)`。 2009e41f4b71Sopenharmony_ci无论传递给ArrayInit函数的数组实际长度如何,表达式的`sizeof(inArray) / sizeof(inArray[0])`计算结果均为1,与预期不符。 2010e41f4b71Sopenharmony_ci 2011e41f4b71Sopenharmony_ci```c 2012e41f4b71Sopenharmony_ci#define DATA_LEN 256 2013e41f4b71Sopenharmony_civoid ArrayInit(int inArray[]) 2014e41f4b71Sopenharmony_ci{ 2015e41f4b71Sopenharmony_ci // 不符合:这里使用sizeof(inArray)计算数组大小 2016e41f4b71Sopenharmony_ci for (size_t i = 0; i < sizeof(inArray) / sizeof(inArray[0]); i++) { 2017e41f4b71Sopenharmony_ci ... 2018e41f4b71Sopenharmony_ci } 2019e41f4b71Sopenharmony_ci} 2020e41f4b71Sopenharmony_ci 2021e41f4b71Sopenharmony_civoid FunctionData(void) 2022e41f4b71Sopenharmony_ci{ 2023e41f4b71Sopenharmony_ci int data[DATA_LEN]; 2024e41f4b71Sopenharmony_ci 2025e41f4b71Sopenharmony_ci ... 2026e41f4b71Sopenharmony_ci ArrayInit(data); // 调用ArrayInit函数初始化数组data数据 2027e41f4b71Sopenharmony_ci ... 2028e41f4b71Sopenharmony_ci} 2029e41f4b71Sopenharmony_ci``` 2030e41f4b71Sopenharmony_ci 2031e41f4b71Sopenharmony_ci**【正例】** 2032e41f4b71Sopenharmony_ci如下代码示例中,修改函数定义,添加数组长度参数,并在调用处正确传入数组长度。 2033e41f4b71Sopenharmony_ci 2034e41f4b71Sopenharmony_ci```c 2035e41f4b71Sopenharmony_ci#define DATA_LEN 256 2036e41f4b71Sopenharmony_ci// 函数说明:入参len是入参inArray数组的长度 2037e41f4b71Sopenharmony_civoid ArrayInit(int inArray[], size_t len) 2038e41f4b71Sopenharmony_ci{ 2039e41f4b71Sopenharmony_ci for (size_t i = 0; i < len; i++) { 2040e41f4b71Sopenharmony_ci ... 2041e41f4b71Sopenharmony_ci } 2042e41f4b71Sopenharmony_ci} 2043e41f4b71Sopenharmony_ci 2044e41f4b71Sopenharmony_civoid FunctionData(void) 2045e41f4b71Sopenharmony_ci{ 2046e41f4b71Sopenharmony_ci int data[DATA_LEN]; 2047e41f4b71Sopenharmony_ci 2048e41f4b71Sopenharmony_ci ArrayInit(data, sizeof(data) / sizeof(data[0])); 2049e41f4b71Sopenharmony_ci ... 2050e41f4b71Sopenharmony_ci} 2051e41f4b71Sopenharmony_ci``` 2052e41f4b71Sopenharmony_ci 2053e41f4b71Sopenharmony_ci**【反例】** 2054e41f4b71Sopenharmony_ci如下代码示例中,`sizeof(inArray)`不等于`ARRAY_MAX_LEN * sizeof(int)`,因为将sizeof操作符应用于声明为具有数组类型的参数时,即使参数声明指定了长度,也会被调整为指针,`sizeof(inArray)`等同于 `sizeof(int *)`: 2055e41f4b71Sopenharmony_ci 2056e41f4b71Sopenharmony_ci```c 2057e41f4b71Sopenharmony_ci#define ARRAY_MAX_LEN 256 2058e41f4b71Sopenharmony_ci 2059e41f4b71Sopenharmony_civoid ArrayInit(int inArray[ARRAY_MAX_LEN]) 2060e41f4b71Sopenharmony_ci{ 2061e41f4b71Sopenharmony_ci // 不符合:sizeof(inArray),得到的长度是指针的大小,不是数组的长度,和预期不符。 2062e41f4b71Sopenharmony_ci for (size_t i = 0; i < sizeof(inArray) / sizeof(inArray[0]); i++) { 2063e41f4b71Sopenharmony_ci ... 2064e41f4b71Sopenharmony_ci } 2065e41f4b71Sopenharmony_ci} 2066e41f4b71Sopenharmony_ci 2067e41f4b71Sopenharmony_ciint main(void) 2068e41f4b71Sopenharmony_ci{ 2069e41f4b71Sopenharmony_ci int masterArray[ARRAY_MAX_LEN]; 2070e41f4b71Sopenharmony_ci 2071e41f4b71Sopenharmony_ci ... 2072e41f4b71Sopenharmony_ci ArrayInit(masterArray); 2073e41f4b71Sopenharmony_ci 2074e41f4b71Sopenharmony_ci return 0; 2075e41f4b71Sopenharmony_ci} 2076e41f4b71Sopenharmony_ci``` 2077e41f4b71Sopenharmony_ci 2078e41f4b71Sopenharmony_ci**【正例】** 2079e41f4b71Sopenharmony_ci如下代码示例中,使用入参len表示指定数组的长度: 2080e41f4b71Sopenharmony_ci 2081e41f4b71Sopenharmony_ci```c 2082e41f4b71Sopenharmony_ci#define ARRAY_MAX_LEN 256 2083e41f4b71Sopenharmony_ci 2084e41f4b71Sopenharmony_ci// 函数说明:入参len是入参数组的长度 2085e41f4b71Sopenharmony_civoid ArrayInit(int inArray[], size_t len) 2086e41f4b71Sopenharmony_ci{ 2087e41f4b71Sopenharmony_ci for (size_t i = 0; i < len; i++) { 2088e41f4b71Sopenharmony_ci ... 2089e41f4b71Sopenharmony_ci } 2090e41f4b71Sopenharmony_ci} 2091e41f4b71Sopenharmony_ci 2092e41f4b71Sopenharmony_ciint main(void) 2093e41f4b71Sopenharmony_ci{ 2094e41f4b71Sopenharmony_ci int masterArray[ARRAY_MAX_LEN]; 2095e41f4b71Sopenharmony_ci 2096e41f4b71Sopenharmony_ci ArrayInit(masterArray, ARRAY_MAX_LEN); 2097e41f4b71Sopenharmony_ci ... 2098e41f4b71Sopenharmony_ci 2099e41f4b71Sopenharmony_ci return 0; 2100e41f4b71Sopenharmony_ci} 2101e41f4b71Sopenharmony_ci``` 2102e41f4b71Sopenharmony_ci 2103e41f4b71Sopenharmony_ci## 禁止通过对指针变量进行sizeof操作来获取数组大小 2104e41f4b71Sopenharmony_ci 2105e41f4b71Sopenharmony_ci**描述】** 2106e41f4b71Sopenharmony_ci将指针当做数组进行sizeof操作时,会导致实际的执行结果与预期不符。例如:变量定义 `char *p = array`,其中array的定义为`char array[LEN]`,表达式`sizeof(p)`得到的结果与 `sizeof(char *)`相同,并非array的长度。 2107e41f4b71Sopenharmony_ci 2108e41f4b71Sopenharmony_ci**【反例】** 2109e41f4b71Sopenharmony_ci如下代码示例中,buffer和path分别是指针和数组,程序员想对这2个内存进行清0操作,但由于程序员的疏忽,将内存大小误写成了`sizeof(buffer)`,与预期不符。 2110e41f4b71Sopenharmony_ci 2111e41f4b71Sopenharmony_ci```c 2112e41f4b71Sopenharmony_cichar path[MAX_PATH]; 2113e41f4b71Sopenharmony_cichar *buffer = (char *)malloc(SIZE); 2114e41f4b71Sopenharmony_ci... 2115e41f4b71Sopenharmony_ci 2116e41f4b71Sopenharmony_ci... 2117e41f4b71Sopenharmony_cimemset(path, 0, sizeof(path)); 2118e41f4b71Sopenharmony_ci 2119e41f4b71Sopenharmony_ci// sizeof与预期不符,其结果为指针本身的大小而不是缓冲区大小 2120e41f4b71Sopenharmony_cimemset(buffer, 0, sizeof(buffer)); 2121e41f4b71Sopenharmony_ci``` 2122e41f4b71Sopenharmony_ci 2123e41f4b71Sopenharmony_ci**【正例】** 2124e41f4b71Sopenharmony_ci如下代码示例中,将`sizeof(buffer)`修改为申请的缓冲区大小: 2125e41f4b71Sopenharmony_ci 2126e41f4b71Sopenharmony_ci```c 2127e41f4b71Sopenharmony_cichar path[MAX_PATH]; 2128e41f4b71Sopenharmony_cichar *buffer = (char *)malloc(SIZE); 2129e41f4b71Sopenharmony_ci... 2130e41f4b71Sopenharmony_ci 2131e41f4b71Sopenharmony_ci... 2132e41f4b71Sopenharmony_cimemset(path, 0, sizeof(path)); 2133e41f4b71Sopenharmony_cimemset(buffer, 0, SIZE); // 使用申请的缓冲区大小 2134e41f4b71Sopenharmony_ci``` 2135e41f4b71Sopenharmony_ci 2136e41f4b71Sopenharmony_ci## 禁止直接使用外部数据拼接SQL命令 2137e41f4b71Sopenharmony_ci 2138e41f4b71Sopenharmony_ci**【描述】** 2139e41f4b71Sopenharmony_ciSQL注入是指SQL查询被恶意更改成一个与程序预期完全不同的查询。执行更改后的查询可能会导致信息泄露或者数据被篡改。而SQL注入的根源就是使用外部数据来拼接SQL语句。C/C++语言中常见的使用外部数据拼接SQL语句的场景有(包括但不局限于): 2140e41f4b71Sopenharmony_ci 2141e41f4b71Sopenharmony_ci- 连接MySQL时调用mysql_query(),Execute()时的入参 2142e41f4b71Sopenharmony_ci- 连接SQL Server时调用db-library驱动的dbsqlexec()的入参 2143e41f4b71Sopenharmony_ci- 调用ODBC驱动的SQLprepare()连接数据库时的SQL语句的参数 2144e41f4b71Sopenharmony_ci- C++程序调用OTL类库中的otl_stream(),otl_column_desc()时的入参 2145e41f4b71Sopenharmony_ci- C++程序连接Oracle数据库时调用ExecuteWithResSQL()的入参 2146e41f4b71Sopenharmony_ci 2147e41f4b71Sopenharmony_ci防止SQL注入的方法主要有以下几种: 2148e41f4b71Sopenharmony_ci 2149e41f4b71Sopenharmony_ci- 参数化查询(通常也叫作预处理语句):参数化查询是一种简单有效的防止SQL注入的查询方式,应该被优先考虑使用。支持的数据库有MySQL,Oracle(OCI)。 2150e41f4b71Sopenharmony_ci- 参数化查询(通过ODBC驱动):支持ODBC驱动参数化查询的数据库有Oracle、SQLServer、PostgreSQL和GaussDB。 2151e41f4b71Sopenharmony_ci- 对外部数据进行校验(对于每个引入的外部数据推荐“白名单”校验)。 2152e41f4b71Sopenharmony_ci- 对外部数据中的SQL特殊字符进行转义。 2153e41f4b71Sopenharmony_ci 2154e41f4b71Sopenharmony_ci**【反例】** 2155e41f4b71Sopenharmony_ci下列代码拼接用户输入,没有进行输入检查,存在SQL注入风险: 2156e41f4b71Sopenharmony_ci 2157e41f4b71Sopenharmony_ci```c 2158e41f4b71Sopenharmony_cichar name[NAME_MAX]; 2159e41f4b71Sopenharmony_cichar sqlStatements[SQL_CMD_MAX]; 2160e41f4b71Sopenharmony_ciint ret = GetUserInput(name, NAME_MAX); 2161e41f4b71Sopenharmony_ci... 2162e41f4b71Sopenharmony_ciret = sprintf(sqlStatements, 2163e41f4b71Sopenharmony_ci "SELECT childinfo FROM children WHERE name= ‘%s’", 2164e41f4b71Sopenharmony_ci name); 2165e41f4b71Sopenharmony_ci... 2166e41f4b71Sopenharmony_ciret = mysql_query(&myConnection, sqlStatements); 2167e41f4b71Sopenharmony_ci... 2168e41f4b71Sopenharmony_ci``` 2169e41f4b71Sopenharmony_ci 2170e41f4b71Sopenharmony_ci**【正例】** 2171e41f4b71Sopenharmony_ci使用预处理语句进行参数化查询可以防御SQL注入攻击: 2172e41f4b71Sopenharmony_ci 2173e41f4b71Sopenharmony_ci```c 2174e41f4b71Sopenharmony_cichar name[NAME_MAX]; 2175e41f4b71Sopenharmony_ci... 2176e41f4b71Sopenharmony_ciMYSQL_STMT *stmt = mysql_stmt_init(myConnection); 2177e41f4b71Sopenharmony_cichar *query = "SELECT childinfo FROM children WHERE name= ?"; 2178e41f4b71Sopenharmony_ciif (mysql_stmt_prepare(stmt, query, strlen(query))) { 2179e41f4b71Sopenharmony_ci ... 2180e41f4b71Sopenharmony_ci} 2181e41f4b71Sopenharmony_ciint ret = GetUserInput(name, NAME_MAX); 2182e41f4b71Sopenharmony_ci... 2183e41f4b71Sopenharmony_ciMYSQL_BIND params[1]; 2184e41f4b71Sopenharmony_ci(void)memset(params, 0, sizeof(params)); 2185e41f4b71Sopenharmony_ci... 2186e41f4b71Sopenharmony_ciparams[0].bufferType = MYSQL_TYPE_STRING; 2187e41f4b71Sopenharmony_ciparams[0].buffer = (char *)name; 2188e41f4b71Sopenharmony_ciparams[0].bufferLength = strlen(name); 2189e41f4b71Sopenharmony_ciparams[0].isNull = 0; 2190e41f4b71Sopenharmony_ci 2191e41f4b71Sopenharmony_cibool isCompleted = mysql_stmt_bind_param(stmt, params); 2192e41f4b71Sopenharmony_ci... 2193e41f4b71Sopenharmony_ciret = mysql_stmt_execute(stmt); 2194e41f4b71Sopenharmony_ci... 2195e41f4b71Sopenharmony_ci``` 2196e41f4b71Sopenharmony_ci 2197e41f4b71Sopenharmony_ci**【影响】** 2198e41f4b71Sopenharmony_ci 2199e41f4b71Sopenharmony_ci如果拼接SQL语句的字符串是外部可控的,则攻击者可以通过注入特定的字符串欺骗程序执行恶意的SQL命令,造成信息泄露、权限绕过、数据被篡改等问题。 2200e41f4b71Sopenharmony_ci 2201e41f4b71Sopenharmony_ci## 内存中的敏感信息使用完毕后立即清0 2202e41f4b71Sopenharmony_ci 2203e41f4b71Sopenharmony_ci**【描述】** 2204e41f4b71Sopenharmony_ci内存中的口令、密钥等敏感信息使用完毕后立即清0,避免被攻击者获取或者无意间泄露给低权限用户。这里所说的内存包括但不限于: 2205e41f4b71Sopenharmony_ci 2206e41f4b71Sopenharmony_ci- 动态分配的内存 2207e41f4b71Sopenharmony_ci- 静态分配的内存 2208e41f4b71Sopenharmony_ci- 自动分配(堆栈)内存 2209e41f4b71Sopenharmony_ci- 内存缓存 2210e41f4b71Sopenharmony_ci- 磁盘缓存 2211e41f4b71Sopenharmony_ci 2212e41f4b71Sopenharmony_ci**【反例】** 2213e41f4b71Sopenharmony_ci通常内存在释放前不需要清除内存数据,因为这样在运行时会增加额外开销,所以在这段内存被释放之后,之前的数据还是会保留在其中。如果这段内存中的数据包含敏感信息,则可能会意外泄露敏感信息。为了防止敏感信息泄露,必须先清除内存中的敏感信息,然后再释放。 2214e41f4b71Sopenharmony_ci在如下代码示例中,存储在所引用的动态内存中的敏感信息secret被复制到新动态分配的缓冲区newSecret,最终通过free()释放。因为释放前未清除这块内存数据,这块内存可能被重新分配到程序的另一部分,之前存储在newSecret中的敏感信息可能会无意中被泄露。 2215e41f4b71Sopenharmony_ci 2216e41f4b71Sopenharmony_ci```c 2217e41f4b71Sopenharmony_cichar *secret = NULL; 2218e41f4b71Sopenharmony_ci/* 2219e41f4b71Sopenharmony_ci * 假设 secret 指向敏感信息,敏感信息的内容是长度小于SIZE_MAX个字符, 2220e41f4b71Sopenharmony_ci * 并且以null终止的字节字符串 2221e41f4b71Sopenharmony_ci */ 2222e41f4b71Sopenharmony_ci 2223e41f4b71Sopenharmony_cisize_t size = strlen(secret); 2224e41f4b71Sopenharmony_cichar *newSecret = NULL; 2225e41f4b71Sopenharmony_cinewSecret = (char *)malloc(size + 1); 2226e41f4b71Sopenharmony_ciif (newSecret == NULL) { 2227e41f4b71Sopenharmony_ci ... // 错误处理 2228e41f4b71Sopenharmony_ci} else { 2229e41f4b71Sopenharmony_ci errno_t ret = strcpy(newSecret, secret); 2230e41f4b71Sopenharmony_ci ... // 处理 ret 2231e41f4b71Sopenharmony_ci 2232e41f4b71Sopenharmony_ci ... // 处理 newSecret... 2233e41f4b71Sopenharmony_ci 2234e41f4b71Sopenharmony_ci free(newSecret); 2235e41f4b71Sopenharmony_ci newSecret = NULL; 2236e41f4b71Sopenharmony_ci} 2237e41f4b71Sopenharmony_ci... 2238e41f4b71Sopenharmony_ci``` 2239e41f4b71Sopenharmony_ci 2240e41f4b71Sopenharmony_ci**【正例】** 2241e41f4b71Sopenharmony_ci如下代码示例中,为了防止信息泄露,应先清除包含敏感信息的动态内存(用’\0’字符填充空间),然后再释放它。 2242e41f4b71Sopenharmony_ci 2243e41f4b71Sopenharmony_ci```c 2244e41f4b71Sopenharmony_cichar *secret = NULL; 2245e41f4b71Sopenharmony_ci/* 2246e41f4b71Sopenharmony_ci * 假设 secret 指向敏感信息,敏感信息的内容是长度小于SIZE_MAX个字符, 2247e41f4b71Sopenharmony_ci * 并且以null终止的字节字符串 2248e41f4b71Sopenharmony_ci */ 2249e41f4b71Sopenharmony_cisize_t size = strlen(secret); 2250e41f4b71Sopenharmony_cichar *newSecret = NULL; 2251e41f4b71Sopenharmony_cinewSecret = (char *)malloc(size + 1); 2252e41f4b71Sopenharmony_ciif (newSecret == NULL) { 2253e41f4b71Sopenharmony_ci ... // 错误处理 2254e41f4b71Sopenharmony_ci} else { 2255e41f4b71Sopenharmony_ci errno_t ret = strcpy(newSecret, secret); 2256e41f4b71Sopenharmony_ci ... // 处理 ret 2257e41f4b71Sopenharmony_ci 2258e41f4b71Sopenharmony_ci ... // 处理 newSecret... 2259e41f4b71Sopenharmony_ci 2260e41f4b71Sopenharmony_ci (void)memset(newSecret, 0, size + 1); 2261e41f4b71Sopenharmony_ci free(newSecret); 2262e41f4b71Sopenharmony_ci newSecret = NULL; 2263e41f4b71Sopenharmony_ci} 2264e41f4b71Sopenharmony_ci... 2265e41f4b71Sopenharmony_ci``` 2266e41f4b71Sopenharmony_ci 2267e41f4b71Sopenharmony_ci**【正例】** 2268e41f4b71Sopenharmony_ci下面是另外一个涉及敏感信息清理的场景,在代码获取到密码后,将密码保存到password中,进行密码验证,使用完毕后,通过`memset()`函数对password清0。 2269e41f4b71Sopenharmony_ci 2270e41f4b71Sopenharmony_ci```c 2271e41f4b71Sopenharmony_ciint Foo(void) 2272e41f4b71Sopenharmony_ci{ 2273e41f4b71Sopenharmony_ci char password[MAX_PWD_LEN]; 2274e41f4b71Sopenharmony_ci if (!GetPassword(password, sizeof(password))) { 2275e41f4b71Sopenharmony_ci ... 2276e41f4b71Sopenharmony_ci } 2277e41f4b71Sopenharmony_ci if (!VerifyPassword(password)) { 2278e41f4b71Sopenharmony_ci ... 2279e41f4b71Sopenharmony_ci } 2280e41f4b71Sopenharmony_ci ... 2281e41f4b71Sopenharmony_ci (void)memset(password, 0, sizeof(password)); 2282e41f4b71Sopenharmony_ci ... 2283e41f4b71Sopenharmony_ci} 2284e41f4b71Sopenharmony_ci``` 2285e41f4b71Sopenharmony_ci 2286e41f4b71Sopenharmony_ci要特别**注意**:对敏感信息清理的时候要同时防止因编译器优化而使清理代码无效。 2287e41f4b71Sopenharmony_ci 2288e41f4b71Sopenharmony_ci例如,下列代码使用了可能被编译器优化掉的语句。 2289e41f4b71Sopenharmony_ci 2290e41f4b71Sopenharmony_ci```c 2291e41f4b71Sopenharmony_ciint SecureLogin(void) 2292e41f4b71Sopenharmony_ci{ 2293e41f4b71Sopenharmony_ci char pwd[PWD_SIZE]; 2294e41f4b71Sopenharmony_ci if (RetrievePassword(pwd, sizeof(pwd))) { 2295e41f4b71Sopenharmony_ci ... // 口令检查及其他处理 2296e41f4b71Sopenharmony_ci } 2297e41f4b71Sopenharmony_ci memset(pwd, 0, sizeof(pwd)); // 编译器优化有可能会使该语句失效 2298e41f4b71Sopenharmony_ci ... 2299e41f4b71Sopenharmony_ci} 2300e41f4b71Sopenharmony_ci``` 2301e41f4b71Sopenharmony_ci 2302e41f4b71Sopenharmony_ci某些编译器在优化时候不会执行它认为不会改变程序执行结果的代码,因此memset()操作会被优化掉。 2303e41f4b71Sopenharmony_ci 2304e41f4b71Sopenharmony_ci如果编译器支持#pragma指令,那么可以使用该指令指示编译器不作优化。 2305e41f4b71Sopenharmony_ci 2306e41f4b71Sopenharmony_ci```c 2307e41f4b71Sopenharmony_civoid SecureLogin(void) 2308e41f4b71Sopenharmony_ci{ 2309e41f4b71Sopenharmony_ci char pwd[PWD_SIZE]; 2310e41f4b71Sopenharmony_ci if (RetrievePassword(pwd, sizeof(pwd))) { 2311e41f4b71Sopenharmony_ci ... // 口令检查及其他处理 2312e41f4b71Sopenharmony_ci } 2313e41f4b71Sopenharmony_ci #pragma optimize("", off) 2314e41f4b71Sopenharmony_ci // 清除内存 2315e41f4b71Sopenharmony_ci ... 2316e41f4b71Sopenharmony_ci #pragma optimize("", on) 2317e41f4b71Sopenharmony_ci ... 2318e41f4b71Sopenharmony_ci} 2319e41f4b71Sopenharmony_ci``` 2320e41f4b71Sopenharmony_ci 2321e41f4b71Sopenharmony_ci**【影响】** 2322e41f4b71Sopenharmony_ci 2323e41f4b71Sopenharmony_ci未及时清理敏感信息,可能导致信息泄露。 2324e41f4b71Sopenharmony_ci 2325e41f4b71Sopenharmony_ci## 创建文件时必须显式指定合适的文件访问权限 2326e41f4b71Sopenharmony_ci 2327e41f4b71Sopenharmony_ci**【描述】** 2328e41f4b71Sopenharmony_ci创建文件时,如果不显式指定合适访问权限,可能会让未经授权的用户访问该文件,造成信息泄露,文件数据被篡改,文件中被注入恶意代码等风险。 2329e41f4b71Sopenharmony_ci 2330e41f4b71Sopenharmony_ci虽然文件的访问权限也依赖于文件系统,但是当前许多文件创建函数(例如POSIX open函数)都具有设置(或影响)文件访问权限的功能,所以当使用这些函数创建文件时,必须显式指定合适的文件访问权限,以防止意外访问。 2331e41f4b71Sopenharmony_ci 2332e41f4b71Sopenharmony_ci**【反例】** 2333e41f4b71Sopenharmony_ci使用POSIX open()函数创建文件但未显示指定该文件的访问权限,可能会导致文件创建时具有过高的访问权限。这可能会导致漏洞(例如CVE-2006-1174)。 2334e41f4b71Sopenharmony_ci 2335e41f4b71Sopenharmony_ci```c 2336e41f4b71Sopenharmony_civoid Foo(void) 2337e41f4b71Sopenharmony_ci{ 2338e41f4b71Sopenharmony_ci int fd = -1; 2339e41f4b71Sopenharmony_ci char *filename = NULL; 2340e41f4b71Sopenharmony_ci 2341e41f4b71Sopenharmony_ci ... // 初始化 filename 2342e41f4b71Sopenharmony_ci 2343e41f4b71Sopenharmony_ci fd = open(filename, O_CREAT | O_WRONLY); // 没有显式指定访问权限 2344e41f4b71Sopenharmony_ci if (fd == -1) { 2345e41f4b71Sopenharmony_ci ... // 错误处理 2346e41f4b71Sopenharmony_ci } 2347e41f4b71Sopenharmony_ci ... 2348e41f4b71Sopenharmony_ci} 2349e41f4b71Sopenharmony_ci``` 2350e41f4b71Sopenharmony_ci 2351e41f4b71Sopenharmony_ci**【正例】** 2352e41f4b71Sopenharmony_ci应该在open的第三个参数中显式指定新创建文件的访问权限。可以根据文件实际的应用情况设置何种访问权限。 2353e41f4b71Sopenharmony_ci 2354e41f4b71Sopenharmony_ci```c 2355e41f4b71Sopenharmony_civoid Foo(void) 2356e41f4b71Sopenharmony_ci{ 2357e41f4b71Sopenharmony_ci int fd = -1; 2358e41f4b71Sopenharmony_ci char *filename = NULL; 2359e41f4b71Sopenharmony_ci 2360e41f4b71Sopenharmony_ci ... // 初始化 filename 和指定其访问权限 2361e41f4b71Sopenharmony_ci 2362e41f4b71Sopenharmony_ci // 此处根据文件实际需要,显式指定其访问权限 2363e41f4b71Sopenharmony_ci int fd = open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); 2364e41f4b71Sopenharmony_ci if (fd == -1) { 2365e41f4b71Sopenharmony_ci ... // 错误处理 2366e41f4b71Sopenharmony_ci } 2367e41f4b71Sopenharmony_ci ... 2368e41f4b71Sopenharmony_ci} 2369e41f4b71Sopenharmony_ci``` 2370e41f4b71Sopenharmony_ci 2371e41f4b71Sopenharmony_ci**【影响】** 2372e41f4b71Sopenharmony_ci 2373e41f4b71Sopenharmony_ci创建访问权限弱的文件,可能会导致对这些文件的非法访问。 2374e41f4b71Sopenharmony_ci 2375e41f4b71Sopenharmony_ci## 使用文件路径前必须进行规范化并校验 2376e41f4b71Sopenharmony_ci 2377e41f4b71Sopenharmony_ci**【描述】** 2378e41f4b71Sopenharmony_ci当文件路径来自外部数据时,必须对其做合法性校验,如果不校验,可能造成系统文件的被任意访问。但是禁止直接对其进行校验,正确做法是在校验之前必须对其进行路径规范化处理。这是因为同一个文件可以通过多种形式的路径来描述和引用,例如既可以是绝对路径,也可以是相对路径;而且路径名、目录名和文件名可能包含使校验变得困难和不准确的字符(如:“.”、“..”)。此外,文件还可以是符号链接,这进一步模糊了文件的实际位置或标识,增加了校验的难度和校验准确性。所以必须先将文件路径规范化,从而更容易校验其路径、目录或文件名,增加校验准确性。 2379e41f4b71Sopenharmony_ci 2380e41f4b71Sopenharmony_ci因为规范化机制在不同的操作系统和文件系统之间可能有所不同,所以最好使用符合当前系统特性的规范化机制。 2381e41f4b71Sopenharmony_ci 2382e41f4b71Sopenharmony_ci一个简单的案例说明如下: 2383e41f4b71Sopenharmony_ci 2384e41f4b71Sopenharmony_ci```c 2385e41f4b71Sopenharmony_ci当文件路径来自外部数据时,需要先将文件路径规范化,如果没有作规范化处理,攻击者就有机会通过恶意构造文件路径进行文件的越权访问。 2386e41f4b71Sopenharmony_ci例如,攻击者可以构造“../../../etc/passwd”的方式进行任意文件访问。 2387e41f4b71Sopenharmony_ci``` 2388e41f4b71Sopenharmony_ci 2389e41f4b71Sopenharmony_ci**【反例】** 2390e41f4b71Sopenharmony_ci在此错误的示例中,inputFilename包含一个源于受污染源的文件名,并且该文件名已打开以进行写入。在使用此文件名操作之前,应该对其进行验证,以确保它引用的是预期的有效文件。 2391e41f4b71Sopenharmony_ci不幸的是,inputFilename引用的文件名可能包含特殊字符,例如目录字符,这使验证变得困难,甚至不可能。而且,inputFilename中可能包含可以指向任意文件路径的符号链接,即使该文件名通过了验证,也会导致该文件名是无效的。 2392e41f4b71Sopenharmony_ci这种场景下,对文件名的直接验证即使被执行也是得不到预期的结果,对fopen()的调用可能会导致访问一个意外的文件。 2393e41f4b71Sopenharmony_ci 2394e41f4b71Sopenharmony_ci```c 2395e41f4b71Sopenharmony_ci... 2396e41f4b71Sopenharmony_ci 2397e41f4b71Sopenharmony_ciif (!verify_file(inputFilename) { // 没有对inputFilename做规范化,直接做校验 2398e41f4b71Sopenharmony_ci ... // 错误处理 2399e41f4b71Sopenharmony_ci} 2400e41f4b71Sopenharmony_ci 2401e41f4b71Sopenharmony_ciif (fopen(inputFilename, "w") == NULL) { 2402e41f4b71Sopenharmony_ci ... // 错误处理 2403e41f4b71Sopenharmony_ci} 2404e41f4b71Sopenharmony_ci 2405e41f4b71Sopenharmony_ci... 2406e41f4b71Sopenharmony_ci``` 2407e41f4b71Sopenharmony_ci 2408e41f4b71Sopenharmony_ci**【正例】** 2409e41f4b71Sopenharmony_ci规范化文件名是具有一定难度的,因为这需要了解底层文件系统。 2410e41f4b71Sopenharmony_ciPOSIX realpath()函数可以帮助将路径名转换为规范形式。参考信息技术标准-POSIX®,基本规范第7期[IEEE std 1003.1:2013]: 2411e41f4b71Sopenharmony_ci 2412e41f4b71Sopenharmony_ci- 该realpath()函数应从所指向的路径名派生一个filename的绝对路径名,两者指向同一文件,绝对路径其文件名不涉及“ .”,“ ..”或符号链接。 2413e41f4b71Sopenharmony_ci 在规范化路径之后,还必须执行进一步的验证,例如确保两个连续的斜杠或特殊文件不会出现在文件名中。有关如何执行路径名解析的更多详细信息,请参见[IEEE Std 1003.1: 2013]第4.12节“路径名解析”。 2414e41f4b71Sopenharmony_ci 使用realpath()函数有许多需要注意的地方。 2415e41f4b71Sopenharmony_ci 在了解了以上原理之后,对上面的错误代码示例,我们采用如下解决方案: 2416e41f4b71Sopenharmony_ci 2417e41f4b71Sopenharmony_ci```c 2418e41f4b71Sopenharmony_cichar *realpathRes = NULL; 2419e41f4b71Sopenharmony_ci 2420e41f4b71Sopenharmony_ci... 2421e41f4b71Sopenharmony_ci 2422e41f4b71Sopenharmony_ci// 在校验之前,先对inputFilename做规范化处理 2423e41f4b71Sopenharmony_cirealpathRes = realpath(inputFilename, NULL); 2424e41f4b71Sopenharmony_ciif (realpathRes == NULL) { 2425e41f4b71Sopenharmony_ci ... // 规范化的错误处理 2426e41f4b71Sopenharmony_ci} 2427e41f4b71Sopenharmony_ci 2428e41f4b71Sopenharmony_ci// 规范化以后对路径进行校验 2429e41f4b71Sopenharmony_ciif (!verify_file(realpathRes) { 2430e41f4b71Sopenharmony_ci ... // 校验的错误处理 2431e41f4b71Sopenharmony_ci} 2432e41f4b71Sopenharmony_ci 2433e41f4b71Sopenharmony_ci// 使用 2434e41f4b71Sopenharmony_ciif (fopen(realpathRes, "w") == NULL) { 2435e41f4b71Sopenharmony_ci ... // 实际操作的错误处理 2436e41f4b71Sopenharmony_ci} 2437e41f4b71Sopenharmony_ci 2438e41f4b71Sopenharmony_ci... 2439e41f4b71Sopenharmony_ci 2440e41f4b71Sopenharmony_cifree(realpathRes); 2441e41f4b71Sopenharmony_cirealpathRes = NULL; 2442e41f4b71Sopenharmony_ci... 2443e41f4b71Sopenharmony_ci``` 2444e41f4b71Sopenharmony_ci 2445e41f4b71Sopenharmony_ci**【正例】** 2446e41f4b71Sopenharmony_ci根据我们的实际场景,我们还可以采用的第二套解决方案,说明如下: 2447e41f4b71Sopenharmony_ci如果`PATH_MAX`被定义为 limits.h 中的一个常量,那么使用非空的`resolved_path`调用realpath()也是安全的。 2448e41f4b71Sopenharmony_ci在本例中realpath()函数期望`resolved_path`引用一个字符数组,该字符数组足够大,可以容纳规范化的路径。 2449e41f4b71Sopenharmony_ci如果定义了PATH_MAX,则分配一个大小为`PATH_MAX`的缓冲区来保存realpath()的结果。正确代码示例如下: 2450e41f4b71Sopenharmony_ci 2451e41f4b71Sopenharmony_ci```c 2452e41f4b71Sopenharmony_cichar *realpathRes = NULL; 2453e41f4b71Sopenharmony_cichar *canonicalFilename = NULL; 2454e41f4b71Sopenharmony_cisize_t pathSize = 0; 2455e41f4b71Sopenharmony_ci 2456e41f4b71Sopenharmony_ci... 2457e41f4b71Sopenharmony_ci 2458e41f4b71Sopenharmony_cipathSize = (size_t)PATH_MAX; 2459e41f4b71Sopenharmony_ci 2460e41f4b71Sopenharmony_ciif (VerifyPathSize(pathSize)) { 2461e41f4b71Sopenharmony_ci canonicalFilename = (char *)malloc(pathSize); 2462e41f4b71Sopenharmony_ci 2463e41f4b71Sopenharmony_ci if (canonicalFilename == NULL) { 2464e41f4b71Sopenharmony_ci ... // 错误处理 2465e41f4b71Sopenharmony_ci } 2466e41f4b71Sopenharmony_ci 2467e41f4b71Sopenharmony_ci realpathRes = realpath(inputFilename, canonicalFilename); 2468e41f4b71Sopenharmony_ci} 2469e41f4b71Sopenharmony_ci 2470e41f4b71Sopenharmony_ciif (realpathRes == NULL) { 2471e41f4b71Sopenharmony_ci ... // 错误处理 2472e41f4b71Sopenharmony_ci} 2473e41f4b71Sopenharmony_ci 2474e41f4b71Sopenharmony_ciif (VerifyFile(realpathRes)) { 2475e41f4b71Sopenharmony_ci ... // 错误处理 2476e41f4b71Sopenharmony_ci} 2477e41f4b71Sopenharmony_ci 2478e41f4b71Sopenharmony_ciif (fopen(realpathRes, "w") == NULL ) { 2479e41f4b71Sopenharmony_ci ... // 错误处理 2480e41f4b71Sopenharmony_ci} 2481e41f4b71Sopenharmony_ci 2482e41f4b71Sopenharmony_ci... 2483e41f4b71Sopenharmony_ci 2484e41f4b71Sopenharmony_cifree(canonicalFilename); 2485e41f4b71Sopenharmony_cicanonicalFilename = NULL; 2486e41f4b71Sopenharmony_ci... 2487e41f4b71Sopenharmony_ci``` 2488e41f4b71Sopenharmony_ci 2489e41f4b71Sopenharmony_ci**【反例】** 2490e41f4b71Sopenharmony_ci下面的代码场景是从外部获取到文件名称,拼接成文件路径后,直接对文件内容进行读取,导致攻击者可以读取到任意文件的内容: 2491e41f4b71Sopenharmony_ci 2492e41f4b71Sopenharmony_ci```c 2493e41f4b71Sopenharmony_cichar *filename = GetMsgFromRemote(); 2494e41f4b71Sopenharmony_ci... 2495e41f4b71Sopenharmony_ciint ret = sprintf(untrustPath, "/tmp/%s", filename); 2496e41f4b71Sopenharmony_ci... 2497e41f4b71Sopenharmony_cichar *text = ReadFileContent(untrustPath); 2498e41f4b71Sopenharmony_ci``` 2499e41f4b71Sopenharmony_ci 2500e41f4b71Sopenharmony_ci**【正例】** 2501e41f4b71Sopenharmony_ci正确的做法是,对路径进行规范化后,再判断路径是否是本程序所认为的合法的路径: 2502e41f4b71Sopenharmony_ci 2503e41f4b71Sopenharmony_ci```c 2504e41f4b71Sopenharmony_cichar *filename = GetMsgFromRemote(); 2505e41f4b71Sopenharmony_ci... 2506e41f4b71Sopenharmony_cisprintf(untrustPath, "/tmp/%s", filename); 2507e41f4b71Sopenharmony_cichar path[PATH_MAX]; 2508e41f4b71Sopenharmony_ciif (realpath(untrustPath, path) == NULL) { 2509e41f4b71Sopenharmony_ci ... // 处理错误 2510e41f4b71Sopenharmony_ci} 2511e41f4b71Sopenharmony_ciif (!IsValidPath(path)) { // 检查文件的位置是否正确 2512e41f4b71Sopenharmony_ci ... // 处理错误 2513e41f4b71Sopenharmony_ci} 2514e41f4b71Sopenharmony_cichar *text = ReadFileContent(path); 2515e41f4b71Sopenharmony_ci``` 2516e41f4b71Sopenharmony_ci 2517e41f4b71Sopenharmony_ci**【例外】** 2518e41f4b71Sopenharmony_ci 2519e41f4b71Sopenharmony_ci运行于控制台的命令行程序,通过控制台手工输入文件路径,可以作为本条款例外。 2520e41f4b71Sopenharmony_ci 2521e41f4b71Sopenharmony_ci```c 2522e41f4b71Sopenharmony_ciint main(int argc, char **argv) 2523e41f4b71Sopenharmony_ci{ 2524e41f4b71Sopenharmony_ci int fd = -1; 2525e41f4b71Sopenharmony_ci 2526e41f4b71Sopenharmony_ci if (argc == 2) { 2527e41f4b71Sopenharmony_ci fd = open(argv[1], O_RDONLY); 2528e41f4b71Sopenharmony_ci ... 2529e41f4b71Sopenharmony_ci } 2530e41f4b71Sopenharmony_ci 2531e41f4b71Sopenharmony_ci ... 2532e41f4b71Sopenharmony_ci return 0; 2533e41f4b71Sopenharmony_ci} 2534e41f4b71Sopenharmony_ci``` 2535e41f4b71Sopenharmony_ci 2536e41f4b71Sopenharmony_ci**【影响】** 2537e41f4b71Sopenharmony_ci 2538e41f4b71Sopenharmony_ci未对不可信的文件路径进行规范化和校验,可能造成对任意文件的访问。 2539e41f4b71Sopenharmony_ci 2540e41f4b71Sopenharmony_ci## 不要在共享目录中创建临时文件 2541e41f4b71Sopenharmony_ci 2542e41f4b71Sopenharmony_ci**【描述】** 2543e41f4b71Sopenharmony_ci共享目录是指其它非特权用户可以访问的目录。程序的临时文件应当是程序自身独享的,任何将自身临时文件置于共享目录的做法,将导致其他共享用户获得该程序的额外信息,产生信息泄露。因此,不要在任何共享目录创建仅由程序自身使用的临时文件。 2544e41f4b71Sopenharmony_ci 2545e41f4b71Sopenharmony_ci临时文件通常用于辅助保存不能驻留在内存中的数据或存储临时的数据,也可用作进程间通信的一种手段(通过文件系统传输数据)。例如,一个进程在共享目录中创建一个临时文件,该文件名可能使用了众所周知的名称或者一个临时的名称,然后就可以通过该文件在进程间共享信息。这种通过在共享目录中创建临时文件的方法实现进程间共享的做法很危险,因为共享目录中的这些文件很容易被攻击者劫持或操纵。这里有几种缓解策略: 2546e41f4b71Sopenharmony_ci 2547e41f4b71Sopenharmony_ci1. 使用其他低级IPC(进程间通信)机制,例如套接字或共享内存。 2548e41f4b71Sopenharmony_ci2. 使用更高级别的IPC机制,例如远程过程调用。 2549e41f4b71Sopenharmony_ci3. 使用仅能由程序本身访问的安全目录(多线程/进程下注意防止条件竞争)。 2550e41f4b71Sopenharmony_ci 2551e41f4b71Sopenharmony_ci同时,下面列出了几项临时文件创建使用的方法,产品根据具体场景执行以下一项或者几项,同时产品也可以自定义合适的方法。 2552e41f4b71Sopenharmony_ci 2553e41f4b71Sopenharmony_ci1. 文件必须具有合适的权限,只有符合权限的用户才能访问 2554e41f4b71Sopenharmony_ci2. 创建的文件名是唯一的、或不可预测的 2555e41f4b71Sopenharmony_ci3. 仅当文件不存在时才创建打开(原子创建打开) 2556e41f4b71Sopenharmony_ci4. 使用独占访问打开,避免竞争条件 2557e41f4b71Sopenharmony_ci5. 在程序退出之前移除 2558e41f4b71Sopenharmony_ci 2559e41f4b71Sopenharmony_ci同时也需要注意到,当某个目录被开放读/写权限给多个用户或者一组用户时,该共享目录潜在的安全风险远远大于访问该目录中临时文件这个功能的本身。 2560e41f4b71Sopenharmony_ci 2561e41f4b71Sopenharmony_ci在共享目录中创建临时文件很容易受到威胁。例如,用于本地挂载的文件系统的代码在与远程挂载的文件系统一起共享使用时可能会受到攻击。安全的解决方案是不要在共享目录中创建临时文件。 2562e41f4b71Sopenharmony_ci 2563e41f4b71Sopenharmony_ci**【反例】** 2564e41f4b71Sopenharmony_ci如下代码示例,程序在系统的共享目录/tmp下创建临时文件来保存临时数据,且文件名是硬编码的。 2565e41f4b71Sopenharmony_ci由于文件名是硬编码的,因此是可预测的,攻击者只需用符号链接替换文件,然后链接所引用的目标文件就会被打开并写入新内容。 2566e41f4b71Sopenharmony_ci 2567e41f4b71Sopenharmony_ci```c 2568e41f4b71Sopenharmony_civoid ProcData(const char *filename) 2569e41f4b71Sopenharmony_ci{ 2570e41f4b71Sopenharmony_ci FILE *fp = fopen(filename, "wb+"); 2571e41f4b71Sopenharmony_ci if (fp == NULL) { 2572e41f4b71Sopenharmony_ci ... // 错误处理 2573e41f4b71Sopenharmony_ci } 2574e41f4b71Sopenharmony_ci 2575e41f4b71Sopenharmony_ci ... // 写文件 2576e41f4b71Sopenharmony_ci 2577e41f4b71Sopenharmony_ci fclose(fp); 2578e41f4b71Sopenharmony_ci} 2579e41f4b71Sopenharmony_ci 2580e41f4b71Sopenharmony_ciint main(void) 2581e41f4b71Sopenharmony_ci{ 2582e41f4b71Sopenharmony_ci // 不符合:1.在系统共享目录中创建临时文件;2.临时文件名硬编码 2583e41f4b71Sopenharmony_ci char *pFile = "/tmp/data"; 2584e41f4b71Sopenharmony_ci ... 2585e41f4b71Sopenharmony_ci 2586e41f4b71Sopenharmony_ci ProcData(pFile); 2587e41f4b71Sopenharmony_ci 2588e41f4b71Sopenharmony_ci ... 2589e41f4b71Sopenharmony_ci return 0; 2590e41f4b71Sopenharmony_ci} 2591e41f4b71Sopenharmony_ci``` 2592e41f4b71Sopenharmony_ci 2593e41f4b71Sopenharmony_ci**【正确案例】** 2594e41f4b71Sopenharmony_ci 2595e41f4b71Sopenharmony_ci```c 2596e41f4b71Sopenharmony_ci不应在该目录下创建仅由程序自身使用的临时文件。 2597e41f4b71Sopenharmony_ci``` 2598e41f4b71Sopenharmony_ci 2599e41f4b71Sopenharmony_ci**【影响】** 2600e41f4b71Sopenharmony_ci 2601e41f4b71Sopenharmony_ci不安全的创建临时文件,可能导致文件非法访问,并造成本地系统上的权限提升。 2602e41f4b71Sopenharmony_ci 2603e41f4b71Sopenharmony_ci## 不要在信号处理函数中访问共享对象 2604e41f4b71Sopenharmony_ci 2605e41f4b71Sopenharmony_ci**【描述】** 2606e41f4b71Sopenharmony_ci如果在信号处理程序中访问和修改共享对象,可能会造成竞争条件,使数据处于不确定的状态。 2607e41f4b71Sopenharmony_ci这条规则有两个不适用的场景(参考C11标准5.1.2.3第5段): 2608e41f4b71Sopenharmony_ci 2609e41f4b71Sopenharmony_ci- 读写不需要加锁的原子对象; 2610e41f4b71Sopenharmony_ci- 读写volatile sig_atomic_t类型的对象,因为具有volatile sig_atomic_t类型的对象即使在出现异步中断的时候也可以作为一个原子实体访问,是异步安全的。 2611e41f4b71Sopenharmony_ci 2612e41f4b71Sopenharmony_ci**【反例】** 2613e41f4b71Sopenharmony_ci在这个信号处理过程中,程序打算将`g_msg`作为共享对象,当产生SIGINT信号时更新共享对象的内容,但是该`g_msg`变量类型不是`volatile sig_atomic_t`,所以不是异步安全的。 2614e41f4b71Sopenharmony_ci 2615e41f4b71Sopenharmony_ci```c 2616e41f4b71Sopenharmony_ci#define MAX_MSG_SIZE 32 2617e41f4b71Sopenharmony_cistatic char g_msgBuf[MAX_MSG_SIZE] = {0}; 2618e41f4b71Sopenharmony_cistatic char *g_msg = g_msgBuf; 2619e41f4b71Sopenharmony_ci 2620e41f4b71Sopenharmony_civoid SignalHandler(int signum) 2621e41f4b71Sopenharmony_ci{ 2622e41f4b71Sopenharmony_ci // 下面代码操作g_msg不合规,因为不是异步安全的 2623e41f4b71Sopenharmony_ci (void)memset(g_msg,0, MAX_MSG_SIZE); 2624e41f4b71Sopenharmony_ci errno_t ret = strcpy(g_msg, "signal SIGINT received."); 2625e41f4b71Sopenharmony_ci ... // 处理 ret 2626e41f4b71Sopenharmony_ci} 2627e41f4b71Sopenharmony_ci 2628e41f4b71Sopenharmony_ciint main(void) 2629e41f4b71Sopenharmony_ci{ 2630e41f4b71Sopenharmony_ci errno_t ret = strcpy(g_msg, "No msg yet."); // 初始化消息内容 2631e41f4b71Sopenharmony_ci ... // 处理 ret 2632e41f4b71Sopenharmony_ci 2633e41f4b71Sopenharmony_ci signal(SIGINT, SignalHandler); // 设置SIGINT信号对应的处理函数 2634e41f4b71Sopenharmony_ci 2635e41f4b71Sopenharmony_ci ... // 程序主循环代码 2636e41f4b71Sopenharmony_ci 2637e41f4b71Sopenharmony_ci return 0; 2638e41f4b71Sopenharmony_ci} 2639e41f4b71Sopenharmony_ci``` 2640e41f4b71Sopenharmony_ci 2641e41f4b71Sopenharmony_ci**【正例】** 2642e41f4b71Sopenharmony_ci如下代码示例中,在信号处理函数中仅将`volatile sig_atomic_t`类型作为共享对象使用。 2643e41f4b71Sopenharmony_ci 2644e41f4b71Sopenharmony_ci```c 2645e41f4b71Sopenharmony_ci#define MAX_MSG_SIZE 32 2646e41f4b71Sopenharmony_civolatile sig_atomic_t g_sigFlag = 0; 2647e41f4b71Sopenharmony_ci 2648e41f4b71Sopenharmony_civoid SignalHandler(int signum) 2649e41f4b71Sopenharmony_ci{ 2650e41f4b71Sopenharmony_ci g_sigFlag = 1; // 符合 2651e41f4b71Sopenharmony_ci} 2652e41f4b71Sopenharmony_ci 2653e41f4b71Sopenharmony_ciint main(void) 2654e41f4b71Sopenharmony_ci{ 2655e41f4b71Sopenharmony_ci signal(SIGINT, SignalHandler); 2656e41f4b71Sopenharmony_ci char msgBuf[MAX_MSG_SIZE]; 2657e41f4b71Sopenharmony_ci errno_t ret = strcpy(msgBuf, "No msg yet."); // 初始化消息内容 2658e41f4b71Sopenharmony_ci ... // 处理 ret 2659e41f4b71Sopenharmony_ci 2660e41f4b71Sopenharmony_ci ... // 程序主循环代码 2661e41f4b71Sopenharmony_ci 2662e41f4b71Sopenharmony_ci if (g_sigFlag == 1) { // 在退出主循环之后,根据g_sigFlag状态再刷新消息内容 2663e41f4b71Sopenharmony_ci ret = strcpy(msgBuf, "signal SIGINT received."); 2664e41f4b71Sopenharmony_ci ... // 处理 ret 2665e41f4b71Sopenharmony_ci } 2666e41f4b71Sopenharmony_ci 2667e41f4b71Sopenharmony_ci return 0; 2668e41f4b71Sopenharmony_ci} 2669e41f4b71Sopenharmony_ci``` 2670e41f4b71Sopenharmony_ci 2671e41f4b71Sopenharmony_ci**【影响】** 2672e41f4b71Sopenharmony_ci 2673e41f4b71Sopenharmony_ci在信号处理程序中访问或修改共享对象,可能造成以不一致的状态访问数据。 2674e41f4b71Sopenharmony_ci 2675e41f4b71Sopenharmony_ci## 禁用rand函数产生用于安全用途的伪随机数 2676e41f4b71Sopenharmony_ci 2677e41f4b71Sopenharmony_ci**【描述】** 2678e41f4b71Sopenharmony_ciC语言标准库rand()函数生成的是伪随机数,所以不能保证其产生的随机数序列质量。根据C11标准,rand()函数产生的随机数范围是`[0, RAND_MAX(0x7FFF)]`,因为范围相对较短,所以这些数字可以被预测。 2679e41f4b71Sopenharmony_ci所以禁止使用rand()函数产生的随机数用于安全用途,必须使用安全的随机数产生方式。 2680e41f4b71Sopenharmony_ci 2681e41f4b71Sopenharmony_ci典型的安全用途场景包括(但不限于)以下几种: 2682e41f4b71Sopenharmony_ci 2683e41f4b71Sopenharmony_ci- 会话标识SessionID的生成; 2684e41f4b71Sopenharmony_ci- 挑战算法中的随机数生成; 2685e41f4b71Sopenharmony_ci- 验证码的随机数生成; 2686e41f4b71Sopenharmony_ci- 用于密码算法用途(例如用于生成IV、盐值、密钥等)的随机数生成。 2687e41f4b71Sopenharmony_ci 2688e41f4b71Sopenharmony_ci**【反例】** 2689e41f4b71Sopenharmony_ci程序员期望生成一个唯一的不可被猜测的HTTP会话ID,但该ID是通过调用rand()函数产生的数字随机数,它的ID是可猜测的,并且随机性有限。 2690e41f4b71Sopenharmony_ci 2691e41f4b71Sopenharmony_ci**【影响】** 2692e41f4b71Sopenharmony_ci 2693e41f4b71Sopenharmony_ci使用rand()函数可能造成可预测的随机数。 2694e41f4b71Sopenharmony_ci 2695e41f4b71Sopenharmony_ci## 禁止在发布版本中输出对象或函数的地址 2696e41f4b71Sopenharmony_ci 2697e41f4b71Sopenharmony_ci**【描述】** 2698e41f4b71Sopenharmony_ci禁止在发布版本中输出对象或函数的地址,如:将变量或函数的地址输出到客户端、日志、串口中。 2699e41f4b71Sopenharmony_ci 2700e41f4b71Sopenharmony_ci当攻击者实施高级攻击时,通常需要先获取目标程序中的内存地址(如变量地址、函数地址等),再通过修改指定内存的内容,达到攻击目的。 2701e41f4b71Sopenharmony_ci如果程序中主动输出对象或函数的地址,则为攻击者提供了便利条件,可以根据这些地址以及偏移量计算出其他对象或函数的地址,并实施攻击。 2702e41f4b71Sopenharmony_ci另外,由于内存地址泄露,也会造成地址空间随机化的保护功能失效。 2703e41f4b71Sopenharmony_ci 2704e41f4b71Sopenharmony_ci**【反例】** 2705e41f4b71Sopenharmony_ci如下代码中,使用%p格式将指针指向的地址记录到日志中。 2706e41f4b71Sopenharmony_ci 2707e41f4b71Sopenharmony_ci```c 2708e41f4b71Sopenharmony_ciint Encode(unsigned char *in, size_t inSize, unsigned char *out, size_t maxSize) 2709e41f4b71Sopenharmony_ci{ 2710e41f4b71Sopenharmony_ci ... 2711e41f4b71Sopenharmony_ci Log("in=%p, in size=%zu, out=%p, max size=%zu\n", in, inSize, out, maxSize); 2712e41f4b71Sopenharmony_ci ... 2713e41f4b71Sopenharmony_ci} 2714e41f4b71Sopenharmony_ci``` 2715e41f4b71Sopenharmony_ci 2716e41f4b71Sopenharmony_ci备注:这里仅用%p打印指针作为示例,代码中将指针转换为整数再打印也存在同样的风险。 2717e41f4b71Sopenharmony_ci 2718e41f4b71Sopenharmony_ci**【正例】** 2719e41f4b71Sopenharmony_ci如下代码中,删除打印地址的代码。 2720e41f4b71Sopenharmony_ci 2721e41f4b71Sopenharmony_ci```c 2722e41f4b71Sopenharmony_ciint Encode(unsigned char *in, size_t inSize, unsigned char *out, size_t maxSize) 2723e41f4b71Sopenharmony_ci{ 2724e41f4b71Sopenharmony_ci ... 2725e41f4b71Sopenharmony_ci Log("in size=%zu, max size=%zu\n", inSize, maxSize); 2726e41f4b71Sopenharmony_ci ... 2727e41f4b71Sopenharmony_ci} 2728e41f4b71Sopenharmony_ci``` 2729e41f4b71Sopenharmony_ci 2730e41f4b71Sopenharmony_ci**【例外】** 2731e41f4b71Sopenharmony_ci当程序崩溃退出时,在记录崩溃的异常信息中可以输出内存地址等信息。 2732e41f4b71Sopenharmony_ci 2733e41f4b71Sopenharmony_ci**【影响】** 2734e41f4b71Sopenharmony_ci 2735e41f4b71Sopenharmony_ci内存地址信息泄露,为攻击者实施攻击提供有利信息,可能造成地址空间随机化防护失效。 2736e41f4b71Sopenharmony_ci 2737e41f4b71Sopenharmony_ci## 禁止代码中包含公网地址 2738e41f4b71Sopenharmony_ci 2739e41f4b71Sopenharmony_ci**【描述】** 2740e41f4b71Sopenharmony_ci 2741e41f4b71Sopenharmony_ci代码或脚本中包含用户不可见,不可知的公网地址,可能会引起客户质疑。 2742e41f4b71Sopenharmony_ci 2743e41f4b71Sopenharmony_ci对产品发布的软件(包含软件包/补丁包)中包含的公网地址(包括公网IP地址、公网URL地址/域名、邮箱地址)要求如下: 2744e41f4b71Sopenharmony_ci1、禁止包含用户界面不可见、或产品资料未描述的未公开的公网地址。 2745e41f4b71Sopenharmony_ci2、已公开的公网地址禁止写在代码或者脚本中,可以存储在配置文件或数据库中。 2746e41f4b71Sopenharmony_ci 2747e41f4b71Sopenharmony_ci对于开源/第三方软件自带的公网地址必须至少满足上述第1条公开性要求。 2748e41f4b71Sopenharmony_ci 2749e41f4b71Sopenharmony_ci**【例外】** 2750e41f4b71Sopenharmony_ci 2751e41f4b71Sopenharmony_ci- 对于标准协议中必须指定公网地址的场景可例外,如soap协议中函数的命名空间必须指定的一个组装的公网URL、http页面中包含w3.org网址等。 2752e41f4b71Sopenharmony_ci 2753e41f4b71Sopenharmony_ci# 内核安全编程 2754e41f4b71Sopenharmony_ci 2755e41f4b71Sopenharmony_ci## 内核mmap接口实现中,确保对映射起始地址和大小进行合法性校验 2756e41f4b71Sopenharmony_ci 2757e41f4b71Sopenharmony_ci**【描述】** 2758e41f4b71Sopenharmony_ci 2759e41f4b71Sopenharmony_ci**说明**:内核 mmap接口中,经常使用remap_pfn_range()函数将设备物理内存映射到用户进程空间。如果映射起始地址等参数由用户态控制并缺少合法性校验,将导致用户态可通过映射读写任意内核地址。如果攻击者精心构造传入参数,甚至可在内核中执行任意代码。 2760e41f4b71Sopenharmony_ci 2761e41f4b71Sopenharmony_ci**【错误代码示例】** 2762e41f4b71Sopenharmony_ci 2763e41f4b71Sopenharmony_ci如下代码在使用remap_pfn_range()进行内存映射时,未对用户可控的映射起始地址和空间大小进行合法性校验,可导致内核崩溃或任意代码执行。 2764e41f4b71Sopenharmony_ci 2765e41f4b71Sopenharmony_ci```c 2766e41f4b71Sopenharmony_cistatic int incorrect_mmap(struct file *file, struct vm_area_struct *vma) 2767e41f4b71Sopenharmony_ci{ 2768e41f4b71Sopenharmony_ci unsigned long size; 2769e41f4b71Sopenharmony_ci size = vma->vm_end - vma->vm_start; 2770e41f4b71Sopenharmony_ci vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); 2771e41f4b71Sopenharmony_ci //错误:未对映射起始地址、空间大小做合法性校验 2772e41f4b71Sopenharmony_ci if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot)) { 2773e41f4b71Sopenharmony_ci err_log("%s, remap_pfn_range fail", __func__); 2774e41f4b71Sopenharmony_ci return EFAULT; 2775e41f4b71Sopenharmony_ci } else { 2776e41f4b71Sopenharmony_ci vma->vm_flags &= ~VM_IO; 2777e41f4b71Sopenharmony_ci } 2778e41f4b71Sopenharmony_ci 2779e41f4b71Sopenharmony_ci return EOK; 2780e41f4b71Sopenharmony_ci} 2781e41f4b71Sopenharmony_ci``` 2782e41f4b71Sopenharmony_ci 2783e41f4b71Sopenharmony_ci**【正确代码示例】** 2784e41f4b71Sopenharmony_ci 2785e41f4b71Sopenharmony_ci增加对映射起始地址等参数的合法性校验。 2786e41f4b71Sopenharmony_ci 2787e41f4b71Sopenharmony_ci```c 2788e41f4b71Sopenharmony_cistatic int correct_mmap(struct file *file, struct vm_area_struct *vma) 2789e41f4b71Sopenharmony_ci{ 2790e41f4b71Sopenharmony_ci unsigned long size; 2791e41f4b71Sopenharmony_ci size = vma->vm_end - vma->vm_start; 2792e41f4b71Sopenharmony_ci //修改:添加校验函数,验证映射起始地址、空间大小是否合法 2793e41f4b71Sopenharmony_ci if (!valid_mmap_phys_addr_range(vma->vm_pgoff, size)) { 2794e41f4b71Sopenharmony_ci return EINVAL; 2795e41f4b71Sopenharmony_ci } 2796e41f4b71Sopenharmony_ci 2797e41f4b71Sopenharmony_ci vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); 2798e41f4b71Sopenharmony_ci if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot)) { 2799e41f4b71Sopenharmony_ci err_log( "%s, remap_pfn_range fail ", __func__); 2800e41f4b71Sopenharmony_ci return EFAULT; 2801e41f4b71Sopenharmony_ci } else { 2802e41f4b71Sopenharmony_ci vma->vm_flags &= ~VM_IO; 2803e41f4b71Sopenharmony_ci } 2804e41f4b71Sopenharmony_ci 2805e41f4b71Sopenharmony_ci return EOK; 2806e41f4b71Sopenharmony_ci} 2807e41f4b71Sopenharmony_ci``` 2808e41f4b71Sopenharmony_ci 2809e41f4b71Sopenharmony_ci## 内核程序中必须使用内核专用函数读写用户态缓冲区 2810e41f4b71Sopenharmony_ci 2811e41f4b71Sopenharmony_ci**【描述】** 2812e41f4b71Sopenharmony_ci 2813e41f4b71Sopenharmony_ci用户态与内核态之间进行数据交换时,如果在内核中不加任何校验(如校验地址范围、空指针)而直接引用用户态传入指针,当用户态传入非法指针时,可导致内核崩溃、任意地址读写等问题。因此,应当禁止使用memcpy()、sprintf()等危险函数,而是使用内核提供的专用函数:copy_from_user()、copy_to_user()、put_user()和get_user()来读写用户态缓冲区,这些函数内部添加了入参校验功能。 2814e41f4b71Sopenharmony_ci 2815e41f4b71Sopenharmony_ci所有禁用函数列表为:memcpy()、bcopy()、memmove()、strcpy()、strncpy()、strcat()、strncat()、sprintf()、vsprintf()、snprintf()、vsnprintf()、sscanf()、vsscanf()。 2816e41f4b71Sopenharmony_ci 2817e41f4b71Sopenharmony_ci**【错误代码示例】** 2818e41f4b71Sopenharmony_ci 2819e41f4b71Sopenharmony_ci内核态直接使用用户态传入的buf指针作为snprintf()的参数,当buf为NULL时,可导致内核崩溃。 2820e41f4b71Sopenharmony_ci 2821e41f4b71Sopenharmony_ci```c 2822e41f4b71Sopenharmony_cissize_t incorrect_show(struct file *file, char__user *buf, size_t size, loff_t *data) 2823e41f4b71Sopenharmony_ci{ 2824e41f4b71Sopenharmony_ci // 错误:直接引用用户态传入指针,如果buf为NULL,则空指针异常导致内核崩溃 2825e41f4b71Sopenharmony_ci return snprintf(buf, size, "%ld\n", debug_level); 2826e41f4b71Sopenharmony_ci} 2827e41f4b71Sopenharmony_ci``` 2828e41f4b71Sopenharmony_ci 2829e41f4b71Sopenharmony_ci**【正确代码示例】** 2830e41f4b71Sopenharmony_ci 2831e41f4b71Sopenharmony_ci使用copy_to_user()函数代替snprintf()。 2832e41f4b71Sopenharmony_ci 2833e41f4b71Sopenharmony_ci```c 2834e41f4b71Sopenharmony_cissize_t correct_show(struct file *file, char __user *buf, size_t size, loff_t *data) 2835e41f4b71Sopenharmony_ci{ 2836e41f4b71Sopenharmony_ci int ret = 0; 2837e41f4b71Sopenharmony_ci char level_str[MAX_STR_LEN] = {0}; 2838e41f4b71Sopenharmony_ci snprintf(level_str, MAX_STR_LEN, "%ld \n", debug_level); 2839e41f4b71Sopenharmony_ci if(strlen(level_str) >= size) { 2840e41f4b71Sopenharmony_ci return EFAULT; 2841e41f4b71Sopenharmony_ci } 2842e41f4b71Sopenharmony_ci 2843e41f4b71Sopenharmony_ci // 修改:使用专用函数copy_to_user()将数据写入到用户态buf,并注意防止缓冲区溢出 2844e41f4b71Sopenharmony_ci ret = copy_to_user(buf, level_str, strlen(level_str)+1); 2845e41f4b71Sopenharmony_ci return ret; 2846e41f4b71Sopenharmony_ci} 2847e41f4b71Sopenharmony_ci``` 2848e41f4b71Sopenharmony_ci 2849e41f4b71Sopenharmony_ci**【错误代码示例】** 2850e41f4b71Sopenharmony_ci 2851e41f4b71Sopenharmony_ci内核态直接使用用户态传入的指针user_buf作为数据源进行memcpy()操作,当user_buf为NULL时,可导致内核崩溃。 2852e41f4b71Sopenharmony_ci 2853e41f4b71Sopenharmony_ci```c 2854e41f4b71Sopenharmony_cisize_t incorrect_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) 2855e41f4b71Sopenharmony_ci{ 2856e41f4b71Sopenharmony_ci ... 2857e41f4b71Sopenharmony_ci char buf [128] = {0}; 2858e41f4b71Sopenharmony_ci int buf_size = 0; 2859e41f4b71Sopenharmony_ci buf_size = min(count, (sizeof(buf)-1)); 2860e41f4b71Sopenharmony_ci // 错误:直接引用用户态传入指针,如果user_buf为NULL,则可导致内核崩溃 2861e41f4b71Sopenharmony_ci (void)memcpy(buf, user_buf, buf_size); 2862e41f4b71Sopenharmony_ci ... 2863e41f4b71Sopenharmony_ci} 2864e41f4b71Sopenharmony_ci``` 2865e41f4b71Sopenharmony_ci 2866e41f4b71Sopenharmony_ci**【正确代码示例】** 2867e41f4b71Sopenharmony_ci 2868e41f4b71Sopenharmony_ci使用copy_from_user()函数代替memcpy()。 2869e41f4b71Sopenharmony_ci 2870e41f4b71Sopenharmony_ci```c 2871e41f4b71Sopenharmony_cissize_t correct_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) 2872e41f4b71Sopenharmony_ci{ 2873e41f4b71Sopenharmony_ci ... 2874e41f4b71Sopenharmony_ci char buf[128] = {0}; 2875e41f4b71Sopenharmony_ci int buf_size = 0; 2876e41f4b71Sopenharmony_ci 2877e41f4b71Sopenharmony_ci buf_size = min(count, (sizeof(buf)-1)); 2878e41f4b71Sopenharmony_ci // 修改:使用专用函数copy_from_user()将数据写入到内核态buf,并注意防止缓冲区溢出 2879e41f4b71Sopenharmony_ci if (copy_from_user(buf, user_buf, buf_size)) { 2880e41f4b71Sopenharmony_ci return EFAULT; 2881e41f4b71Sopenharmony_ci } 2882e41f4b71Sopenharmony_ci 2883e41f4b71Sopenharmony_ci ... 2884e41f4b71Sopenharmony_ci} 2885e41f4b71Sopenharmony_ci``` 2886e41f4b71Sopenharmony_ci 2887e41f4b71Sopenharmony_ci## 必须对copy_from_user()拷贝长度进行校验,防止缓冲区溢出 2888e41f4b71Sopenharmony_ci 2889e41f4b71Sopenharmony_ci**说明**:内核态从用户态拷贝数据时通常使用copy_from_user()函数,如果未对拷贝长度做校验或者校验不当,会造成内核缓冲区溢出,导致内核panic或提权。 2890e41f4b71Sopenharmony_ci 2891e41f4b71Sopenharmony_ci**【错误代码示例】** 2892e41f4b71Sopenharmony_ci 2893e41f4b71Sopenharmony_ci未校验拷贝长度。 2894e41f4b71Sopenharmony_ci 2895e41f4b71Sopenharmony_ci```c 2896e41f4b71Sopenharmony_cistatic long gser_ioctl(struct file *fp, unsigned cmd, unsigned long arg) 2897e41f4b71Sopenharmony_ci{ 2898e41f4b71Sopenharmony_ci char smd_write_buf[GSERIAL_BUF_LEN]; 2899e41f4b71Sopenharmony_ci switch (cmd) 2900e41f4b71Sopenharmony_ci { 2901e41f4b71Sopenharmony_ci case GSERIAL_SMD_WRITE: 2902e41f4b71Sopenharmony_ci if (copy_from_user(&smd_write_arg, argp, sizeof(smd_write_arg))) {...} 2903e41f4b71Sopenharmony_ci // 错误:拷贝长度参数smd_write_arg.size由用户输入,未校验 2904e41f4b71Sopenharmony_ci copy_from_user(smd_write_buf, smd_write_arg.buf, smd_write_arg.size); 2905e41f4b71Sopenharmony_ci ... 2906e41f4b71Sopenharmony_ci } 2907e41f4b71Sopenharmony_ci} 2908e41f4b71Sopenharmony_ci``` 2909e41f4b71Sopenharmony_ci 2910e41f4b71Sopenharmony_ci**【正确代码示例】** 2911e41f4b71Sopenharmony_ci 2912e41f4b71Sopenharmony_ci添加长度校验。 2913e41f4b71Sopenharmony_ci 2914e41f4b71Sopenharmony_ci```c 2915e41f4b71Sopenharmony_cistatic long gser_ioctl(struct file *fp, unsigned cmd, unsigned long arg) 2916e41f4b71Sopenharmony_ci{ 2917e41f4b71Sopenharmony_ci char smd_write_buf[GSERIAL_BUF_LEN]; 2918e41f4b71Sopenharmony_ci switch (cmd) 2919e41f4b71Sopenharmony_ci { 2920e41f4b71Sopenharmony_ci case GSERIAL_SMD_WRITE: 2921e41f4b71Sopenharmony_ci if (copy_from_user(&smd_write_arg, argp, sizeof(smd_write_arg))){...} 2922e41f4b71Sopenharmony_ci // 修改:添加校验 2923e41f4b71Sopenharmony_ci if (smd_write_arg.size >= GSERIAL_BUF_LEN) {......} 2924e41f4b71Sopenharmony_ci copy_from_user(smd_write_buf, smd_write_arg.buf, smd_write_arg.size); 2925e41f4b71Sopenharmony_ci ... 2926e41f4b71Sopenharmony_ci } 2927e41f4b71Sopenharmony_ci} 2928e41f4b71Sopenharmony_ci``` 2929e41f4b71Sopenharmony_ci 2930e41f4b71Sopenharmony_ci## 必须对copy_to_user()拷贝的数据进行初始化,防止信息泄漏 2931e41f4b71Sopenharmony_ci 2932e41f4b71Sopenharmony_ci**【描述】** 2933e41f4b71Sopenharmony_ci 2934e41f4b71Sopenharmony_ci**说明**:内核态使用copy_to_user()向用户态拷贝数据时,当数据未完全初始化(如结构体成员未赋值、字节对齐引起的内存空洞等),会导致栈上指针等敏感信息泄漏。攻击者可利用绕过kaslr等安全机制。 2935e41f4b71Sopenharmony_ci 2936e41f4b71Sopenharmony_ci**【错误代码示例】** 2937e41f4b71Sopenharmony_ci 2938e41f4b71Sopenharmony_ci未完全初始化数据结构成员。 2939e41f4b71Sopenharmony_ci 2940e41f4b71Sopenharmony_ci```c 2941e41f4b71Sopenharmony_cistatic long rmnet_ctrl_ioctl(struct file *fp, unsigned cmd, unsigned long arg) 2942e41f4b71Sopenharmony_ci{ 2943e41f4b71Sopenharmony_ci struct ep_info info; 2944e41f4b71Sopenharmony_ci switch (cmd) { 2945e41f4b71Sopenharmony_ci case FRMNET_CTRL_EP_LOOKUP: 2946e41f4b71Sopenharmony_ci info.ph_ep_info.ep_type = DATA_EP_TYPE_HSUSB; 2947e41f4b71Sopenharmony_ci info.ipa_ep_pair.cons_pipe_num = port->ipa_cons_idx; 2948e41f4b71Sopenharmony_ci info.ipa_ep_pair.prod_pipe_num = port->ipa_prod_idx; 2949e41f4b71Sopenharmony_ci // 错误: info结构体有4个成员,未全部赋值 2950e41f4b71Sopenharmony_ci ret = copy_to_user((void __user *)arg, &info, sizeof(info)); 2951e41f4b71Sopenharmony_ci ... 2952e41f4b71Sopenharmony_ci } 2953e41f4b71Sopenharmony_ci} 2954e41f4b71Sopenharmony_ci``` 2955e41f4b71Sopenharmony_ci 2956e41f4b71Sopenharmony_ci**【正确代码示例】** 2957e41f4b71Sopenharmony_ci 2958e41f4b71Sopenharmony_ci全部进行初始化。 2959e41f4b71Sopenharmony_ci 2960e41f4b71Sopenharmony_ci```c 2961e41f4b71Sopenharmony_cistatic long rmnet_ctrl_ioctl(struct file *fp, unsigned cmd, unsigned long arg) 2962e41f4b71Sopenharmony_ci{ 2963e41f4b71Sopenharmony_ci struct ep_info info; 2964e41f4b71Sopenharmony_ci // 修改:使用memset初始化缓冲区,保证不存在因字节对齐或未赋值导致的内存空洞 2965e41f4b71Sopenharmony_ci (void)memset(&info, '0', sizeof(ep_info)); 2966e41f4b71Sopenharmony_ci switch (cmd) { 2967e41f4b71Sopenharmony_ci case FRMNET_CTRL_EP_LOOKUP: 2968e41f4b71Sopenharmony_ci info.ph_ep_info.ep_type = DATA_EP_TYPE_HSUSB; 2969e41f4b71Sopenharmony_ci info.ipa_ep_pair.cons_pipe_num = port->ipa_cons_idx; 2970e41f4b71Sopenharmony_ci info.ipa_ep_pair.prod_pipe_num = port->ipa_prod_idx; 2971e41f4b71Sopenharmony_ci ret = copy_to_user((void __user *)arg, &info, sizeof(info)); 2972e41f4b71Sopenharmony_ci ... 2973e41f4b71Sopenharmony_ci } 2974e41f4b71Sopenharmony_ci} 2975e41f4b71Sopenharmony_ci``` 2976e41f4b71Sopenharmony_ci 2977e41f4b71Sopenharmony_ci## 禁止在异常处理中使用BUG_ON宏,避免造成内核panic 2978e41f4b71Sopenharmony_ci 2979e41f4b71Sopenharmony_ci**【描述】** 2980e41f4b71Sopenharmony_ci 2981e41f4b71Sopenharmony_ciBUG_ON宏会调用内核的panic()函数,打印错误信息并主动崩溃系统,在正常逻辑处理中(如ioctl接口的cmd参数不识别)不应当使系统崩溃,禁止在此类异常处理场景中使用BUG_ON宏,推荐使用WARN_ON宏。 2982e41f4b71Sopenharmony_ci 2983e41f4b71Sopenharmony_ci**【错误代码示例】** 2984e41f4b71Sopenharmony_ci 2985e41f4b71Sopenharmony_ci正常流程中使用了BUG_ON宏 2986e41f4b71Sopenharmony_ci 2987e41f4b71Sopenharmony_ci```c 2988e41f4b71Sopenharmony_ci/ * 判断Q6侧设置定时器是否繁忙,1-忙,0-不忙 */ 2989e41f4b71Sopenharmony_cistatic unsigned int is_modem_set_timer_busy(special_timer *smem_ptr) 2990e41f4b71Sopenharmony_ci{ 2991e41f4b71Sopenharmony_ci int i = 0; 2992e41f4b71Sopenharmony_ci if (smem_ptr == NULL) { 2993e41f4b71Sopenharmony_ci printk(KERN_EMERG"%s:smem_ptr NULL!\n", __FUNCTION__); 2994e41f4b71Sopenharmony_ci // 错误:系统BUG_ON宏打印调用栈后调用panic(),导致内核拒绝服务,不应在正常流程中使用 2995e41f4b71Sopenharmony_ci BUG_ON(1); 2996e41f4b71Sopenharmony_ci return 1; 2997e41f4b71Sopenharmony_ci } 2998e41f4b71Sopenharmony_ci 2999e41f4b71Sopenharmony_ci ... 3000e41f4b71Sopenharmony_ci} 3001e41f4b71Sopenharmony_ci``` 3002e41f4b71Sopenharmony_ci 3003e41f4b71Sopenharmony_ci**【正确代码示例】** 3004e41f4b71Sopenharmony_ci 3005e41f4b71Sopenharmony_ci去掉BUG_ON宏。 3006e41f4b71Sopenharmony_ci 3007e41f4b71Sopenharmony_ci```c 3008e41f4b71Sopenharmony_ci/ * 判断Q6侧设置定时器是否繁忙,1-忙,0-不忙 */ 3009e41f4b71Sopenharmony_cistatic unsigned int is_modem_set_timer_busy(special_timer *smem_ptr) 3010e41f4b71Sopenharmony_ci{ 3011e41f4b71Sopenharmony_ci int i = 0; 3012e41f4b71Sopenharmony_ci if (smem_ptr == NULL) { 3013e41f4b71Sopenharmony_ci printk(KERN_EMERG"%s:smem_ptr NULL!\n", __FUNCTION__); 3014e41f4b71Sopenharmony_ci // 修改:去掉BUG_ON调用,或使用WARN_ON 3015e41f4b71Sopenharmony_ci return 1; 3016e41f4b71Sopenharmony_ci } 3017e41f4b71Sopenharmony_ci 3018e41f4b71Sopenharmony_ci ... 3019e41f4b71Sopenharmony_ci} 3020e41f4b71Sopenharmony_ci``` 3021e41f4b71Sopenharmony_ci 3022e41f4b71Sopenharmony_ci## 在中断处理程序或持有自旋锁的进程上下文代码中,禁止使用会引起进程休眠的函数 3023e41f4b71Sopenharmony_ci 3024e41f4b71Sopenharmony_ci**【描述】** 3025e41f4b71Sopenharmony_ci 3026e41f4b71Sopenharmony_ci系统以进程为调度单位,在中断上下文中,只有更高优先级的中断才能将其打断,系统在中断处理的时候不能进行进程调度。如果中断处理程序处于休眠状态,就会导致内核无法唤醒,从而使得内核处于瘫痪。 3027e41f4b71Sopenharmony_ci 3028e41f4b71Sopenharmony_ci自旋锁在使用时,抢占是失效的。若自旋锁在锁住以后进入睡眠,由于不能进行处理器抢占,其它进程都将因为不能获得CPU(单核CPU)而停止运行,对外表现为系统将不作任何响应,出现挂死。 3029e41f4b71Sopenharmony_ci 3030e41f4b71Sopenharmony_ci因此,在中断处理程序或持有自旋锁的进程上下文代码中,应该禁止使用可能会引起休眠(如vmalloc()、msleep()等)、阻塞(如copy_from_user(),copy_to_user()等)或者耗费大量时间(如printk()等)的函数。 3031e41f4b71Sopenharmony_ci 3032e41f4b71Sopenharmony_ci## 合理使用内核栈,防止内核栈溢出 3033e41f4b71Sopenharmony_ci 3034e41f4b71Sopenharmony_ci**【描述】** 3035e41f4b71Sopenharmony_ci 3036e41f4b71Sopenharmony_ci内核栈大小是固定的(一般32位系统为8K,64位系统为16K,因此资源非常宝贵。不合理的使用内核栈,可能会导致栈溢出,造成系统挂死。因此需要做到以下几点: 3037e41f4b71Sopenharmony_ci 3038e41f4b71Sopenharmony_ci- 在栈上申请内存空间不要超过内核栈大小; 3039e41f4b71Sopenharmony_ci- 注意函数的嵌套使用次数; 3040e41f4b71Sopenharmony_ci- 不要定义过多的变量。 3041e41f4b71Sopenharmony_ci 3042e41f4b71Sopenharmony_ci**【错误代码示例】** 3043e41f4b71Sopenharmony_ci 3044e41f4b71Sopenharmony_ci以下代码中定义的变量过大,导致栈溢出。 3045e41f4b71Sopenharmony_ci 3046e41f4b71Sopenharmony_ci```c 3047e41f4b71Sopenharmony_ci... 3048e41f4b71Sopenharmony_cistruct result 3049e41f4b71Sopenharmony_ci{ 3050e41f4b71Sopenharmony_ci char name[4]; 3051e41f4b71Sopenharmony_ci unsigned int a; 3052e41f4b71Sopenharmony_ci unsigned int b; 3053e41f4b71Sopenharmony_ci unsigned int c; 3054e41f4b71Sopenharmony_ci unsigned int d; 3055e41f4b71Sopenharmony_ci}; // 结构体result的大小为20字节 3056e41f4b71Sopenharmony_ci 3057e41f4b71Sopenharmony_ciint foo() 3058e41f4b71Sopenharmony_ci{ 3059e41f4b71Sopenharmony_ci struct result temp[512]; 3060e41f4b71Sopenharmony_ci // 错误: temp数组含有512个元素,总大小为10K,远超内核栈大小 3061e41f4b71Sopenharmony_ci (void)memset(temp, 0, sizeof(result) * 512); 3062e41f4b71Sopenharmony_ci ... // use temp do something 3063e41f4b71Sopenharmony_ci return 0; 3064e41f4b71Sopenharmony_ci} 3065e41f4b71Sopenharmony_ci 3066e41f4b71Sopenharmony_ci... 3067e41f4b71Sopenharmony_ci``` 3068e41f4b71Sopenharmony_ci 3069e41f4b71Sopenharmony_ci代码中数组temp有512个元素,总共10K大小,远超内核的8K,明显的栈溢出。 3070e41f4b71Sopenharmony_ci 3071e41f4b71Sopenharmony_ci**【正确代码示例】** 3072e41f4b71Sopenharmony_ci 3073e41f4b71Sopenharmony_ci使用kmalloc()代替之。 3074e41f4b71Sopenharmony_ci 3075e41f4b71Sopenharmony_ci```c 3076e41f4b71Sopenharmony_ci... 3077e41f4b71Sopenharmony_cistruct result 3078e41f4b71Sopenharmony_ci{ 3079e41f4b71Sopenharmony_ci char name[4]; 3080e41f4b71Sopenharmony_ci unsigned int a; 3081e41f4b71Sopenharmony_ci unsigned int b; 3082e41f4b71Sopenharmony_ci unsigned int c; 3083e41f4b71Sopenharmony_ci unsigned int d; 3084e41f4b71Sopenharmony_ci}; // 结构体result的大小为20字节 3085e41f4b71Sopenharmony_ci 3086e41f4b71Sopenharmony_ciint foo() 3087e41f4b71Sopenharmony_ci{ 3088e41f4b71Sopenharmony_ci struct result *temp = NULL; 3089e41f4b71Sopenharmony_ci temp = (result *)kmalloc(sizeof(result) * 512, GFP_KERNEL); //修改:使用kmalloc()申请内存 3090e41f4b71Sopenharmony_ci ... // check temp is not NULL 3091e41f4b71Sopenharmony_ci (void)memset(temp, 0, sizeof(result) * 512); 3092e41f4b71Sopenharmony_ci ... // use temp do something 3093e41f4b71Sopenharmony_ci ... // free temp 3094e41f4b71Sopenharmony_ci return 0; 3095e41f4b71Sopenharmony_ci} 3096e41f4b71Sopenharmony_ci... 3097e41f4b71Sopenharmony_ci``` 3098e41f4b71Sopenharmony_ci 3099e41f4b71Sopenharmony_ci## 临时关闭地址校验机制后,在操作完成后必须及时恢复 3100e41f4b71Sopenharmony_ci 3101e41f4b71Sopenharmony_ci**【描述】** 3102e41f4b71Sopenharmony_ci 3103e41f4b71Sopenharmony_ciSMEP安全机制是指禁止内核执行用户空间的代码(PXN是ARM版本的SMEP)。系统调用(如open(),write()等)本来是提供给用户空间程序访问的。默认情况下,这些函数会对传入的参数地址进行校验,如果入参是非用户空间地址则报错。因此,要在内核程序中使用这些系统调用,就必须使参数地址校验功能失效。set_fs()/get_fs()就用来解决该问题。详细说明见如下代码: 3104e41f4b71Sopenharmony_ci 3105e41f4b71Sopenharmony_ci```c 3106e41f4b71Sopenharmony_ci... 3107e41f4b71Sopenharmony_cimmegment_t old_fs; 3108e41f4b71Sopenharmony_ciprintk("Hello, I'm the module that intends to write message to file.\n"); 3109e41f4b71Sopenharmony_ciif (file == NULL) { 3110e41f4b71Sopenharmony_ci file = filp_open(MY_FILE, O_RDWR | O_APPEND | O_CREAT, 0664); 3111e41f4b71Sopenharmony_ci} 3112e41f4b71Sopenharmony_ci 3113e41f4b71Sopenharmony_ciif (IS_ERR(file)) { 3114e41f4b71Sopenharmony_ci printk("Error occurred while opening file %s, exiting ...\n", MY_FILE); 3115e41f4b71Sopenharmony_ci return 0; 3116e41f4b71Sopenharmony_ci} 3117e41f4b71Sopenharmony_ci 3118e41f4b71Sopenharmony_cisprintf(buf, "%s", "The Message."); 3119e41f4b71Sopenharmony_ciold_fs = get_fs(); // get_fs()的作用是获取用户空间地址上限值 3120e41f4b71Sopenharmony_ci // #define get_fs() (current->addr_limit 3121e41f4b71Sopenharmony_ciset_fs(KERNEL_DS); // set_fs的作用是将地址空间上限扩大到KERNEL_DS,这样内核代码可以调用系统函数 3122e41f4b71Sopenharmony_cifile->f_op->write(file, (char *)buf, sizeof(buf), &file->f_pos); // 内核代码可以调用write()函数 3123e41f4b71Sopenharmony_ciset_fs(old_fs); // 使用完后及时恢复原来用户空间地址限制值 3124e41f4b71Sopenharmony_ci... 3125e41f4b71Sopenharmony_ci``` 3126e41f4b71Sopenharmony_ci 3127e41f4b71Sopenharmony_ci通过上述代码,可以了解到最为关键的就是操作完成后,要及时恢复地址校验功能。否则SMEP/PXN安全机制就会失效,使得许多漏洞的利用变得很容易。 3128e41f4b71Sopenharmony_ci 3129e41f4b71Sopenharmony_ci**【错误代码示例】** 3130e41f4b71Sopenharmony_ci 3131e41f4b71Sopenharmony_ci在程序错误处理分支,未通过set_fs()恢复地址校验功能。 3132e41f4b71Sopenharmony_ci 3133e41f4b71Sopenharmony_ci```c 3134e41f4b71Sopenharmony_ci... 3135e41f4b71Sopenharmony_cioldfs = get_fs(); 3136e41f4b71Sopenharmony_ciset_fs(KERNEL_DS); 3137e41f4b71Sopenharmony_ci/* 在时间戳目录下面创建done文件 */ 3138e41f4b71Sopenharmony_cifd = sys_open(path, O_CREAT | O_WRONLY, FILE_LIMIT); 3139e41f4b71Sopenharmony_ciif (fd < 0) { 3140e41f4b71Sopenharmony_ci BB_PRINT_ERR("sys_mkdir[%s] error, fd is[%d]\n", path, fd); 3141e41f4b71Sopenharmony_ci return; // 错误:在错误处理程序分支未恢复地址校验机制 3142e41f4b71Sopenharmony_ci} 3143e41f4b71Sopenharmony_ci 3144e41f4b71Sopenharmony_cisys_close(fd); 3145e41f4b71Sopenharmony_ciset_fs(oldfs); 3146e41f4b71Sopenharmony_ci... 3147e41f4b71Sopenharmony_ci``` 3148e41f4b71Sopenharmony_ci 3149e41f4b71Sopenharmony_ci**【正确代码示例】** 3150e41f4b71Sopenharmony_ci 3151e41f4b71Sopenharmony_ci在错误处理程序中恢复地址校验功能。 3152e41f4b71Sopenharmony_ci 3153e41f4b71Sopenharmony_ci```c 3154e41f4b71Sopenharmony_ci... 3155e41f4b71Sopenharmony_cioldfs = get_fs(); 3156e41f4b71Sopenharmony_ciset_fs(KERNEL_DS); 3157e41f4b71Sopenharmony_ci 3158e41f4b71Sopenharmony_ci/* 在时间戳目录下面创建done文件 */ 3159e41f4b71Sopenharmony_cifd = sys_open(path, O_CREAT | O_WRONLY, FILE_LIMIT); 3160e41f4b71Sopenharmony_ciif (fd < 0) { 3161e41f4b71Sopenharmony_ci BB_PRINT_ERR("sys_mkdir[%s] error, fd is[%d] \n", path, fd); 3162e41f4b71Sopenharmony_ci set_fs(oldfs); // 修改:在错误处理程序分支中恢复地址校验机制 3163e41f4b71Sopenharmony_ci return; 3164e41f4b71Sopenharmony_ci} 3165e41f4b71Sopenharmony_ci 3166e41f4b71Sopenharmony_cisys_close(fd); 3167e41f4b71Sopenharmony_ciset_fs(oldfs); 3168e41f4b71Sopenharmony_ci... 3169e41f4b71Sopenharmony_ci``` 3170e41f4b71Sopenharmony_ci 3171