原文链接:

INT50-CPP. Do not cast to an out-of-range enumeration value


C++ 中枚举 (enumeration) 值有两种形式: 范围 枚举值 – 底层类型是固定的, 非范围 枚举值 – 底层类型不确定是否是固定的. 以枚举形式表示的值范围也可能包含未被枚举本身指定的值. C++ Standard, [dcl.enum], in paragraph 8 [ISO/IEC 14882-2014] 定义了枚举类型合法的枚举值范围:

对于底层类型是固定的枚举, 枚举值就是底层类型的值. 换句话说, 对于一个枚举器 (enumerator) 最小为 emin , 最大为 emax 的枚举, 枚举值在范围 bminbmax 之间, 有如下定义: 以 K = 1来表示补码 (two’s complement) , 0 表示反码 (one’s complement) 或者原码 (sign-magnitude) . bmax 大于等于 max(|emin| − K, |emax|) , 且等于 2M − 1, M 为一非负整数. 若 emin 是非负数, bmin 等于零, 否则等于 −(bmax + K) . 若 bmin 等于零, 则可足够容纳该枚举类型所有值的位域 (bit-field) 大小为 Max(M, 1) , 否则为 M + 1 . 若枚举可能包含那些没有被任一枚举器定义的值. 如果枚举器列表是空的, 枚举值和只包含值为 0 的单个枚举器的枚举类似.

For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two’s complement representation and 0 for a one’s complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| − K, |emax|) and equal to 2M − 1, where M is a non-negative integer. bmin is zero if emin is non-negative and −(bmax + K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M, 1) if bmin is zero and M + 1 otherwise. It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.

The C++ Standard, [expr.static.cast], paragraph 10, 陈述如下:

整型和枚举类型的值可以被显式转换为一个枚举类型. 如果原值在枚举值范围内, 则结果值不会变化 (7.2). 否则, 该值是不确定的 (并且可能不在范围内). 一个浮点类型的值也可以被显式转换为一个枚举类型. 结果值和将原值转换为枚举底层类型是一样的 (4.9), 并且之后为枚举类型.

A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the resulting value is unspecified (and might not be in that range). A value of floating-point type can also be explicitly converted to an enumeration type. The resulting value is the same as converting the original value to the underlying type of the enumeration (4.9), and subsequently to the enumeration type.

为避免产生 未指定值, unspecified values, 被转换的算数值必须在枚举可表示的值的范围内. 当动态检查越界的值时, 检查必须在转换之前执行.

不合规代码示例 (Bounds Checking)

这个不合规代码示例试图检查给定的值是否在枚举值可接受的范围内. 然而, 这在转换为枚举类型之后才做, 可能不能够来表示给定的整型值. 在补码系统中, 可被 EnumType 表示值的合法范围是 [0..3], 所以如果将越界的值传入到 f() , 转换为 EnumType 将会得到一个未指定值, 如果在 if 语句中使用该值则导致 未定义行为, unspecified behavior.

1
2
3
4
5
6
7
8
9
10
11
12
13
enum EnumType {
First,
Second,
Third
};

void f(int intVar) {
EnumType enumVar = static_cast<EnumType>(intVar);

if (enumVar < First || enumVar > Third) {
// Handle error
}
}

合规方案 (Bounds Checking)

这个合规方案在执行转换前检查了该值是否能被枚举类型表达来保证转换不会产生未指定的值. 通过限制被转换的值转换为一个已有指定的枚举器值来完成的.

1
2
3
4
5
6
7
8
9
10
11
12
enum EnumType {
First,
Second,
Third
};

void f(int intVar) {
if (intVar < First || intVar > Third) {
// Handle error
}
EnumType enumVar = static_cast<EnumType>(intVar);
}

合规方案 (Scoped Enumeration)

这个合规方案使用范围枚举 – 默认有一个固定的底层 int 类型 – 允许任意参数值被转换到合法的枚举值. 这没有限制被转换的值需要有个指定的枚举器值, 但是这也可以达到前述合规方案所示的功能.

This compliant solution uses a scoped enumeration, which has a fixed underlying int type by default, allowing any value from the parameter to be converted into a valid enumeration value. It does not restrict the converted value to one for which there is a specific enumerator value, but it could do so as shown in the previous compliant solution.

1
2
3
4
5
6
7
8
9
enum class EnumType {
First,
Second,
Third
};

void f(int intVar) {
EnumType enumVar = static_cast<EnumType>(intVar);
}

合规方案 (Fixed Unscoped Enumeration)

类似前述的合规方案, 这个合规方案使用一个非范围枚举, 但提供了固定的底层 int 类型, 允许传入参数可被转换为合法的枚举值.

1
2
3
4
5
6
7
8
9
enum EnumType : int {
First,
Second,
Third
};

void f(int intVar) {
EnumType enumVar = static_cast<EnumType>(intVar);
}

虽然和前述合规方案类似, 但是该方案与不合规代码示例在枚举器值表示上有所不同, 并且可允许隐式转换. 前述合规方案需要嵌套名称修饰符来区分枚举器 (例如, EnumType::First), 且不能将枚举器值隐式转换 int . 和不合规代码示例一样, 该合规方案不允许嵌套名称修饰符, 但可以隐式将枚举器值隐式转化为 int .

风险评估

非指定值有可能造成缓冲溢出, 导致被攻击者执行任意代码. 然而, 因为枚举器很少被用来数组或其他指针运算的下标, 更可能导致数据整型冲突而非执行任意代码的情景.

Rule Severity Likelihood Remediation Cost Priority Level
INT50-CPP Medium Unlikely Medium P4 L3

Automated Detection

Tool Version Checker Description
Axivion Bauhaus Suite 7.2.0 CertC++-INT50
Helix QAC 2021.2 C++3013
Parasoft C/C++test 2021.1 CERT_CPP-INT50-a An expression with enum underlying type shall only have values corresponding to the enumerators of the enumeration
PRQA QA-C++ 4.4 3013
PVS-Studio 7.15 V1016

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Bibliography

[Becker 2009] Section 7.2, “Enumeration Declarations”
[ISO/IEC 14882-2014] Subclause 5.2.9, “Static Cast” Subclause 7.2, “Enumeration Declarations”

img img img