1. 可用检查器¶
分析器执行分类为系列或“检查器”的检查。
默认的检查器集涵盖了各种检查,这些检查旨在发现安全性和 API 使用错误、死代码以及其他逻辑错误。请参阅以下 默认检查器 列表。
除了这些检查器之外,分析器还包含一些 实验检查器(也称为alpha 检查器)。这些检查器正在开发中,默认情况下处于关闭状态。它们可能会崩溃或发出更多误报。
该 debug 包含用于分析器开发人员的用于调试目的的检查器。
1.1. 默认检查器¶
1.1.1. core¶
模拟核心语言特性,并包含通用检查器,例如除以零、空指针解引用、使用未初始化值等。这些检查器必须始终开启,因为其他检查器依赖于它们。
1.1.1.1. core.BitwiseShift (C, C++)¶
查找由位左移和右移运算符对整数类型进行操作引起的未定义行为。
默认情况下,此检查器仅报告右操作数为负数或大于左操作数类型位宽的情况;这些逻辑上不合理。
此外,如果通过 -analyzer-config core.BitwiseShift:Pedantic=true
激活了严格模式,则此检查器还会报告_左_操作数为负数或在有符号值的右移过程中发生溢出的情况。(大多数编译器可以预测地处理这些情况,但 C 标准和 C++20 之前的标准说它们是未定义的行为。在 C++20 标准中,这些构造是明确定义的,因此在 C++20 中激活严格模式没有效果。)
示例
static_assert(sizeof(int) == 4, "assuming 32-bit int")
void basic_examples(int a, int b) {
if (b < 0) {
b = a << b; // warn: right operand is negative in left shift
} else if (b >= 32) {
b = a >> b; // warn: right shift overflows the capacity of 'int'
}
}
int pedantic_examples(int a, int b) {
if (a < 0) {
return a >> b; // warn: left operand is negative in right shift
}
a = 1000u << 31; // OK, overflow of unsigned value is well-defined, a == 0
if (b > 10) {
a = b << 31; // this is undefined before C++20, but the checker doesn't
// warn because it doesn't know the exact value of b
}
return 1000 << 31; // warn: this overflows the capacity of 'int'
}
解决方案
确保移位操作数在移位之前处于适当的范围内。
1.1.1.2. core.CallAndMessage (C, C++, ObjC)¶
检查函数调用和 Objective-C 消息表达式的逻辑错误(例如,未初始化参数、空函数指针)。
//C
void test() {
void (*foo)(void);
foo = 0;
foo(); // warn: function pointer is null
}
// C++
class C {
public:
void f();
};
void test() {
C *pc;
pc->f(); // warn: object pointer is uninitialized
}
// C++
class C {
public:
void f();
};
void test() {
C *pc = 0;
pc->f(); // warn: object pointer is null
}
// Objective-C
@interface MyClass : NSObject
@property (readwrite,assign) id x;
- (long double)longDoubleM;
@end
void test() {
MyClass *obj1;
long double ld1 = [obj1 longDoubleM];
// warn: receiver is uninitialized
}
// Objective-C
@interface MyClass : NSObject
@property (readwrite,assign) id x;
- (long double)longDoubleM;
@end
void test() {
MyClass *obj1;
id i = obj1.x; // warn: uninitialized object pointer
}
// Objective-C
@interface Subscriptable : NSObject
- (id)objectAtIndexedSubscript:(unsigned int)index;
@end
@interface MyClass : Subscriptable
@property (readwrite,assign) id x;
- (long double)longDoubleM;
@end
void test() {
MyClass *obj1;
id i = obj1[0]; // warn: uninitialized object pointer
}
1.1.1.3. core.DivideZero (C, C++, ObjC)¶
检查除以零。
void test(int z) {
if (z == 0)
int x = 1 / z; // warn
}
void test() {
int x = 1;
int y = x % 0; // warn
}
1.1.1.4. core.NonNullParamChecker (C, C++, ObjC)¶
检查传递给函数的空指针,该函数的参数是引用或用“nonnull”属性标记的。
int f(int *p) __attribute__((nonnull));
void test(int *p) {
if (!p)
f(p); // warn
}
1.1.1.5. core.NullDereference (C, C++, ObjC)¶
检查对空指针的解引用。
此检查器专门不报告 x86 和 x86-64 目标的空指针解引用,当地址空间为 256(x86 GS 段)、257(x86 FS 段)或 258(x86 SS 段)时。请参阅 X86/X86-64 语言扩展 以供参考。
该 SuppressAddressSpaces
选项抑制对所有具有地址空间的指针的空指针解引用的警告。可以使用选项 -analyzer-config core.NullDereference:SuppressAddressSpaces=false
禁用此行为。默认值为 true。
// C
void test(int *p) {
if (p)
return;
int x = p[0]; // warn
}
// C
void test(int *p) {
if (!p)
*p = 0; // warn
}
// C++
class C {
public:
int x;
};
void test() {
C *pc = 0;
int k = pc->x; // warn
}
// Objective-C
@interface MyClass {
@public
int x;
}
@end
void test() {
MyClass *obj = 0;
obj->x = 1; // warn
}
1.1.1.6. core.StackAddressEscape (C)¶
检查堆栈内存的地址是否未逃逸出函数。
char const *p;
void test() {
char const str[] = "string";
p = str; // warn
}
void* test() {
return __builtin_alloca(12); // warn
}
void test() {
static int *x;
int y;
x = &y; // warn
}
1.1.1.7. core.UndefinedBinaryOperatorResult (C)¶
检查二元运算符的未定义结果。
void test() {
int x;
int y = x + 1; // warn: left operand is garbage
}
1.1.1.8. core.VLASize (C)¶
检查未定义、零或负大小的可变长度数组 (VLA) 的声明。
void test() {
int x;
int vla1[x]; // warn: garbage as size
}
void test() {
int x = 0;
int vla2[x]; // warn: zero size
}
如果 TaintPropagation 检查器开启,并且使用未绑定的、攻击者控制的(受污染的)值来定义 VLA 的大小,则检查器也会发出警告。
void taintedVLA(void) {
int x;
scanf("%d", &x);
int vla[x]; // Declared variable-length array (VLA) has tainted (attacker controlled) size, that can be 0 or negative
}
void taintedVerfieidVLA(void) {
int x;
scanf("%d", &x);
if (x<1)
return;
int vla[x]; // no-warning. The analyzer can prove that x must be positive.
}
1.1.1.9. core.uninitialized.ArraySubscript (C)¶
检查用作数组下标的未初始化值。
void test() {
int i, a[10];
int x = a[i]; // warn: array subscript is undefined
}
1.1.1.10. core.uninitialized.Assign (C)¶
检查分配未初始化值。
void test() {
int x;
x |= 1; // warn: left expression is uninitialized
}
1.1.1.11. core.uninitialized.Branch (C)¶
检查用作分支条件的未初始化值。
void test() {
int x;
if (x) // warn
return;
}
1.1.1.12. core.uninitialized.CapturedBlockVariable (C)¶
检查捕获未初始化值的代码块。
void test() {
int x;
^{ int y = x; }(); // warn
}
1.1.1.13. core.uninitialized.UndefReturn (C)¶
检查返回给调用者的未初始化值。
int test() {
int x;
return x; // warn
}
1.1.1.14. core.uninitialized.NewArraySize (C++)¶
检查 new[]
中的元素数量是否为垃圾或未定义。
void test() {
int n;
int *arr = new int[n]; // warn: Element count in new[] is a garbage value
delete[] arr;
}
1.1.2. cplusplus¶
C++ 检查器。
1.1.2.1. cplusplus.ArrayDelete (C++)¶
报告作为其基类销毁的多态对象数组的销毁。如果数组的动态类型不同于其静态类型,则调用 delete[] 未定义。
此检查器对应于 SEI CERT 规则 EXP51-CPP: 不要通过不正确的类型指针删除数组。
class Base {
public:
virtual ~Base() {}
};
class Derived : public Base {};
Base *create() {
Base *x = new Derived[10]; // note: Casting from 'Derived' to 'Base' here
return x;
}
void foo() {
Base *x = create();
delete[] x; // warn: Deleting an array of 'Derived' objects as their base class 'Base' is undefined
}
限制
即使在引用类型之间进行强制转换时会跟踪指针值,但检查器也不会发出注释标签。
void foo() {
Derived *d = new Derived[10];
Derived &dref = *d;
Base &bref = static_cast<Base&>(dref); // no note
Base *b = &bref;
delete[] b; // warn: Deleting an array of 'Derived' objects as their base class 'Base' is undefined
}
1.1.2.2. cplusplus.InnerPointer (C++)¶
检查重新分配或释放后使用的 C++ 容器的内部指针。
C++ 标准库中的许多容器方法已知会使对容器元素的“引用”(包括实际引用、迭代器和原始指针)无效。在引用无效后使用这些引用会导致未定义的行为,这是 C++ 中内存错误的常见来源,此检查器能够找到这些错误。
检查器目前仅限于 std::string
对象,并且不识别某些更复杂的方法来传递未拥有的指针,例如 std::string_view
。
void deref_after_assignment() {
std::string s = "llvm";
const char *c = s.data(); // note: pointer to inner buffer of 'std::string' obtained here
s = "clang"; // note: inner buffer of 'std::string' reallocated by call to 'operator='
consume(c); // warn: inner pointer of container used after re/deallocation
}
const char *return_temp(int x) {
return std::to_string(x).c_str(); // warn: inner pointer of container used after re/deallocation
// note: pointer to inner buffer of 'std::string' obtained here
// note: inner buffer of 'std::string' deallocated by call to destructor
}
1.1.2.3. cplusplus.Move (C++)¶
查找 C++ 中的移动后使用错误。这包括对已移动的对象进行方法调用、已移动对象的赋值以及对已移动对象的重复移动。
struct A {
void foo() {}
};
void f1() {
A a;
A b = std::move(a); // note: 'a' became 'moved-from' here
a.foo(); // warn: method call on a 'moved-from' object 'a'
}
void f2() {
A a;
A b = std::move(a);
A c(std::move(a)); // warn: move of an already moved-from object
}
void f3() {
A a;
A b = std::move(a);
b = a; // warn: copy of moved-from object
}
检查器选项 WarnOn
控制对哪些对象检查移动后使用。
最严格的值是
KnownsOnly
,在此模式下,仅检查已知类型为移动不安全的对象。这些包括大多数 STL 对象(但不包括移动安全的对象)和智能指针。对于选项值
KnownsAndLocals
,还会检查局部变量(任何类型)。这样做背后的理念是,局部变量通常不会被重新使用,因此与成员变量相比,移动后使用更可能是一个错误。对于选项值
All
,将检查所有类型的变量上的所有移动后使用条件,不包括全局变量和已知的移动安全情况。
默认值为 KnownsAndLocals
。
对命名为 empty()
或 isEmpty()
的方法的调用在已移动的对象上是允许的,因为这些方法被认为是移动安全的。名为 reset()
、destroy()
、clear()
、assign
、resize
、shrink
的函数被视为状态重置函数,并在已移动的对象上是允许的,这些函数会使对象再次有效。这适用于任何类型的对象(不仅仅是 STL 对象)。
1.1.2.4. cplusplus.NewDelete (C++)¶
检查双重释放和释放后使用问题。跟踪由 new/delete 管理的内存。
void f(int *p);
void testUseMiddleArgAfterDelete(int *p) {
delete p;
f(p); // warn: use after free
}
class SomeClass {
public:
void f();
};
void test() {
SomeClass *c = new SomeClass;
delete c;
c->f(); // warn: use after free
}
void test() {
int *p = (int *)__builtin_alloca(sizeof(int));
delete p; // warn: deleting memory allocated by alloca
}
void test() {
int *p = new int;
delete p;
delete p; // warn: attempt to free released
}
void test() {
int i;
delete &i; // warn: delete address of local
}
void test() {
int *p = new int[1];
delete[] (++p);
// warn: argument to 'delete[]' is offset by 4 bytes
// from the start of memory allocated by 'new[]'
}
1.1.2.5. cplusplus.NewDeleteLeaks (C++)¶
检查内存泄漏。跟踪由 new/delete 管理的内存。
void test() {
int *p = new int;
} // warn
1.1.2.6. cplusplus.PlacementNew (C++)¶
检查是否为默认放置 new 提供了指向足够存储容量的指针。
#include <new>
void f() {
short s;
long *lp = ::new (&s) long; // warn
}
1.1.2.7. cplusplus.SelfAssignment (C++)¶
检查 C++ 复制和移动赋值运算符的自身赋值。
1.1.2.8. cplusplus.StringChecker (C++)¶
检查 std::string 操作。
检查从其构造 std::string
对象的 cstring 指针是否为 NULL
。如果检查器无法推断指针的空值,则它将假定指针不为空以满足构造函数的前提条件。
此检查器能够检查 SEI CERT C++ 编码规则 STR51-CPP. 不要尝试从空指针创建 std::string。
#include <string>
void f(const char *p) {
if (!p) {
std::string msg(p); // warn: The parameter must not be null
}
}
1.1.3. deadcode¶
死代码检查器。
1.1.3.1. deadcode.DeadStores (C)¶
检查存储到变量的值,这些值之后从未被读取。
void test() {
int x;
x = 1; // warn
}
选项 WarnForDeadNestedAssignments
使检查器能够为嵌套的死赋值发出警告。可以通过 -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false
禁用。默认值为 true。
例如,会为以下代码发出警告:if ((y = make_int())) { }
。
1.1.4. nullability¶
检查器(主要是 Objective C)用于警告空指针传递和取消引用错误。
1.1.4.1. nullability.NullPassedToNonnull (ObjC)¶
当空指针传递给具有 _Nonnull 类型的指针时发出警告。
if (name != nil)
return;
// Warning: nil passed to a callee that requires a non-null 1st parameter
NSString *greeting = [@"Hello " stringByAppendingString:name];
1.1.4.2. nullability.NullReturnedFromNonnull (C, C++, ObjC)¶
当空指针从具有 _Nonnull 返回类型的函数返回时发出警告。
- (nonnull id)firstChild {
id result = nil;
if ([_children count] > 0)
result = _children[0];
// Warning: nil returned from a method that is expected
// to return a non-null value
return result;
}
当空指针从使用 __attribute__((returns_nonnull))
注释的函数返回时发出警告。
int global;
__attribute__((returns_nonnull)) void* getPtr(void* p);
void* getPtr(void* p) {
if (p) { // forgot to negate the condition
return &global;
}
// Warning: nullptr returned from a function that is expected
// to return a non-null value
return p;
}
1.1.4.3. nullability.NullableDereferenced (ObjC)¶
当可空指针被取消引用时发出警告。
struct LinkedList {
int data;
struct LinkedList *next;
};
struct LinkedList * _Nullable getNext(struct LinkedList *l);
void updateNextData(struct LinkedList *list, int newData) {
struct LinkedList *next = getNext(list);
// Warning: Nullable pointer is dereferenced
next->data = 7;
}
1.1.4.4. nullability.NullablePassedToNonnull (ObjC)¶
当可空指针传递给具有 _Nonnull 类型的指针时发出警告。
typedef struct Dummy { int val; } Dummy;
Dummy *_Nullable returnsNullable();
void takesNonnull(Dummy *_Nonnull);
void test() {
Dummy *p = returnsNullable();
takesNonnull(p); // warn
}
1.1.4.5. nullability.NullableReturnedFromNonnull (ObjC)¶
当可空指针从具有 _Nonnull 返回类型的函数返回时发出警告。
1.1.5. optin¶
检查可移植性、性能、可选安全性和编码风格特定规则的检查器。
1.1.5.1. optin.core.EnumCastOutOfRange (C, C++)¶
检查整数到枚举的强制转换,这些强制转换会产生没有对应枚举量的值。这并不一定是不确定的行为,但可能会导致令人不快的意外,因此项目可能会决定使用不允许这些“不寻常”转换的编码标准。
请注意,当枚举类型(例如 std::byte)根本没有枚举量时,不会产生任何警告。
enum WidgetKind { A=1, B, C, X=99 };
void foo() {
WidgetKind c = static_cast<WidgetKind>(3); // OK
WidgetKind x = static_cast<WidgetKind>(99); // OK
WidgetKind d = static_cast<WidgetKind>(4); // warn
}
限制
此检查器不接受将枚举类型用于存储标志值组合的编码模式。
enum AnimalFlags
{
HasClaws = 1,
CanFly = 2,
EatsFish = 4,
Endangered = 8
};
AnimalFlags operator|(AnimalFlags a, AnimalFlags b)
{
return static_cast<AnimalFlags>(static_cast<int>(a) | static_cast<int>(b));
}
auto flags = HasClaws | CanFly;
使用此模式的项目不应启用此 optin 检查器。
1.1.5.2. optin.cplusplus.UninitializedObject (C++)¶
此检查器报告在构造函数调用后创建的对象中的未初始化字段。它不仅会找到直接的未初始化字段,还会对对象进行深入检查,分析其所有字段的子字段。检查器将继承的字段视为直接字段,因此也会收到有关未初始化的继承数据成员的警告。
// With Pedantic and CheckPointeeInitialization set to true
struct A {
struct B {
int x; // note: uninitialized field 'this->b.x'
// note: uninitialized field 'this->bptr->x'
int y; // note: uninitialized field 'this->b.y'
// note: uninitialized field 'this->bptr->y'
};
int *iptr; // note: uninitialized pointer 'this->iptr'
B b;
B *bptr;
char *cptr; // note: uninitialized pointee 'this->cptr'
A (B *bptr, char *cptr) : bptr(bptr), cptr(cptr) {}
};
void f() {
A::B b;
char c;
A a(&b, &c); // warning: 6 uninitialized fields
// after the constructor call
}
// With Pedantic set to false and
// CheckPointeeInitialization set to true
// (every field is uninitialized)
struct A {
struct B {
int x;
int y;
};
int *iptr;
B b;
B *bptr;
char *cptr;
A (B *bptr, char *cptr) : bptr(bptr), cptr(cptr) {}
};
void f() {
A::B b;
char c;
A a(&b, &c); // no warning
}
// With Pedantic set to true and
// CheckPointeeInitialization set to false
// (pointees are regarded as initialized)
struct A {
struct B {
int x; // note: uninitialized field 'this->b.x'
int y; // note: uninitialized field 'this->b.y'
};
int *iptr; // note: uninitialized pointer 'this->iptr'
B b;
B *bptr;
char *cptr;
A (B *bptr, char *cptr) : bptr(bptr), cptr(cptr) {}
};
void f() {
A::B b;
char c;
A a(&b, &c); // warning: 3 uninitialized fields
// after the constructor call
}
选项
此检查器有几个选项,可以从命令行设置(例如 -analyzer-config optin.cplusplus.UninitializedObject:Pedantic=true
)
Pedantic
(布尔值)。如果为 false,则检查器不会为至少有一个已初始化字段的对象发出警告。默认值为 false。NotesAsWarnings
(布尔值)。如果设置为 true,则检查器将为每个未初始化的字段发出警告,而不是为每个构造函数调用发出一个警告,并在注释中列出属于它的未初始化字段。默认值为 false。CheckPointeeInitialization
(布尔值)。如果设置为 false,则检查器不会分析指针/引用字段的指向对象,只会检查对象本身是否已初始化。默认值为 false。IgnoreRecordsWithField
(字符串)。如果提供,则检查器不会分析具有与给定模式匹配的名称或类型名称的字段的结构。默认值为 ""。
1.1.5.3. optin.cplusplus.VirtualCall (C++)¶
检查构造或销毁期间的虚函数调用。
class A {
public:
A() {
f(); // warn
}
virtual void f();
};
class A {
public:
~A() {
this->f(); // warn
}
virtual void f();
};
1.1.5.4. optin.mpi.MPI-Checker (C)¶
检查 MPI 代码。
void test() {
double buf = 0;
MPI_Request sendReq1;
MPI_Ireduce(MPI_IN_PLACE, &buf, 1, MPI_DOUBLE, MPI_SUM,
0, MPI_COMM_WORLD, &sendReq1);
} // warn: request 'sendReq1' has no matching wait.
void test() {
double buf = 0;
MPI_Request sendReq;
MPI_Isend(&buf, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, &sendReq);
MPI_Irecv(&buf, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, &sendReq); // warn
MPI_Isend(&buf, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, &sendReq); // warn
MPI_Wait(&sendReq, MPI_STATUS_IGNORE);
}
void missingNonBlocking() {
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Request sendReq1[10][10][10];
MPI_Wait(&sendReq1[1][7][9], MPI_STATUS_IGNORE); // warn
}
1.1.5.5. optin.osx.cocoa.localizability.EmptyLocalizationContextChecker (ObjC)¶
检查 NSLocalizedString 宏是否包含用于上下文注释。
- (void)test {
NSString *string = NSLocalizedString(@"LocalizedString", nil); // warn
NSString *string2 = NSLocalizedString(@"LocalizedString", @" "); // warn
NSString *string3 = NSLocalizedStringWithDefaultValue(
@"LocalizedString", nil, [[NSBundle alloc] init], nil,@""); // warn
}
1.1.5.6. optin.osx.cocoa.localizability.NonLocalizedStringChecker (ObjC)¶
警告使用传递给期望本地化 NSStrings 的 UI 方法的非本地化 NSStrings。
NSString *alarmText =
NSLocalizedString(@"Enabled", @"Indicates alarm is turned on");
if (!isEnabled) {
alarmText = @"Disabled";
}
UILabel *alarmStateLabel = [[UILabel alloc] init];
// Warning: User-facing text should use localized string macro
[alarmStateLabel setText:alarmText];
1.1.5.7. optin.performance.GCDAntipattern¶
使用 Grand Central Dispatch 时,检查是否存在性能反模式。
1.1.5.8. optin.performance.Padding (C, C++, ObjC)¶
检查过度填充的结构体。
此检查器检测具有过度填充的结构体,这会导致内存浪费,从而由于降低处理器缓存的效率而降低性能。 填充字节由编译器添加,以对齐数据访问,因为某些处理器要求数据对齐到某些边界。 在其他情况下,未对齐的数据访问是可能的,但会带来明显更大的延迟。
为了避免填充字节,结构体中的字段应按对齐方式递减排序。 通常,更容易想到字段的 sizeof
,并且按 sizeof
对字段进行排序通常也会导致相同的最佳布局。
在极少数情况下,可以使用 #pragma pack(1)
指令强制执行打包布局,但这会显著增加访问时间,因此重新排序字段通常是更好的解决方案。
// warn: Excessive padding in 'struct NonOptimal' (35 padding bytes, where 3 is optimal)
struct NonOptimal {
char c1;
// 7 bytes of padding
std::int64_t big1; // 8 bytes
char c2;
// 7 bytes of padding
std::int64_t big2; // 8 bytes
char c3;
// 7 bytes of padding
std::int64_t big3; // 8 bytes
char c4;
// 7 bytes of padding
std::int64_t big4; // 8 bytes
char c5;
// 7 bytes of padding
};
static_assert(sizeof(NonOptimal) == 4*8+5+5*7);
// no-warning: The fields are nicely aligned to have the minimal amount of padding bytes.
struct Optimal {
std::int64_t big1; // 8 bytes
std::int64_t big2; // 8 bytes
std::int64_t big3; // 8 bytes
std::int64_t big4; // 8 bytes
char c1;
char c2;
char c3;
char c4;
char c5;
// 3 bytes of padding
};
static_assert(sizeof(Optimal) == 4*8+5+3);
// no-warning: Bit packing representation is also accepted by this checker, but
// it can significantly increase access times, so prefer reordering the fields.
#pragma pack(1)
struct BitPacked {
char c1;
std::int64_t big1; // 8 bytes
char c2;
std::int64_t big2; // 8 bytes
char c3;
std::int64_t big3; // 8 bytes
char c4;
std::int64_t big4; // 8 bytes
char c5;
};
static_assert(sizeof(BitPacked) == 4*8+5);
可以使用 AllowedPad
选项指定触发警告的填充字节数阈值。 如果结构体的填充字节数与最佳填充字节数的差值大于阈值,则会触发警告。
默认情况下,AllowedPad
阈值为 24 字节。
要将此阈值覆盖为例如 4 字节,请使用 -analyzer-config optin.performance.Padding:AllowedPad=4
选项。
1.1.5.9. optin.portability.UnixAPI¶
查找 UNIX/Posix 函数中的实现定义行为。
1.1.6. optin.taint¶
实现 污点分析 的检查器。
1.1.6.1. optin.taint.GenericTaint (C, C++)¶
污点分析识别潜在的安全漏洞,攻击者可以在其中将恶意数据注入程序以执行攻击(权限提升、命令注入、SQL 注入等)。
恶意数据在污点源处注入(例如 getenv()
调用),然后通过函数调用传播,并用作敏感操作的参数,也称为污点接收器(例如 system()
调用)。
可以通过始终检查和清理潜在的恶意、不受信任的用户输入来防御此类漏洞。
检查器的目标是发现并向用户显示这些潜在的污点源-接收器对以及传播调用链。
污点源最著名的例子是
来自网络的数据
文件或标准输入
环境变量
来自数据库的数据
让我们研究一个命令注入攻击的实际例子。
// Command Injection Vulnerability Example
int main(int argc, char** argv) {
char cmd[2048] = "/bin/cat ";
char filename[1024];
printf("Filename:");
scanf (" %1023[^\n]", filename); // The attacker can inject a shell escape here
strcat(cmd, filename);
system(cmd); // Warning: Untrusted data is passed to a system call
}
程序打印任何用户指定文件的內容。 不幸的是,攻击者可以使用 shell 转义执行任意命令。 例如,使用以下输入,ls 命令也会在打印 /etc/shadow 的內容后执行。 输入:/etc/shadow ; ls /
此检查器中实现的分析指出了这个问题。
可以通过例如检查提供的输入是否引用有效文件并删除任何无效用户输入来防止此类攻击。
// No vulnerability anymore, but we still get the warning
void sanitizeFileName(char* filename){
if (access(filename,F_OK)){// Verifying user input
printf("File does not exist\n");
filename[0]='\0';
}
}
int main(int argc, char** argv) {
char cmd[2048] = "/bin/cat ";
char filename[1024];
printf("Filename:");
scanf (" %1023[^\n]", filename); // The attacker can inject a shell escape here
sanitizeFileName(filename);// filename is safe after this point
if (!filename[0])
return -1;
strcat(cmd, filename);
system(cmd); // Superfluous Warning: Untrusted data is passed to a system call
}
不幸的是,检查器无法自动发现程序员是否执行了数据清理,因此它仍然发出警告。
可以通过在污点配置文件中指定清理函数来消除这种多余的警告(请参阅 污点分析配置)。
Filters:
- Name: sanitizeFileName
Args: [0]
传递配置文件位置的 clang 调用
clang --analyze -Xclang -analyzer-config -Xclang optin.taint.TaintPropagation:Config=`pwd`/taint_config.yml ...
如果您正在验证输入而不是清理输入,或者不想在我们的配置中提及每个清理函数,可以使用更通用的方法。
引入一个通用的无操作 csa_mark_sanitized(..) 函数,告诉 Clang 静态分析器该变量在此分析路径上是安全的。
// Marking sanitized variables safe.
// No vulnerability anymore, no warning.
// User csa_mark_sanitize function is for the analyzer only
#ifdef __clang_analyzer__
void csa_mark_sanitized(const void *);
#endif
int main(int argc, char** argv) {
char cmd[2048] = "/bin/cat ";
char filename[1024];
printf("Filename:");
scanf (" %1023[^\n]", filename);
if (access(filename,F_OK)){// Verifying user input
printf("File does not exist\n");
return -1;
}
#ifdef __clang_analyzer__
csa_mark_sanitized(filename); // Indicating to CSA that filename variable is safe to be used after this point
#endif
strcat(cmd, filename);
system(cmd); // No warning
}
与前面的示例类似,您需要在 YAML 配置文件中定义一个 Filter 函数,并添加 csa_mark_sanitized 函数。
Filters:
- Name: csa_mark_sanitized
Args: [0]
然后调用 csa_mark_sanitized(X) 将告诉分析器 X 在此之后是安全的,因为它的内容已验证。 程序员有责任确保此验证确实正确。 请注意,csa_mark_sanitized 函数仅在 Clang 静态分析期间声明和使用,并在(生产)构建中跳过。
此检查器可以找到的注入漏洞的更多示例。
void test() {
char x = getchar(); // 'x' marked as tainted
system(&x); // warn: untrusted data is passed to a system call
}
// note: compiler internally checks if the second param to
// sprintf is a string literal or not.
// Use -Wno-format-security to suppress compiler warning.
void test() {
char s[10], buf[10];
fscanf(stdin, "%s", s); // 's' marked as tainted
sprintf(buf, s); // warn: untrusted data used as a format string
}
即使没有提供外部污点配置,也存在内置的源、传播和接收器。
- 默认源
_IO_getc
,fdopen
,fopen
,freopen
,get_current_dir_name
,getch
,getchar
,getchar_unlocked
,getwd
,getcwd
,getgroups
,gethostname
,getlogin
,getlogin_r
,getnameinfo
,gets
,gets_s
,getseuserbyname
,readlink
,readlinkat
,scanf
,scanf_s
,socket
,wgetch
- 默认传播规则
atoi
,atol
,atoll
,basename
,dirname
,fgetc
,fgetln
,fgets
,fnmatch
,fread
,fscanf
,fscanf_s
,index
,inflate
,isalnum
,isalpha
,isascii
,isblank
,iscntrl
,isdigit
,isgraph
,islower
,isprint
,ispunct
,isspace
,isupper
,isxdigit
,memchr
,memrchr
,sscanf
,getc
,getc_unlocked
,getdelim
,getline
,getw
,memcmp
,memcpy
,memmem
,memmove
,mbtowc
,pread
,qsort
,qsort_r
,rawmemchr
,read
,recv
,recvfrom
,rindex
,strcasestr
,strchr
,strchrnul
,strcasecmp
,strcmp
,strcspn
,strncasecmp
,strncmp
,strndup
,strndupa
,strpbrk
,strrchr
,strsep
,strspn
,strstr
,strtol
,strtoll
,strtoul
,strtoull
,tolower
,toupper
,ttyname
,ttyname_r
,wctomb
,wcwidth
- 默认接收器
printf
,setproctitle
,system
,popen
,execl
,execle
,execlp
,execv
,execvp
,execvP
,execve
,dlopen
请注意,这里没有内置的过滤器函数。
可以通过检查器选项 optin.taint.TaintPropagation:Config
提供配置文件来配置自己的污染源、接收器和传播规则。配置文件采用 YAML 格式。在配置文件中定义的与污染相关的选项扩展了内置源、规则、接收器,但不会覆盖它们。外部污染配置文件的格式并不稳定,并且可能在没有任何通知的情况下以非向后兼容的方式更改。
有关配置选项的更详细说明,请参见 污染分析配置。有关示例,请参见 示例配置文件。
配置
Config 指定 YAML 配置文件的名称。用户可以定义自己的污染源和接收器。
相关指南
限制
除非检查器中内置了特定传播规则或在 YAML 配置文件中给出,否则污染属性不会通过分析器未知(或过于复杂)的函数调用传播。这会导致潜在的真阳性发现丢失。
1.1.6.2. optin.taint.TaintedAlloc (C, C++)¶
当 malloc
、 calloc
、 realloc
、 alloca
或 C++ 数组 new 运算符的大小参数被污染(可能由攻击者控制)时,此检查器会发出警告。如果攻击者可以注入一个很大的值作为大小参数,则可以执行内存耗尽拒绝服务攻击。
仅当分析器无法证明大小参数在合理范围内 (<= SIZE_MAX/4
) 时,分析器才会发出警告。此功能部分涵盖了 SEI Cert 编码标准规则 INT04-C。
可以通过对 size
参数进行边界检查或通过显式地将 size
参数标记为已清理来消除此警告。有关示例,请参见 optin.taint.GenericTaint (C, C++) 检查器。
void vulnerable(void) {
size_t size = 0;
scanf("%zu", &size);
int *p = malloc(size); // warn: malloc is called with a tainted (potentially attacker controlled) value
free(p);
}
void not_vulnerable(void) {
size_t size = 0;
scanf("%zu", &size);
if (1024 < size)
return;
int *p = malloc(size); // No warning expected as the the user input is bound
free(p);
}
void vulnerable_cpp(void) {
size_t size = 0;
scanf("%zu", &size);
int *ptr = new int[size];// warn: Memory allocation function is called with a tainted (potentially attacker controlled) value
delete[] ptr;
}
1.1.6.3. optin.taint.TaintedDiv (C, C++, ObjC)¶
当除法运算中的分母是污染值(可能由攻击者控制)时,此检查器会发出警告。如果攻击者可以将分母设置为 0,则会触发运行时错误。当分母是污染值并且分析器无法证明它不为 0 时,检查器会发出警告。此警告比 core.DivideZero (C, C++, ObjC) 检查器更悲观,该检查器仅当它能够证明分母为 0 时才会发出警告。
int vulnerable(int n) {
size_t size = 0;
scanf("%zu", &size);
return n / size; // warn: Division by a tainted value, possibly zero
}
int not_vulnerable(int n) {
size_t size = 0;
scanf("%zu", &size);
if (!size)
return 0;
return n / size; // no warning
}
1.1.7. security¶
与安全相关的检查器。
1.1.7.1. security.cert.env.InvalidPtr¶
对应于 SEI CERT 规则 ENV31-C 和 ENV34-C。
ENV31-C:规则是关于
main
函数的第三个参数,环境指针“envp”的潜在问题。当使用一些修改函数(如putenv
、setenv
或其他函数)修改环境数组时,可能会发生内存重新分配,但“envp”不会更新以反映这些更改,并指向旧的内存区域。ENV34-C:一些函数返回指向静态分配的缓冲区的指针。因此,随后调用这些函数将使以前的指针无效。这些函数包括:
getenv
、localeconv
、asctime
、setlocale
、strerror
int main(int argc, const char *argv[], const char *envp[]) {
if (setenv("MY_NEW_VAR", "new_value", 1) != 0) {
// setenv call may invalidate 'envp'
/* Handle error */
}
if (envp != NULL) {
for (size_t i = 0; envp[i] != NULL; ++i) {
puts(envp[i]);
// envp may no longer point to the current environment
// this program has unanticipated behavior, since envp
// does not reflect changes made by setenv function.
}
}
return 0;
}
void previous_call_invalidation() {
char *p, *pp;
p = getenv("VAR");
setenv("SOMEVAR", "VALUE", /*overwrite = */1);
// call to 'setenv' may invalidate p
*p;
// dereferencing invalid pointer
}
可以使用 InvalidatingGetEnv
选项将 getenv
调用视为无效。启用后,如果 getenv
被多次调用并且其结果在先创建副本之前使用,则检查器会发出警告。对于常用的 getenv
实现,这种严格程度可能被认为过于迂腐。
要启用此选项,请使用: -analyzer-config security.cert.env.InvalidPtr:InvalidatingGetEnv=true
。
默认情况下,此选项设置为false。
启用此选项后,将针对以下情况生成警告
char* p = getenv("VAR");
char* pp = getenv("VAR2"); // assumes this call can invalidate `env`
strlen(p); // warns about accessing invalid ptr
1.1.7.2. security.FloatLoopCounter (C)¶
在使用浮点值作为循环计数器时发出警告(CERT:FLP30-C、FLP30-CPP)。
void test() {
for (float x = 0.1f; x <= 1.0f; x += 0.1f) {} // warn
}
1.1.7.3. security.insecureAPI.UncheckedReturn (C)¶
在使用必须始终检查其返回值的函数时发出警告。
void test() {
setuid(1); // warn
}
1.1.7.4. security.insecureAPI.bcmp (C)¶
在使用“bcmp”函数时发出警告。
void test() {
bcmp(ptr0, ptr1, n); // warn
}
1.1.7.5. security.insecureAPI.bcopy (C)¶
在使用“bcopy”函数时发出警告。
void test() {
bcopy(src, dst, n); // warn
}
1.1.7.6. security.insecureAPI.bzero (C)¶
在使用“bzero”函数时发出警告。
void test() {
bzero(ptr, n); // warn
}
1.1.7.7. security.insecureAPI.getpw (C)¶
在使用“getpw”函数时发出警告。
void test() {
char buff[1024];
getpw(2, buff); // warn
}
1.1.7.8. security.insecureAPI.gets (C)¶
在使用“gets”函数时发出警告。
void test() {
char buff[1024];
gets(buff); // warn
}
1.1.7.9. security.insecureAPI.mkstemp (C)¶
当“mkstemp”在格式字符串中传递少于 6 个 X 时发出警告。
void test() {
mkstemp("XX"); // warn
}
1.1.7.10. security.insecureAPI.mktemp (C)¶
在使用 mktemp
函数时发出警告。
void test() {
char *x = mktemp("/tmp/zxcv"); // warn: insecure, use mkstemp
}
1.1.7.11. security.insecureAPI.rand (C)¶
在使用低劣的随机数生成函数时发出警告(仅当 arc4random 函数可用时): drand48, erand48, jrand48, lcong48, lrand48, mrand48, nrand48, random, rand_r
。
void test() {
random(); // warn
}
1.1.7.12. security.insecureAPI.strcpy (C)¶
在使用 strcpy
和 strcat
函数时发出警告。
void test() {
char x[4];
char *y = "abcd";
strcpy(x, y); // warn
}
1.1.7.13. security.insecureAPI.vfork (C)¶
在使用“vfork”函数时发出警告。
void test() {
vfork(); // warn
}
1.1.7.14. security.insecureAPI.DeprecatedOrUnsafeBufferHandling (C)¶
在出现不安全或已弃用的缓冲区处理函数时发出警告,这些函数现在具有安全的变体:
sprintf, fprintf, vsprintf, scanf, wscanf, fscanf, fwscanf, vscanf, vwscanf, vfscanf, vfwscanf, sscanf, swscanf, vsscanf, vswscanf, swprintf, snprintf, vswprintf, vsnprintf, memcpy, memmove, strncpy, strncat, memset
void test() {
char buf [5];
strncpy(buf, "a", 1); // warn
}
1.1.7.15. security.MmapWriteExec (C)¶
在 mmap()
调用同时具有可写和可执行访问权限时发出警告。
void test(int n) {
void *c = mmap(NULL, 32, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANON, -1, 0);
// warn: Both PROT_WRITE and PROT_EXEC flags are set. This can lead to
// exploitable memory regions, which could be overwritten with malicious
// code
}
1.1.7.16. security.PointerSub (C)¶
检查指向不同内存块的两个指针的指针减法。根据 C 标准 §6.5.6,仅指向(或指向同一数组对象的末尾)的指针的减法才是有效的(为此目的,非数组变量就像大小为 1 的数组)。此检查器仅在减法时搜索不同的内存对象,但不检查数组索引是否正确。此外,仅报告涉及堆栈分配对象的案例(不会对指向由 malloc 分配的内存的指针发出警告)。
void test() {
int a, b, c[10], d[10];
int x = &c[3] - &c[1];
x = &d[4] - &c[1]; // warn: 'c' and 'd' are different arrays
x = (&a + 1) - &a;
x = &b - &a; // warn: 'a' and 'b' are different variables
}
struct S {
int x[10];
int y[10];
};
void test1() {
struct S a[10];
struct S b;
int d = &a[4] - &a[6];
d = &a[0].x[3] - &a[0].x[1];
d = a[0].y - a[0].x; // warn: 'S.b' and 'S.a' are different objects
d = (char *)&b.y - (char *)&b.x; // warn: different members of the same object
d = (char *)&b.y - (char *)&b; // warn: object of type S is not the same array as a member of it
}
可能存在使用上述代码来计算结构中成员的偏移量的现有应用程序,使用指针减法。这仍然是标准规定的未定义行为,并且可以使用 offsetof 宏替换这种代码。
1.1.7.17. security.PutenvStackArray (C)¶
查找调用了 putenv
函数并将指向堆栈分配(自动)数组的指针作为参数传递的代码。函数 putenv
不会复制传递的字符串,只存储指向数据的指针,并且其他线程也可以读取此数据。堆栈分配数组的内容很可能在退出函数后被覆盖。
可以通过使用静态数组变量或动态分配的内存来解决此问题。更好的方法是避免使用 putenv
(它存在其他与内存泄漏相关的问题)并改为使用 setenv
。
此检查对应于 CERT 规则 POS34-C. 不要将指向自动变量的指针作为参数调用 putenv().
int f() {
char env[] = "NAME=value";
return putenv(env); // putenv function should not be called with stack-allocated string
}
有一种情况会导致检查器报告假阳性。当堆栈分配的数组在函数或不返回的代码分支(所有执行路径上进程都终止)的 putenv 中使用时,就会出现这种情况。
另一个特殊情况是,如果从函数 main 中调用了 putenv。这里,堆栈在程序结束时被释放,因此使用堆栈分配的字符串应该没有问题(多线程程序可能需要更多注意)。检查器不会对在 putenv 调用中使用 main 的堆栈空间的情况发出警告。
1.1.7.18. security.SetgidSetuidOrder (C)¶
在使用 setuid
和 setgid
调用在程序中降低用户级和组级权限时,重要的是要首先重置组级权限(使用 setgid
)。如果超级用户权限已经被放弃,函数 setgid
很可能会失败。
检查器检查 setuid(getuid())
和 setgid(getgid())
调用的序列(按此顺序)。如果找到这样的序列,并且在两者之间没有其他更改权限的函数调用(seteuid
、setreuid
、setresuid
以及这些函数的 GID 版本),则会生成警告。检查器只查找完全匹配 setuid(getuid())
的调用(以及 GID 版本),例如,如果 getuid()
的结果存储在变量中,则不会查找。
void test1() {
// ...
// end of section with elevated privileges
// reset privileges (user and group) to normal user
if (setuid(getuid()) != 0) {
handle_error();
return;
}
if (setgid(getgid()) != 0) { // warning: A 'setgid(getgid())' call following a 'setuid(getuid())' call is likely to fail
handle_error();
return;
}
// user-ID and group-ID are reset to normal user now
// ...
}
上述代码中的问题在于,setuid(getuid())
在调用 setgid(getgid())
之前删除了超级用户权限。要解决此问题,应首先调用 setgid(getgid())
。还需要注意避免像 setgid(getuid())
这样的代码(此检查器不会检测到此类错误),并始终检查这些调用的返回值。
此检查对应于 SEI CERT 规则 POS36-C.
1.1.8. unix¶
POSIX/Unix 检查器。
1.1.8.1. unix.API (C)¶
检查对各种 UNIX/Posix 函数的调用:open, pthread_once, calloc, malloc, realloc, alloca
。
// Currently the check is performed for apple targets only.
void test(const char *path) {
int fd = open(path, O_CREAT);
// warn: call to 'open' requires a third argument when the
// 'O_CREAT' flag is set
}
void f();
void test() {
pthread_once_t pred = {0x30B1BCBA, {0}};
pthread_once(&pred, f);
// warn: call to 'pthread_once' uses the local variable
}
void test() {
void *p = malloc(0); // warn: allocation size of 0 bytes
}
void test() {
void *p = calloc(0, 42); // warn: allocation size of 0 bytes
}
void test() {
void *p = malloc(1);
p = realloc(p, 0); // warn: allocation size of 0 bytes
}
void test() {
void *p = alloca(0); // warn: allocation size of 0 bytes
}
void test() {
void *p = valloc(0); // warn: allocation size of 0 bytes
}
1.1.8.2. unix.BlockInCriticalSection (C, C++)¶
检查临界区内对阻塞函数的调用。此检查器检测到的阻塞函数:sleep, getc, fgets, read, recv
。此检查器建模的临界区处理函数:lock, unlock, pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock, mtx_lock, mtx_timedlock, mtx_trylock, mtx_unlock, lock_guard, unique_lock
。
void pthread_lock_example(pthread_mutex_t *m) {
pthread_mutex_lock(m); // note: entering critical section here
sleep(10); // warn: Call to blocking function 'sleep' inside of critical section
pthread_mutex_unlock(m);
}
void overlapping_critical_sections(mtx_t *m1, std::mutex &m2) {
std::lock_guard lg{m2}; // note: entering critical section here
mtx_lock(m1); // note: entering critical section here
sleep(10); // warn: Call to blocking function 'sleep' inside of critical section
mtx_unlock(m1);
sleep(10); // warn: Call to blocking function 'sleep' inside of critical section
// still inside of the critical section of the std::lock_guard
}
限制
目前假设获取锁的
trylock
和timedlock
版本始终会成功。这会导致假阳性。
void trylock_example(pthread_mutex_t *m) {
if (pthread_mutex_trylock(m) == 0) { // assume trylock always succeeds
sleep(10); // warn: Call to blocking function 'sleep' inside of critical section
pthread_mutex_unlock(m);
} else {
sleep(10); // false positive: Incorrect warning about blocking function inside critical section.
}
}
1.1.8.3. unix.Errno (C)¶
检查对 errno
的不当使用。此检查器部分实现了 CERT 规则 ERR30-C. 在调用已知会设置 errno 的库函数之前将 errno 设置为零,并且仅在函数返回指示失败的值后检查 errno。检查器可以找到在成功调用标准函数后第一次读取 errno
的位置。
C 和 POSIX 标准通常没有定义如果调用不失败,标准库函数是否可以更改 errno
的值。因此,只有当从函数的返回值中得知调用失败时,才能使用 errno
。此规则有一些例外(例如 strtol
),但检查器尚未支持受影响的函数。失败情况下的返回值记录在函数的标准 Linux 手册页和 POSIX 标准 中。
int unsafe_errno_read(int sock, void *data, int data_size) {
if (send(sock, data, data_size, 0) != data_size) {
// 'send' can be successful even if not all data was sent
if (errno == 1) { // An undefined value may be read from 'errno'
return 0;
}
}
return 1;
}
必须开启检查器 unix.StdCLibraryFunctions (C) 才能获得来自此检查器的警告。支持的函数与 unix.StdCLibraryFunctions (C) 中的相同。该检查器的 ModelPOSIX
选项会影响所检查函数的集合。
参数
如果 errno
的值未在条件中使用(在 if
语句、循环、条件表达式、switch
语句中),则 AllowErrnoReadOutsideConditionExpressions
选项允许读取 errno
的值。例如,errno
可以存储到变量中而不会收到检查器的警告。
int unsafe_errno_read(int sock, void *data, int data_size) {
if (send(sock, data, data_size, 0) != data_size) {
int err = errno;
// warning if 'AllowErrnoReadOutsideConditionExpressions' is false
// no warning if 'AllowErrnoReadOutsideConditionExpressions' is true
}
return 1;
}
此选项的默认值为 true
。这允许保存 errno
的值以备日后进行错误处理。
限制
仅检查在受影响函数调用后第一次使用
errno
的情况。当errno
的值存储到变量或从函数返回时,不会跟踪该值。函数
lseek
的文档没有明确说明如果函数返回的值与预期的文件位置不同但不是 -1 会发生什么。为了避免可能的假阳性,在这种情况下允许使用errno
。
1.1.8.4. unix.Malloc (C)¶
检查内存泄漏、双重释放和释放后使用问题。跟踪由 malloc()/free() 管理的内存。
void test() {
int *p = malloc(1);
free(p);
free(p); // warn: attempt to free released memory
}
void test() {
int *p = malloc(sizeof(int));
free(p);
*p = 1; // warn: use after free
}
void test() {
int *p = malloc(1);
if (p)
return; // warn: memory is never released
}
void test() {
int a[] = { 1 };
free(a); // warn: argument is not allocated by malloc
}
void test() {
int *p = malloc(sizeof(char));
p = p - 1;
free(p); // warn: argument to free() is offset by -4 bytes
}
1.1.8.5. unix.MallocSizeof (C)¶
检查涉及 sizeof
的可疑 malloc
参数。
void test() {
long *p = malloc(sizeof(short));
// warn: result is converted to 'long *', which is
// incompatible with operand type 'short'
free(p);
}
1.1.8.6. unix.MismatchedDeallocator (C, C++)¶
检查不匹配的释放器。
// C, C++
void test() {
int *p = (int *)malloc(sizeof(int));
delete p; // warn
}
// C, C++
void __attribute((ownership_returns(malloc))) *user_malloc(size_t);
void __attribute((ownership_takes(malloc, 1))) *user_free(void *);
void __attribute((ownership_returns(malloc1))) *user_malloc1(size_t);
void __attribute((ownership_takes(malloc1, 1))) *user_free1(void *);
void test() {
int *p = (int *)user_malloc(sizeof(int));
delete p; // warn
}
// C, C++
void test() {
int *p = new int;
free(p); // warn
}
// C, C++
void test() {
int *p = new int[1];
realloc(p, sizeof(long)); // warn
}
// C, C++
void test() {
int *p = user_malloc(10);
user_free1(p); // warn
}
// C, C++
template <typename T>
struct SimpleSmartPointer {
T *ptr;
explicit SimpleSmartPointer(T *p = 0) : ptr(p) {}
~SimpleSmartPointer() {
delete ptr; // warn
}
};
void test() {
SimpleSmartPointer<int> a((int *)malloc(4));
}
// C++
void test() {
int *p = (int *)operator new(0);
delete[] p; // warn
}
// Objective-C, C++
void test(NSUInteger dataLength) {
int *p = new int;
NSData *d = [NSData dataWithBytesNoCopy:p
length:sizeof(int) freeWhenDone:1];
// warn +dataWithBytesNoCopy:length:freeWhenDone: cannot take
// ownership of memory allocated by 'new'
}
1.1.8.7. unix.Vfork (C)¶
检查对 vfork
的正确使用。
int test(int x) {
pid_t pid = vfork(); // warn
if (pid != 0)
return 0;
switch (x) {
case 0:
pid = 1;
execl("", "", 0);
_exit(1);
break;
case 1:
x = 0; // warn: this assignment is prohibited
break;
case 2:
foo(); // warn: this function call is prohibited
break;
default:
return 0; // warn: return is prohibited
}
while(1);
}
1.1.8.8. unix.cstring.BadSizeArg (C)¶
检查传递给 C 字符串函数的 size 参数是否存在常见的错误模式。使用 -Wno-strncat-size
编译器选项来静默其他与 strncat
相关的编译器警告。
void test() {
char dest[3];
strncat(dest, """""""""""""""""""""""""*", sizeof(dest));
// warn: potential buffer overflow
}
1.1.8.9. unix.cstring.NullArg (C)¶
检查将空指针作为参数传递给 C 字符串函数的情况:strlen, strnlen, strcpy, strncpy, strcat, strncat, strcmp, strncmp, strcasecmp, strncasecmp, wcslen, wcsnlen
。
int test() {
return strlen(0); // warn
}
1.1.8.10. unix.StdCLibraryFunctions (C)¶
检查对违反预定义参数约束的标准库函数的调用。例如,根据 C 标准,如果 ch
的值不能表示为 unsigned char
且不等于 EOF
,则函数 int isalnum(int ch)
的行为未定义。
您可以将此检查器视为对标准库函数定义限制(前置条件和后置条件)。会检查前置条件,并在违反时发出警告。后置条件会添加到分析中,例如,函数的返回值不大于 255。前置条件也会添加到分析中,如果受影响的值在调用之前未知,则会添加前置条件。
例如,如果函数的参数必须在 0 到 255 之间,但参数的值未知,则分析器会假设它在该区间内。类似地,如果函数不能用空指针调用,并且分析器无法证明它为空,则它会假设它不为空。
- 以下是传递给函数参数的值的可能检查
参数具有允许的值范围(或多个范围)。检查器可以检测到传递的值是否在允许的范围内,并显示实际值和允许值。
该参数具有指针类型,不允许为 NULL 指针。许多(但并非所有)标准函数在传递 NULL 指针时可能会产生未定义的行为,这些情况可以由检查器检测到。
该参数是指向内存块的指针,该缓冲区的最小大小由函数的另一个参数确定,或由两个参数的乘积确定(如函数
fread
),或者是一个固定值(例如asctime_r
至少需要大小为 26 的缓冲区)。检查器可以检测到缓冲区大小是否过小,并在最佳情况下显示缓冲区的大小和相应参数的值。
#define EOF -1
void test_alnum_concrete(int v) {
int ret = isalnum(256); // \
// warning: Function argument outside of allowed range
(void)ret;
}
void buffer_size_violation(FILE *file) {
enum { BUFFER_SIZE = 1024 };
wchar_t wbuf[BUFFER_SIZE];
const size_t size = sizeof(*wbuf); // 4
const size_t nitems = sizeof(wbuf); // 4096
// Below we receive a warning because the 3rd parameter should be the
// number of elements to read, not the size in bytes. This case is a known
// vulnerability described by the ARR38-C SEI-CERT rule.
fread(wbuf, size, nitems, file);
}
int test_alnum_symbolic(int x) {
int ret = isalnum(x);
// after the call, ret is assumed to be in the range [-1, 255]
if (ret > 255) // impossible (infeasible branch)
if (x == 0)
return ret / x; // division by zero is not reported
return ret;
}
除了参数和返回值条件之外,此检查器还根据分析情况添加了值 errno
的状态(如果适用)。许多系统函数仅在发生错误时(以及函数的特定返回值)设置 errno
值,否则它将变为未定义。此检查器更改分析状态以包含此类信息。此数据由其他检查器使用,例如 unix.Errno (C)。
限制
检查器并非总是能够提供有关参数值的注释。没有此信息,很难确认约束是否确实被违反。如果参数值是已知的常量,或者该值由先前的(不太复杂的)假设确定,则会显示参数值。
如果程序具有分析器引擎未知的不变式,或者错误报告路径包含对未知函数的调用,则检查器可能会产生误报。在这些情况下,分析器无法检测到参数的实际范围。
参数
选项 ModelPOSIX
控制检查器是否识别 POSIX 标准中的函数。
使用 ModelPOSIX=true
,许多 POSIX 函数根据 POSIX 标准建模。这包括参数范围和可能的返回值。此外,POSIX 情况下的 errno
相关的行为通常是 errno
仅在函数调用失败时设置,并且在函数调用成功后变为未定义。
使用 ModelPOSIX=false
,此检查器遵循 C99 语言标准,并且仅对其中描述的函数进行建模。两种情况下,相同的函数可能以不同的方式建模,因为标准存在差异。C 标准指定了函数的较少方面,例如,确切的 errno
行为通常未指定(并且未由检查器建模)。
该选项的默认值为 true
。
1.1.8.11. unix.Stream (C)¶
检查 C 流处理函数:fopen, fdopen, freopen, tmpfile, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, fprintf, fscanf, ungetc, getdelim, getline, fseek, fseeko, ftell, ftello, fflush, rewind, fgetpos, fsetpos, clearerr, feof, ferror, fileno
。
检查器维护有关 C 流对象 (FILE *
) 的信息,并且可以检测与流使用相关的错误条件。以下条件被检测到
传递给函数的
FILE *
指针为 NULL(唯一例外是fflush
,其中允许 NULL)。在关闭后使用流。
打开的流未关闭。
在文件末尾之后从流中读取。(这不是致命错误,但由检查器报告。流保持在 EOF 状态,读取操作失败。)
在先前的失败操作之后,文件位置不确定时使用流。某些函数(如
ferror
、clearerr
、fseek
)在此状态下是允许的。对
fseek
的无效第 3 个(“whence
”)参数。
此检查器通常将流操作分为两种情况,一种是成功情况,另一种是失败情况。在成功的情况下,它还假设 stdout
、stderr
或 stdin
的当前值不能等于 fopen
返回的文件指针。与 fopen
打开的流相反,此检查器不会检查对 stdout
、stderr
或 stdin
执行的操作。
在写入操作(如 fwrite
、fprintf
甚至 fsetpos
)的情况下,这种行为可能会在没有写入操作周围的错误检查的项目中产生大量不需要的报告,因此默认情况下,检查器假设写入操作始终成功。此行为可以通过 Pedantic
标志控制:使用 -analyzer-config unix.Stream:Pedantic=true
,检查器将对写入操作失败的情况进行建模,并报告导致错误行为的情况。(默认值为 Pedantic=false
,其中假设写入操作成功。)
void test1() {
FILE *p = fopen("foo", "r");
} // warn: opened file is never closed
void test2() {
FILE *p = fopen("foo", "r");
fseek(p, 1, SEEK_SET); // warn: stream pointer might be NULL
fclose(p);
}
void test3() {
FILE *p = fopen("foo", "r");
if (p) {
fseek(p, 1, 3); // warn: third arg should be SEEK_SET, SEEK_END, or SEEK_CUR
fclose(p);
}
}
void test4() {
FILE *p = fopen("foo", "r");
if (!p)
return;
fclose(p);
fclose(p); // warn: stream already closed
}
void test5() {
FILE *p = fopen("foo", "r");
if (!p)
return;
fgetc(p);
if (!ferror(p))
fgetc(p); // warn: possible read after end-of-file
fclose(p);
}
void test6() {
FILE *p = fopen("foo", "r");
if (!p)
return;
fgetc(p);
if (!feof(p))
fgetc(p); // warn: file position may be indeterminate after I/O error
fclose(p);
}
限制
检查器不会跟踪整数文件描述符和 FILE *
指针之间的对应关系。
1.1.9. osx¶
macOS 检查器。
1.1.9.1. osx.API (C)¶
检查各种 Apple API 的正确使用情况。
void test() {
dispatch_once_t pred = 0;
dispatch_once(&pred, ^(){}); // warn: dispatch_once uses local
}
1.1.9.2. osx.NumberObjectConversion (C, C++, ObjC)¶
检查将表示数字的对象转换为数字的错误转换。
NSNumber *photoCount = [albumDescriptor objectForKey:@"PhotoCount"];
// Warning: Comparing a pointer value of type 'NSNumber *'
// to a scalar integer value
if (photoCount > 0) {
[self displayPhotos];
}
1.1.9.3. osx.ObjCProperty (ObjC)¶
检查 Objective-C 属性的正确使用情况。
NSNumber *photoCount = [albumDescriptor objectForKey:@"PhotoCount"];
// Warning: Comparing a pointer value of type 'NSNumber *'
// to a scalar integer value
if (photoCount > 0) {
[self displayPhotos];
}
1.1.9.4. osx.SecKeychainAPI (C)¶
检查 Secure Keychain API 的正确使用情况。
void test() {
unsigned int *ptr = 0;
UInt32 length;
SecKeychainItemFreeContent(ptr, &length);
// warn: trying to free data which has not been allocated
}
void test() {
unsigned int *ptr = 0;
UInt32 *length = 0;
void *outData;
OSStatus st =
SecKeychainItemCopyContent(2, ptr, ptr, length, outData);
// warn: data is not released
}
void test() {
unsigned int *ptr = 0;
UInt32 *length = 0;
void *outData;
OSStatus st =
SecKeychainItemCopyContent(2, ptr, ptr, length, &outData);
SecKeychainItemFreeContent(ptr, outData);
// warn: only call free if a non-NULL buffer was returned
}
void test() {
unsigned int *ptr = 0;
UInt32 *length = 0;
void *outData;
OSStatus st =
SecKeychainItemCopyContent(2, ptr, ptr, length, &outData);
st = SecKeychainItemCopyContent(2, ptr, ptr, length, &outData);
// warn: release data before another call to the allocator
if (st == noErr)
SecKeychainItemFreeContent(ptr, outData);
}
void test() {
SecKeychainItemRef itemRef = 0;
SecKeychainAttributeInfo *info = 0;
SecItemClass *itemClass = 0;
SecKeychainAttributeList *attrList = 0;
UInt32 *length = 0;
void *outData = 0;
OSStatus st =
SecKeychainItemCopyAttributesAndData(itemRef, info,
itemClass, &attrList,
length, &outData);
SecKeychainItemFreeContent(attrList, outData);
// warn: deallocator doesn't match the allocator
}
1.1.9.5. osx.cocoa.AtSync (ObjC)¶
检查用作 @synchronized 互斥锁的 nil 指针。
void test(id x) {
if (!x)
@synchronized(x) {} // warn: nil value used as mutex
}
void test() {
id y;
@synchronized(y) {} // warn: uninitialized value used as mutex
}
1.1.9.6. osx.cocoa.AutoreleaseWrite¶
警告在 Objective-C 中从不同的自动释放池写入自动释放对象的潜在崩溃情况。
1.1.9.7. osx.cocoa.ClassRelease (ObjC)¶
检查是否直接向类发送 'retain'、'release' 或 'autorelease'。
@interface MyClass : NSObject
@end
void test(void) {
[MyClass release]; // warn
}
1.1.9.8. osx.cocoa.Dealloc (ObjC)¶
警告缺乏对 -dealloc 的正确实现的 Objective-C 类。
@interface MyObject : NSObject {
id _myproperty;
}
@end
@implementation MyObject // warn: lacks 'dealloc'
@end
@interface MyObject : NSObject {}
@property(assign) id myproperty;
@end
@implementation MyObject // warn: does not send 'dealloc' to super
- (void)dealloc {
self.myproperty = 0;
}
@end
@interface MyObject : NSObject {
id _myproperty;
}
@property(retain) id myproperty;
@end
@implementation MyObject
@synthesize myproperty = _myproperty;
// warn: var was retained but wasn't released
- (void)dealloc {
[super dealloc];
}
@end
@interface MyObject : NSObject {
id _myproperty;
}
@property(assign) id myproperty;
@end
@implementation MyObject
@synthesize myproperty = _myproperty;
// warn: var wasn't retained but was released
- (void)dealloc {
[_myproperty release];
[super dealloc];
}
@end
1.1.9.9. osx.cocoa.IncompatibleMethodTypes (ObjC)¶
警告类型不兼容的 Objective-C 方法签名。
@interface MyClass1 : NSObject
- (int)foo;
@end
@implementation MyClass1
- (int)foo { return 1; }
@end
@interface MyClass2 : MyClass1
- (float)foo;
@end
@implementation MyClass2
- (float)foo { return 1.0; } // warn
@end
1.1.9.10. osx.cocoa.Loops¶
改进使用 Cocoa 集合类型的循环建模。
1.1.9.11. osx.cocoa.MissingSuperCall (ObjC)¶
警告缺少对 super 的必要调用的 Objective-C 方法。
@interface Test : UIViewController
@end
@implementation test
- (void)viewDidLoad {} // warn
@end
1.1.9.12. osx.cocoa.NSAutoreleasePool (ObjC)¶
警告在 Objective-C GC 模式下对 NSAutoreleasePool 的次优使用情况。
void test() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool release]; // warn
}
1.1.9.13. osx.cocoa.NSError (ObjC)¶
检查 NSError 参数的使用情况。
@interface A : NSObject
- (void)foo:(NSError """""""""""""""""""""""")error;
@end
@implementation A
- (void)foo:(NSError """""""""""""""""""""""")error {
// warn: method accepting NSError"""""""""""""""""""""""" should have a non-void
// return value
}
@end
@interface A : NSObject
- (BOOL)foo:(NSError """""""""""""""""""""""")error;
@end
@implementation A
- (BOOL)foo:(NSError """""""""""""""""""""""")error {
*error = 0; // warn: potential null dereference
return 0;
}
@end
1.1.9.14. osx.cocoa.NilArg (ObjC)¶
检查对 ObjC 方法调用的禁止 nil 参数。
caseInsensitiveCompare
compare
compare:options
compare:options:range
compare:options:range:locale
componentsSeparatedByCharactersInSet
initWithFormat
NSComparisonResult test(NSString *s) {
NSString *aString = nil;
return [s caseInsensitiveCompare:aString];
// warn: argument to 'NSString' method
// 'caseInsensitiveCompare:' cannot be nil
}
1.1.9.15. osx.cocoa.NonNilReturnValue¶
对保证返回非 nil 值的 API 进行建模。
1.1.9.16. osx.cocoa.ObjCGenerics (ObjC)¶
检查使用 Objective-C 泛型时的类型错误。
NSMutableArray *names = [NSMutableArray array];
NSMutableArray *birthDates = names;
// Warning: Conversion from value of type 'NSDate *'
// to incompatible type 'NSString *'
[birthDates addObject: [NSDate date]];
1.1.9.17. osx.cocoa.RetainCount (ObjC)¶
检查泄漏和不正确的引用计数管理
void test() {
NSString *s = [[NSString alloc] init]; // warn
}
CFStringRef test(char *bytes) {
return CFStringCreateWithCStringNoCopy(
0, bytes, NSNEXTSTEPStringEncoding, 0); // warn
}
1.1.9.18. osx.cocoa.RunLoopAutoreleaseLeak¶
检查永远不会清空的自动释放池中的内存泄漏。
1.1.9.19. osx.cocoa.SelfInit (ObjC)¶
检查在初始化方法中是否正确初始化了 'self'。
@interface MyObj : NSObject {
id x;
}
- (id)init;
@end
@implementation MyObj
- (id)init {
[super init];
x = 0; // warn: instance variable used while 'self' is not
// initialized
return 0;
}
@end
@interface MyObj : NSObject
- (id)init;
@end
@implementation MyObj
- (id)init {
[super init];
return self; // warn: returning uninitialized 'self'
}
@end
1.1.9.20. osx.cocoa.SuperDealloc (ObjC)¶
警告在 Objective-C 中不正确使用 ‘[super dealloc]’。
@interface SuperDeallocThenReleaseIvarClass : NSObject {
NSObject *_ivar;
}
@end
@implementation SuperDeallocThenReleaseIvarClass
- (void)dealloc {
[super dealloc];
[_ivar release]; // warn
}
@end
1.1.9.21. osx.cocoa.UnusedIvars (ObjC)¶
警告从未使用过的私有 ivar。
@interface MyObj : NSObject {
@private
id x; // warn
}
@end
@implementation MyObj
@end
1.1.9.22. osx.cocoa.VariadicMethodTypes (ObjC)¶
检查是否将非 Objective-C 类型传递给仅期望 Objective-C 类型的可变参数集合初始化方法。
void test() {
[NSSet setWithObjects:@"Foo", "Bar", nil];
// warn: argument should be an ObjC pointer type, not 'char *'
}
1.1.9.23. osx.coreFoundation.CFError (C)¶
检查 CFErrorRef* 参数的使用情况。
void test(CFErrorRef *error) {
// warn: function accepting CFErrorRef* should have a
// non-void return
}
int foo(CFErrorRef *error) {
*error = 0; // warn: potential null dereference
return 0;
}
1.1.9.24. osx.coreFoundation.CFNumber (C)¶
检查 CFNumber API 的正确使用情况。
CFNumberRef test(unsigned char x) {
return CFNumberCreate(0, kCFNumberSInt16Type, &x);
// warn: 8 bit integer is used to initialize a 16 bit integer
}
1.1.9.25. osx.coreFoundation.CFRetainRelease (C)¶
检查 CFRetain/CFRelease/CFMakeCollectable 的空参数。
void test(CFTypeRef p) {
if (!p)
CFRetain(p); // warn
}
void test(int x, CFTypeRef p) {
if (p)
return;
CFRelease(p); // warn
}
1.1.9.26. osx.coreFoundation.containers.OutOfBounds (C)¶
检查使用 ‘CFArray’ API 时是否超出索引范围。
void test() {
CFArrayRef A = CFArrayCreate(0, 0, 0, &kCFTypeArrayCallBacks);
CFArrayGetValueAtIndex(A, 0); // warn
}
1.1.9.27. osx.coreFoundation.containers.PointerSizedValues (C)¶
如果使用非指针大小的值创建 ‘CFArray’,‘CFDictionary’,‘CFSet’,则发出警告。
void test() {
int x[] = { 1 };
CFArrayRef A = CFArrayCreate(0, (const void """""""""""""""""""""""")x, 1,
&kCFTypeArrayCallBacks); // warn
}
1.1.10. Fuchsia¶
Fuchsia 是一个开源的基于能力的操作系统,目前由 Google 开发。本节描述了可以找到 Fuchsia API 各种误用的检查器。
1.1.10.1. fuchsia.HandleChecker¶
句柄标识资源。类似于指针,它们可能被泄漏、双重释放或使用后释放。此检查尝试找到此类问题。
void checkLeak08(int tag) {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
if (tag)
zx_handle_close(sa);
use(sb); // Warn: Potential leak of handle
zx_handle_close(sb);
}
1.1.11. WebKit¶
WebKit 是一个开源的网页浏览器引擎,可用于 macOS、iOS 和 Linux。本节描述了可以在 WebKit 代码库中查找问题的检查器。
大多数检查器侧重于内存管理,WebKit 为此使用自定义的引用计数智能指针实现。
- 检查器是用与引用计数相关的术语制定的
引用计数类型 既可以是
Ref<T>
也可以是RefPtr<T>
。可引用计数类型 是任何实现
ref()
和deref()
方法的类型,因为RefPtr<>
是一个模板(即依赖于鸭子类型)。未计数类型 是可引用计数但不是引用计数类型。
1.1.11.1. webkit.RefCntblBaseVirtualDtor¶
用作基类的所有未计数类型必须具有虚拟析构函数。
引用计数类型通过原始指针保存它们的引用计数数据,并允许从引用计数指针到派生类型的隐式向上转型到引用计数指针到基类类型。这可能导致(动态)派生类型的对象通过指向基类类型的指针删除,而 C++ 标准将这种情况定义为 UB,如果基类没有虚拟析构函数 [expr.delete]
。
struct RefCntblBase {
void ref() {}
void deref() {}
};
struct Derived : RefCntblBase { }; // warn
1.1.11.2. webkit.NoUncountedMemberChecker¶
指向未计数类型的原始指针和引用不能用作类成员。只允许引用计数类型。
struct RefCntbl {
void ref() {}
void deref() {}
};
struct Foo {
RefCntbl * ptr; // warn
RefCntbl & ptr; // warn
// ...
};
1.1.11.3. webkit.UncountedLambdaCapturesChecker¶
指向未计数类型的原始指针和引用不能在 lambda 中捕获。只允许引用计数类型。
struct RefCntbl {
void ref() {}
void deref() {}
};
void foo(RefCntbl* a, RefCntbl& b) {
[&, a](){ // warn about 'a'
do_something(b); // warn about 'b'
};
};
1.2. 实验性检查器¶
这些是检查器,它们有已知问题或限制,使它们无法默认启用。它们很可能会有误报。欢迎提交错误报告,尤其是补丁。
1.2.1. alpha.clone¶
1.2.1.1. alpha.clone.CloneChecker (C, C++, ObjC)¶
报告类似的代码段。
void log();
int max(int a, int b) { // warn
log();
if (a > b)
return a;
return b;
}
int maxClone(int x, int y) { // similar code here
log();
if (x > y)
return x;
return y;
}
1.2.2. alpha.core¶
1.2.2.1. alpha.core.BoolAssignment (ObjC)¶
警告将非-{0,1} 值分配给布尔变量。
void test() {
BOOL b = -1; // warn
}
1.2.2.2. alpha.core.C11Lock¶
类似于 alpha.unix.PthreadLock,检查 mtx_t
互斥锁的锁定/解锁。
mtx_t mtx1;
void bad1(void)
{
mtx_lock(&mtx1);
mtx_lock(&mtx1); // warn: This lock has already been acquired
}
1.2.2.3. alpha.core.CastSize (C)¶
检查在将 malloc 的类型 T
转换为其他类型时,大小是否为 T
大小的倍数。
void test() {
int *x = (int *) malloc(11); // warn
}
1.2.2.4. alpha.core.CastToStruct (C, C++)¶
检查从非结构指针到结构指针的转换。
// C
struct s {};
void test(int *p) {
struct s *ps = (struct s *) p; // warn
}
// C++
class c {};
void test(int *p) {
c *pc = (c *) p; // warn
}
1.2.2.5. alpha.core.Conversion (C, C++, ObjC)¶
隐式转换中符号/精度的丢失。
void test(unsigned U, signed S) {
if (S > 10) {
if (U < S) {
}
}
if (S < -10) {
if (U < S) { // warn (loss of sign)
}
}
}
void test() {
long long A = 1LL << 60;
short X = A; // warn (loss of precision)
}
1.2.2.6. alpha.core.DynamicTypeChecker (ObjC)¶
检查对象动态类型和静态类型不相关的案例。
id date = [NSDate date];
// Warning: Object has a dynamic type 'NSDate *' which is
// incompatible with static type 'NSNumber *'"
NSNumber *number = date;
[number doubleValue];
1.2.2.7. alpha.core.FixedAddr (C)¶
检查将固定地址分配给指针。
void test() {
int *p;
p = (int *) 0x10000; // warn
}
1.2.2.8. alpha.core.IdenticalExpr (C, C++)¶
警告在运算符中意外使用相同的表达式。
// C
void test() {
int a = 5;
int b = a | 4 | a; // warn: identical expr on both sides
}
// C++
bool f(void);
void test(bool b) {
int i = 10;
if (f()) { // warn: true and false branches are identical
do {
i--;
} while (f());
} else {
do {
i--;
} while (f());
}
}
1.2.2.9. alpha.core.PointerArithm (C)¶
检查对数组元素以外位置的指针运算。
void test() {
int x;
int *p;
p = &x + 1; // warn
}
1.2.2.10. alpha.core.StackAddressAsyncEscape (ObjC)¶
检查堆栈内存的地址是否不逃逸涉及 dispatch_after 或 dispatch_async 的函数。此检查器是 core.StackAddressEscape
的一部分,但暂时被禁用,直到一些误报被修复。
dispatch_block_t test_block_inside_block_async_leak() {
int x = 123;
void (^inner)(void) = ^void(void) {
int y = x;
++y;
};
void (^outer)(void) = ^void(void) {
int z = x;
++z;
inner();
};
return outer; // warn: address of stack-allocated block is captured by a
// returned block
}
1.2.2.11. alpha.core.StdVariant (C++)¶
检查是否使用 std::get
从 std::variant
实例中检索活动类型的的值。如果发生错误的变体类型访问(访问的类型与活动类型不同),则会发出警告。目前,此检查器没有考虑异常处理。
void test() {
std::variant<int, char> v = 25;
char c = stg::get<char>(v); // warn: "int" is the active alternative
}
1.2.2.12. alpha.core.TestAfterDivZero (C)¶
检查对后来与 0 比较的变量进行除法运算。比较要么无用,要么存在除以零的情况。
void test(int x) {
var = 77 / x;
if (x == 0) { } // warn
}
1.2.3. alpha.cplusplus¶
1.2.3.1. alpha.cplusplus.DeleteWithNonVirtualDtor (C++)¶
报告对基类中具有非虚拟析构函数的多态对象的析构。
class NonVirtual {};
class NVDerived : public NonVirtual {};
NonVirtual *create() {
NonVirtual *x = new NVDerived(); // note: Casting from 'NVDerived' to
// 'NonVirtual' here
return x;
}
void foo() {
NonVirtual *x = create();
delete x; // warn: destruction of a polymorphic object with no virtual
// destructor
}
1.2.3.2. alpha.cplusplus.InvalidatedIterator (C++)¶
检查对已失效迭代器的使用。
void bad_copy_assign_operator_list1(std::list &L1,
const std::list &L2) {
auto i0 = L1.cbegin();
L1 = L2;
*i0; // warn: invalidated iterator accessed
}
1.2.3.3. alpha.cplusplus.IteratorRange (C++)¶
检查对超出其有效范围的迭代器的使用。
void simple_bad_end(const std::vector &v) {
auto i = v.end();
*i; // warn: iterator accessed outside of its range
}
1.2.3.4. alpha.cplusplus.MismatchedIterator (C++)¶
检查对不同容器的迭代器的使用情况,在这些情况下应该使用同一个容器的迭代器。
void bad_insert3(std::vector &v1, std::vector &v2) {
v2.insert(v1.cbegin(), v2.cbegin(), v2.cend()); // warn: container accessed
// using foreign
// iterator argument
v1.insert(v1.cbegin(), v1.cbegin(), v2.cend()); // warn: iterators of
// different containers
// used where the same
// container is
// expected
v1.insert(v1.cbegin(), v2.cbegin(), v1.cend()); // warn: iterators of
// different containers
// used where the same
// container is
// expected
}
1.2.3.5. alpha.cplusplus.SmartPtr (C++)¶
检查对空智能指针的解引用。
void deref_smart_ptr() {
std::unique_ptr<int> P;
*P; // warn: dereference of a default constructed smart unique_ptr
}
1.2.4. alpha.deadcode¶
1.2.4.1. alpha.deadcode.UnreachableCode (C, C++)¶
检查不可到达的代码。
// C
int test() {
int x = 1;
while(x);
return x; // warn
}
// C++
void test() {
int a = 2;
while (a > 1)
a--;
if (a > 1)
a++; // warn
}
// Objective-C
void test(id x) {
return;
[x retain]; // warn
}
1.2.5. alpha.fuchsia¶
1.2.5.1. alpha.fuchsia.Lock¶
类似于 alpha.unix.PthreadLock,检查 fuchsia 互斥锁的锁定/解锁。
spin_lock_t mtx1;
void bad1(void)
{
spin_lock(&mtx1);
spin_lock(&mtx1); // warn: This lock has already been acquired
}
1.2.6. alpha.llvm¶
1.2.6.1. alpha.llvm.Conventions¶
检查 LLVM 代码库约定代码。
StringRef 不应该绑定到寿命短于 StringRef 的临时 std::string。
Clang AST 节点不应该包含可以分配内存的字段。
1.2.7. alpha.osx¶
1.2.7.1. alpha.osx.cocoa.DirectIvarAssignment (ObjC)¶
检查对实例变量的直接赋值。
@interface MyClass : NSObject {}
@property (readonly) id A;
- (void) foo;
@end
@implementation MyClass
- (void) foo {
_A = 0; // warn
}
@end
1.2.7.2. alpha.osx.cocoa.DirectIvarAssignmentForAnnotatedFunctions (ObjC)¶
检查使用 objc_no_direct_instance_variable_assignment
注解的方法对实例变量的直接赋值。
@interface MyClass : NSObject {}
@property (readonly) id A;
- (void) fAnnotated __attribute__((
annotate("objc_no_direct_instance_variable_assignment")));
- (void) fNotAnnotated;
@end
@implementation MyClass
- (void) fAnnotated {
_A = 0; // warn
}
- (void) fNotAnnotated {
_A = 0; // no warn
}
@end
1.2.7.3. alpha.osx.cocoa.InstanceVariableInvalidation (ObjC)¶
检查使用 objc_instance_variable_invalidator 注解的方法是否使可失效的实例变量失效。
@protocol Invalidation <NSObject>
- (void) invalidate
__attribute__((annotate("objc_instance_variable_invalidator")));
@end
@interface InvalidationImpObj : NSObject <Invalidation>
@end
@interface SubclassInvalidationImpObj : InvalidationImpObj {
InvalidationImpObj *var;
}
- (void)invalidate;
@end
@implementation SubclassInvalidationImpObj
- (void) invalidate {}
@end
// warn: var needs to be invalidated or set to nil
1.2.7.4. alpha.osx.cocoa.MissingInvalidationMethod (ObjC)¶
检查包含可失效实例变量的类中是否包含失效方法。
@protocol Invalidation <NSObject>
- (void)invalidate
__attribute__((annotate("objc_instance_variable_invalidator")));
@end
@interface NeedInvalidation : NSObject <Invalidation>
@end
@interface MissingInvalidationMethodDecl : NSObject {
NeedInvalidation *Var; // warn
}
@end
@implementation MissingInvalidationMethodDecl
@end
1.2.7.5. alpha.osx.cocoa.localizability.PluralMisuseChecker (ObjC)¶
警告在生成本地化字符串时代码中使用单数或复数模式。
NSString *reminderText =
NSLocalizedString(@"None", @"Indicates no reminders");
if (reminderCount == 1) {
// Warning: Plural cases are not supported across all languages.
// Use a .stringsdict file instead
reminderText =
NSLocalizedString(@"1 Reminder", @"Indicates single reminder");
} else if (reminderCount >= 2) {
// Warning: Plural cases are not supported across all languages.
// Use a .stringsdict file instead
reminderText =
[NSString stringWithFormat:
NSLocalizedString(@"%@ Reminders", @"Indicates multiple reminders"),
reminderCount];
}
1.2.8. alpha.security¶
1.2.8.1. alpha.security.ArrayBound (C)¶
警告缓冲区溢出(较旧的检查器)。
void test() {
char *s = "";
char c = s[1]; // warn
}
struct seven_words {
int c[7];
};
void test() {
struct seven_words a, *p;
p = &a;
p[0] = a;
p[1] = a;
p[2] = a; // warn
}
// note: requires unix.Malloc or
// alpha.unix.MallocWithAnnotations checks enabled.
void test() {
int *p = malloc(12);
p[3] = 4; // warn
}
void test() {
char a[2];
int *b = (int*)a;
b[1] = 3; // warn
}
1.2.8.2. alpha.security.ArrayBoundV2 (C)¶
警告缓冲区溢出(较新的检查器)。
void test() {
char *s = "";
char c = s[1]; // warn
}
void test() {
int buf[100];
int *p = buf;
p = p + 99;
p[1] = 1; // warn
}
// note: compiler has internal check for this.
// Use -Wno-array-bounds to suppress compiler warning.
void test() {
int buf[100][100];
buf[0][-1] = 1; // warn
}
// note: requires optin.taint check turned on.
void test() {
char s[] = "abc";
int x = getchar();
char c = s[x]; // warn: index is tainted
}
1.2.8.3. alpha.security.ReturnPtrRange (C)¶
检查是否向调用者返回了越界指针。
static int A[10];
int *test() {
int *p = A + 10;
return p; // warn
}
int test(void) {
int x;
return x; // warn: undefined or garbage returned
}
1.2.9. alpha.security.cert¶
SEI CERT 检查器,尝试根据其 C 编码规则 查找错误。
1.2.10. alpha.unix¶
1.2.10.1. alpha.unix.Chroot (C)¶
检查 chroot 的不当使用。
void f();
void test() {
chroot("/usr/local");
f(); // warn: no call of chdir("/") immediately after chroot
}
1.2.10.2. alpha.unix.PthreadLock (C)¶
简单锁 -> 解锁检查器。适用于:pthread_mutex_lock, pthread_rwlock_rdlock, pthread_rwlock_wrlock, lck_mtx_lock, lck_rw_lock_exclusive
lck_rw_lock_shared, pthread_mutex_trylock, pthread_rwlock_tryrdlock, pthread_rwlock_tryrwlock, lck_mtx_try_lock, lck_rw_try_lock_exclusive, lck_rw_try_lock_shared, pthread_mutex_unlock, pthread_rwlock_unlock, lck_mtx_unlock, lck_rw_done
。
pthread_mutex_t mtx;
void test() {
pthread_mutex_lock(&mtx);
pthread_mutex_lock(&mtx);
// warn: this lock has already been acquired
}
lck_mtx_t lck1, lck2;
void test() {
lck_mtx_lock(&lck1);
lck_mtx_lock(&lck2);
lck_mtx_unlock(&lck1);
// warn: this was not the most recently acquired lock
}
lck_mtx_t lck1, lck2;
void test() {
if (lck_mtx_try_lock(&lck1) == 0)
return;
lck_mtx_lock(&lck2);
lck_mtx_unlock(&lck1);
// warn: this was not the most recently acquired lock
}
1.2.10.3. alpha.unix.SimpleStream (C)¶
检查流 API 的误用。检查流 API 的误用:fopen, fclose
(演示检查器,演示主题 (幻灯片 , 视频) 由 Anna Zaks 和 Jordan Rose 在 2012 LLVM 开发者会议 上介绍)。
void test() {
FILE *F = fopen("myfile.txt", "w");
} // warn: opened file is never closed
void test() {
FILE *F = fopen("myfile.txt", "w");
if (F)
fclose(F);
fclose(F); // warn: closing a previously closed file stream
}
1.2.10.4. alpha.unix.cstring.BufferOverlap (C)¶
检查两个缓冲区参数是否重叠。适用于:memcpy, mempcpy, wmemcpy, wmempcpy
。
void test() {
int a[4] = {0};
memcpy(a + 2, a + 1, 8); // warn
}
1.2.10.5. alpha.unix.cstring.NotNullTerminated (C)¶
检查参数是否为非空终止字符串;适用于 strlen
,strcpy
,strcat
,strcmp
函数族。
仅检测非常基本的情况,即传递的内存块绝对不同于空终止字符串。此检查器不会查找是否传递了内存缓冲区,其中缺少终止零字符。
void test1() {
int l = strlen((char *)&test); // warn
}
void test2() {
label:
int l = strlen((char *)&&label); // warn
}
1.2.10.6. alpha.unix.cstring.OutOfBounds (C)¶
检查字符串函数中的越界访问,例如:memcpy, bcopy, strcpy, strncpy, strcat, strncat, memmove, memcmp, memset
等等。
此检查也适用于字符串文字,但已知分析器在确定字符串长度时无法检测到嵌入的 NULL 字符,这是一个已知错误。
void test1() {
const char str[] = "Hello world";
char buffer[] = "Hello world";
memcpy(buffer, str, sizeof(str) + 1); // warn
}
void test2() {
const char str[] = "Hello world";
char buffer[] = "Helloworld";
memcpy(buffer, str, sizeof(str)); // warn
}
1.2.10.7. alpha.unix.cstring.UninitializedRead (C)¶
- 检查从常见的内存复制/操作函数(如
memcpy, mempcpy, memmove, memcmp, strcmp, strncmp, strcpy, strlen, strsep
等等。
void test() {
char src[10];
char dst[5];
memcpy(dst,src,sizeof(dst)); // warn: Bytes string function accesses uninitialized/garbage values
}
限制
由于分析器中内存建模的限制,可能会观察到很多类似这样的误报
void false_positive() { int src[] = {1, 2, 3, 4}; int dst[5] = {0}; memcpy(dst, src, 4 * sizeof(int)); // false-positive: // The 'src' buffer was correctly initialized, yet we cannot conclude // that since the analyzer could not see a direct initialization of the // very last byte of the source buffer. }更多详细信息请参阅相应的 GitHub 问题。
1.2.11. alpha.WebKit¶
1.2.11.1. alpha.webkit.NoUncheckedPtrMemberChecker¶
支持 CheckedPtr 或 CheckedRef 的对象的原始指针和引用不能用作类成员。只允许 CheckedPtr、CheckedRef、RefPtr 或 Ref。
struct CheckableObj {
void incrementPtrCount() {}
void decrementPtrCount() {}
};
struct Foo {
CheckableObj* ptr; // warn
CheckableObj& ptr; // warn
// ...
};
有关详细信息,请参阅 WebKit 更安全的 C++ 编程指南。
1.2.11.2. alpha.webkit.UncountedCallArgsChecker¶
此规则的目标是确保作为调用参数传递的任何动态分配的引用计数对象的寿命跨越调用结束。这适用于对任何函数、方法、lambda、函数指针或仿函数的调用。引用计数类型不应该在堆栈上分配,因此我们检查未计数类型的原始指针和引用的参数。
以下是一些我们警告的情况示例,因为它们 *可能* 不安全。逻辑是,我们可以保证一个参数是安全的,或者它被认为是错误的,如果不是错误,就是容易出错的。
RefCountable* provide_uncounted(); void consume(RefCountable*); // In these cases we can't make sure callee won't directly or indirectly call `deref()` on the argument which could make it unsafe from such point until the end of the call. void foo1() { consume(provide_uncounted()); // warn } void foo2() { RefCountable* uncounted = provide_uncounted(); consume(uncounted); // warn }
虽然我们通过 webkit.NoUncountedMemberChecker 强制成员变量被引用计数,但同一个类的任何方法仍然可以无限制地访问这些成员变量。因为从调用者的角度来看,我们无法保证某个成员不会被被调用者修改(直接或间接),所以我们认为从成员中获得的值不安全。
注意:很可能可以通过减少误报来提高这种启发式的准确性——例如,调用没有除了指针之外的任何参数的自由函数应该是安全的,因为被调用者将无法篡改成员,除非它是一个全局变量。
struct Foo { RefPtr<RefCountable> member; void consume(RefCountable*) { /* ... */ } void bugprone() { consume(member.get()); // warn } };
此规则的实现是一种启发式方法——我们定义了一组被认为安全作为参数传递的值类型。如果我们无法证明一个参数是安全的,它将被视为错误。
允许的参数类型
从引用计数对象获得的值(包括临时对象,因为这些对象也会在调用中持续存在)
RefCountable* provide_uncounted(); void consume(RefCountable*); void foo() { RefPtr<RefCountable> rc = makeRef(provide_uncounted()); consume(rc.get()); // ok consume(makeRef(provide_uncounted()).get()); // ok }
将未计数参数从调用者转发到被调用者
void foo(RefCountable& a) { bar(a); // ok }
foo()
的调用者负责a
的寿命。this
指针void Foo::foo() { baz(this); // ok }
foo()
的调用者负责保持this
指针指向的内存安全。常量
foo(nullptr, NULL, 0); // ok
我们还定义了一组安全的转换,如果传递给它作为输入的是一个安全值,它将提供(通常是返回值)一个安全值(或提供安全值的 object)。这也是一种启发式方法。
引用计数类型的构造函数(包括工厂方法)
引用计数类型的 getter
成员重载运算符
强制转换
一元运算符,如
&
或*
1.2.11.3. alpha.webkit.UncountedLocalVarsChecker¶
此规则的目标是确保任何未计数的局部变量都由一个引用计数对象支持,该对象的寿命严格大于未计数局部变量的作用域。为了安全起见,我们要求未计数变量的作用域嵌入在支持它的引用计数对象的范围内。
以下是一些我们认为安全的情况示例
void foo1() { RefPtr<RefCountable> counted; // The scope of uncounted is EMBEDDED in the scope of counted. { RefCountable* uncounted = counted.get(); // ok } } void foo2(RefPtr<RefCountable> counted_param) { RefCountable* uncounted = counted_param.get(); // ok } void FooClass::foo_method() { RefCountable* uncounted = this; // ok }
以下是一些我们警告的情况示例,因为它们 *可能* 不安全。逻辑是,我们可以保证一个局部变量是安全的,或者它被认为是不安全的。
void foo1() { RefCountable* uncounted = new RefCountable; // warn } RefCountable* global_uncounted; void foo2() { RefCountable* uncounted = global_uncounted; // warn } void foo3() { RefPtr<RefCountable> counted; // The scope of uncounted is not EMBEDDED in the scope of counted. RefCountable* uncounted = counted.get(); // warn }
1.2.11.4. alpha.webkit.UncheckedLocalVarsChecker¶
该规则的目的是确保任何未经检查的局部变量都由具有比未经检查的局部变量作用域严格更大的生存期的 CheckedPtr 或 CheckedRef 支持。为了安全起见,我们要求未经检查变量的作用域嵌入到支持它的 CheckedPtr/CheckRef 对象的作用域中。
以下是一些我们认为安全的情况示例
void foo1() { CheckedPtr<RefCountable> counted; // The scope of uncounted is EMBEDDED in the scope of counted. { RefCountable* uncounted = counted.get(); // ok } } void foo2(CheckedPtr<RefCountable> counted_param) { RefCountable* uncounted = counted_param.get(); // ok } void FooClass::foo_method() { RefCountable* uncounted = this; // ok }
以下是一些我们警告的情况示例,因为它们 *可能* 不安全。逻辑是,我们可以保证一个局部变量是安全的,或者它被认为是不安全的。
void foo1() { RefCountable* uncounted = new RefCountable; // warn } RefCountable* global_uncounted; void foo2() { RefCountable* uncounted = global_uncounted; // warn } void foo3() { RefPtr<RefCountable> counted; // The scope of uncounted is not EMBEDDED in the scope of counted. RefCountable* uncounted = counted.get(); // warn }
1.3. 调试检查器¶
1.3.1. debug¶
用于调试分析器的检查器。 调试检查 页面包含详细描述。
1.3.1.1. debug.AnalysisOrder¶
打印在分析过程中按顺序调用的回调。
1.3.1.2. debug.ConfigDumper¶
转储配置表。
1.3.1.3. debug.DumpCFG 显示¶
控制流图。
1.3.1.4. debug.DumpCallGraph¶
显示调用图。
1.3.1.5. debug.DumpCalls¶
在引擎遍历时打印调用。
1.3.1.6. debug.DumpDominators¶
打印给定 CFG 的支配树。
1.3.1.7. debug.DumpLiveVars¶
打印活动变量分析的结果。
1.3.1.8. debug.DumpTraversal¶
在引擎遍历时打印分支条件。
1.3.1.9. debug.ExprInspection¶
检查分析器对表达式的理解。
1.3.1.10. debug.Stats¶
使用分析器统计信息发出警告。
1.3.1.11. debug.TaintTest¶
将污染的符号标记为污染符号。
1.3.1.12. debug.ViewCFG¶
使用 GraphViz 查看控制流图。
1.3.1.13. debug.ViewCallGraph¶
使用 GraphViz 查看调用图。
1.3.1.14. debug.ViewExplodedGraph¶
使用 GraphViz 查看爆炸图。