From 8e9980079df84828ff677236ac93226e60223571 Mon Sep 17 00:00:00 2001 From: Huang Wei Date: Mon, 17 May 2021 10:52:47 +0800 Subject: [PATCH] add zh-cn/contribute/OpenHarmony C&C++ Secure Coding Guide-CN.md. --- ...penHarmony C&C++ Secure Coding Guide-CN.md | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 zh-cn/contribute/OpenHarmony C&C++ Secure Coding Guide-CN.md diff --git a/zh-cn/contribute/OpenHarmony C&C++ Secure Coding Guide-CN.md b/zh-cn/contribute/OpenHarmony C&C++ Secure Coding Guide-CN.md new file mode 100644 index 00000000000..1ba6fd8a8e6 --- /dev/null +++ b/zh-cn/contribute/OpenHarmony C&C++ Secure Coding Guide-CN.md @@ -0,0 +1,151 @@ +# OpenHarmony C&C++ 安全编程指南 + +本文档基于C&C++ 语言提供一些安全编程建议,用于指导开发实践。 + +# 函数 + +## 对所有外部数据进行合法性校验 + +**【描述】** +外部数据的来源包括但不限于:网络、用户输入、命令行、文件(包括程序的配置文件)、环境变量、用户态数据(对于内核程序)、进程间通信(包括管道、消息、共享内存、socket、RPC等,特别需要注意的是设备内部不同单板间通讯也属于进程间通信)、API参数、全局变量。 + +来自程序外部的数据通常被认为是不可信的,在使用这些数据之前,需要进行合法性校验。 +如果不对这些外部数据进行校验,将可能导致不可预期的安全风险。 + +注意:不要使用断言检查外部输入数据,断言应该用于防止不正确的程序假设,而不能用在发布版本上检查程序运行过程中发生的错误。 + +对来自程序外部的数据要校验处理后才能使用。典型场景包括: + +**作为数组索引** +将不可信的数据作为数组索引,可能导致超出数组上限,从而造成非法内存访问。 +**作为内存偏移地址** +将不可信数据作为指针偏移访问内存,可能造成非法内存访问,并可以造成进一步的危害,如任意地址读/写。 +**作为内存分配的尺寸参数** +使用0长度分配内存可能造成非法内存访问;未限制分配内存大小会造成过度资源消耗。 +**作为循环条件** +将不可信数据作为循环限定条件,可能会引发缓冲区溢出、内存越界读/写、死循环等问题。 +**作为除数** +可能产生除零错误(被零除)。 +**作为命令行参数** +可能产生命令注入漏洞。 +**作为数据库查询语句的参数** +可能产生SQL注入漏洞。 +**作为输入/输出格式化字符串** +可能产生格式化字符串漏洞。 +**作为内存复制长度** +可能造成缓冲区溢出问题。 +**作为文件路径** +直接打开不可信路径,可能会导致目录遍历攻击,攻击者操作了无权操作的文件,使得系统被攻击者所控制。 + +输入校验包括但不局限于: + +- API接口参数合法性 +- 校验数据长度 +- 校验数据范围 +- 校验数据类型和格式 +- 校验输入只包含可接受的字符(“白名单”形式),尤其需要注意一些特殊情况下的特殊字符。 + +**外部数据校验原则** +**1.信任边界** +由于外部数据不可信,因此系统在运行过程中,如果数据传输与处理跨越不同的信任边界,为了防止攻击蔓延,必须对来自信任边界外的其他模块的数据进行合法性校验。 +(a)so(或者dll)之间 +so或dll作为独立的第三方模块,用于对外导出公共的api函数,供其他模块进行函数调用。so/dll无法确定上层调用者是否传递了合法参数,因此so/dll的公共函数需要检查调用者提供参数的合法性。so/dll应该设计成低耦合、高复用性,尽管有些软件的so/dll当前设计成只在本软件中使用,但仍然应该将不同的so/dll模块视为不同的信任边界。 +(b)进程与进程之间 +为防止通过高权限进程提权,进程与进程之间的IPC通信(包括单板之间的IPC通信、不同主机间的网络通信),应视为不同信任边界。 +(c)应用层进程与操作系统内核 +操作系统内核具有比应用层更高的权限,内核向应用层提供的接口,应该将来自应用层的数据作为不可信数据处理。 +(d)可信执行环境内外环境 +为防止攻击蔓延至可信执行环境,TEE、SGX等对外提供的接口,应该将来自外部的数据作为不可信数据处理。 + +**2.外部数据校验** +外部数据进入到本模块后,必须经过合法性校验才能使用。被校验后的合法数据,在本模块内,后续传递到内部其他子函数,不需要重复校验。 + +**【反例】** +函数Foo处理外部数据,由于buffer不一定是’\0’结尾, strlen 的返回值 nameLen 有可能超过 len,导致越界读取数据。 + +```cpp +void Foo(const unsigned char* buffer, size_t len) +{ + // buffer可能为空指针,不保证以'\0'结尾 + const char* s = reinterpret_cast(buffer); + size_t nameLen = strlen(s); + std::string name(s, nameLen); + Foo2(name); + ... +} +``` + +**【正例】** +对外部参数做合法性校验,本例中使用 strnlen 进行字符串长度计算,缓解读越界风险。 + +```cpp +void Foo(const unsigned char* buffer, size_t len) +{ + // 必须做参数合法性校验 + if (buffer == nullptr || len == 0 || len >= MAX_BUFFER_LEN) { + ... // 错误处理 + } + + const char* s = reinterpret_cast(buffer); + size_t nameLen = strnlen(s, len); // 使用strnlen缓解读越界风险 + if (nameLen == len) { + ... // 错误处理 + } + std::string name(s, nameLen); + ... + Foo2(name); + ... +} +``` + +```cpp +namespace ModuleA { +// Foo2 为模块内部函数,约定为由调用者保证参数的合法性 +static void Foo2(const std::string& name) +{ + ... + Bar(name.c_str()); // 调用MODULE_B中的函数 +} + +// Foo 为模块的外部接口,需要校验参数的合法性 +void Foo(const unsigned char* buffer, size_t len) +{ + // 检查空指针、参数合法范围等 + if (buffer == nullptr || len <= sizeof(int)) { + // 错误处理 + ... + } + + int nameLen = *(reinterpret_cast(buffer)); // 从报文中获取name字符串长度 + // nameLen 是不可信数据,必须检查合法性 + if (nameLen <= 0 || static_cast(nameLen) > len - sizeof(int)) { + // 错误处理 + ... + } + + std::string name(reinterpret_cast(buffer), nameLen); + Foo2(name); // 调用本模块内内部函数 + ... +} +} +``` + +以下是使用C语言编写的`MODULE_B`模块中的代码: + +```cpp +// Bar 为 MODULE_B 模块的公共函数, +// 其约定为,如果参数name不为nullptr,那么必须是一个具有’\0’结尾的合法字符串并且长度大于0 +void Bar(const char* name) +{ + // 必须做参数合法性校验 + if (name == nullptr || name[0] == '\0') { + // 错误处理 + ... + } + size_t nameLen = strlen(name); // 不需要使用strnlen + ... +} +``` + +对于模块A来说, buffer 是外部不可信输入,必须做严格的校验,从 buffer 解析出来的 name,在解析过程中进行了合法性校验,在模块A内部属于合法数据,作为参数传递给内部子函数时不需要再做合法性校验(如果要继续对 name 内容进行解析,那么仍然必须对 name 内容进行校验)。 +如果模块A中的 name 继续跨越信任面传递给其他模块(在本例中是直接调用模块B的公共函数,也可以是通过文件、管道、网络等方式),那么对于B模块来说, name 属于不可信数据,必须做合法性校验。 \ No newline at end of file -- Gitee