Finding Number Related Memory Corruption Vulns

The root cause of many vulnerabilities are from the mishandling of numbers. The standard int type can go from 0x7FFFFFFF all the way to -0x80000000 (notice the negative) with an integer overflow. Or, it can be truncated and change the number from positive to negative. Integers can be a nightmare in C and have caused many memory corruption vulnerabilities over the years.

Recently quite a few findings have caught my attention that fall into the integer bug classes. A few to call out are the integer truncation bug in the Linux kernel found by Qualys, the signedness conversion bug (issue 2) in the BSD hypervisor by GHSL and an integer overflow in the Kindle on a buffer allocation size found by Checkpoint. Then, my hacking buddy seiraib found an integer overflow bug fuzzing but we could not find where the overflow actually occurred at. All of this got me wondering: “Can these bug classes be discovered at compilation or immediately crash at runtime?

Yes, yes they can! GCC and Clang have compilation flags to find several bug classes at compile time. Additionally, there are some runtime protections that can cause crashes to make root causing MUCH easier. This blog post is about finding vulnerabilities via compiler warnings and dynamic instrumentation. All of the source code snippets with extra examples can be found at mdulin2/integer_ compilation_flags.

Bug Classes

The goal is to change a number in a way that the program does not expect to cause memory corruption down the road. There are three number (but mainly integer) vulnerability classes in C that will be focused on:

  • Overflow/underflow: Integers will simply wrap around if you go past the maximum or minimum value of the type in C. For instance, an overflow of a signed integer would go from 0x7FFFFFFF all the way to -0x80000000. The underflow goes in the opposite direction and happens when subtracting values instead.
  • Truncation: Shrinking the storage capacity of a number. For instance, going from uint64_t to uint32_t would cut the storage capacity of the number in half. This has the potential to drastically change the value and signedness of the number.
  • Signedness conversion: Numbers are either signed and unsigned (unsigned int and int for example). Going between these can have terrible consequences when converting from a negative signed number to an unsigned number or a very large unsigned number to a signed number. For instance, an unsigned integer of 0xFFFFFFFF converted to a signed integer would be -1 instead of a very large number.

Static Analysis

Two of these bug classes can be determined at compile time via specific compilation flags. Although there will likely be an exceptional amount of unexploitable occurrences of this, there are wonderful leads for finding vulnerabilities.

Truncation

Truncation Warning Source Code WConversionFigure 1 – Truncation code
Signedness conversion Warning Source Code WConversionFigure 2 – Signedness Conversion Code

Using the Wconversion flag during compilation will output the warning “implicit conversions that may alter a value“. This is directly referencing truncation and conversion bugs!

For example, code for the truncation case can seen in Figure 1. This code has a type of long long that is being converted to an int. Because this changes the storage capacity from 64-bit to 32-bit, this has the potential to cause corruption. When this code is compiled with the Wconversion flag, a warning message will appear mentioning a truncation issue! This warning can be seen in Figure 3. This exact error message could have been used to find the Linux Kernel kernel bug mentioned above, when the conversion from size_t (unsigned long long) to int was performed.

Truncation warning message
Figure 3 – Truncation warning message

Another interesting item to consider is the case with float and double. Since a double is 2x the size of a float, the same type of truncation can be detected by the same compilation flag. An example of this in code is shown here.

Signedness

The Wconversion flag can also be used in the detection of signedness conversion bugs. This happens when converting from a signed to an unsigned or converting from an unsigned integer to a signed integer; both of these issues are shown in Figure 2 above. An example of the compile time error message is shown in Figure 4 below.

Signedness conversion warning message
Figure 4 – Signedness conversion warning message

Static Analysis Wrap Up

These flags only check for implicit conversions. Sometimes, a value needs to be converted from an unsigned integer to a signed integer in order to do some math, which is valid and expected C. When a number is casted explicitly, these error messages will not show up, as a result. In order to find explicit casts, using something like CodeQL, manual review or dynamic testing is the way to go.

Although we have only been using Wconversion for the static analysis so far, there are a plethora of flags that actually make up this single flag. For instance, Wsign-conversion flag warns for implicit conversions that may change the sign of an integer value. For more information on these, visit the GCC documentation. But, the tldr; is to just use Wconversion to catch all of these issues at compilation time.

Dynamic Instrumentation

The static analysis is likely to pull up mostly false positives with a few real bugs. However, dynamic analysis, will always find real bugs, if the code path can be triggered. Below, we will show instrumentation options to crash/notify on integer related bugs. Instead of discussing bug classes, we are going to talk about a few specific flags in GCC and Clang.

ftrapv

This option generates traps for signed overflows on addition, subtraction and multiplication operations. Again, since this is dynamic testing, this will cause the program to crash whenever this occurs! An example of this code can be seen below:

int main(){
	int a = 0x7FFFFFFF; 
	a = a + 1; 
	printf("Value: %d\n", a);
}

The code above will cause an integer overflow when the line a = a + 1; is executed since the maximum size of an integer in C is 0x7FFFFFFF. What happens? The program is aborted and never reaches the print statement. By forcing the crash when the overflow occurs, we guarantee that the bug is detected and the cause of the overflow is much easier to find. This is extremely useful when fuzzing and trying to root cause a crash!

It should also be noted that this flag detects signed integer underflows as well.

fsanitize=integer

The UBSAN (Uninitialized Behavior Sanitizer) for finding integer bugs is amazing! This flag is specific to Clang, for the integer related bugs.

While the ftrapv catches only signed integer overflows, fsanitize=integer will crash on unsigned and signed integer overflows (signed-integer-overflow and unsigned-integer-overflow). This means that all integer overflows, regardless of sign, via addition, subtraction or multiplication will be caught at runtime! Damn, this is a major improvement.

Besides the discovering of overflow/underflows in programs, we can find the two other bug classes mentioned with this flag: truncation (implicit-signed-integer-truncation & implicit-unsigned-integer-truncation) and conversion (implicit-integer-sign-change). Unlike the static method above, a bad math operation must occur to trigger UBSAN to crash the program.

Let’s see this in action though! We will use the original signedness issue (Figure 1). When we run the code, the long long with the value LONG_MAX ( 0x7FFFFFFFFFFFFFFF) will be cut in half (truncated) because of the conversion to an integer. As a result, this will be 0xFFFFFFFF or -1 as a signed integer. Because of the extra instrumentation added, the program crashes upon this truncation happening! Nice for us, the instrumentation does not require a sign change; it checks that the value inside of the long long will not fit into the int. This crash can be seen in Figure 5 below.

Truncation Crash Clang
Figure 5 – Truncation Conversion Crash

Are We Missing Anything?

We have mentioned dynamic and static checks for integer overflows/underflows, integer truncation’s and signedness conversion issues. However, there is one thing missing from the dynamic instrumentation: floating point math.

When a float is overflowed in C, it goes to infinity or inf. Oddly enough, the float never truly wraps around because of how floating point math handles precision; it simply just goes to infinity! An example of this overflow can be seen at here.

An additional uncaught bug is float truncation. For instance, the conversion from a double to a float is not caught at runtime. There is a misleading UBSAN flag (-fsanitize=float-cast-overflow) that only finds bad double/float to integer conversions but NOT truncation bugs between floating point numbers. An example of this can be seen here.

Knowing about floats becoming inf and NaN may be useful to know since some crazy issues, such as Jack Bakers NaN propagation bug in the Unreal Gaming Engine do happen. However, there is currently no detection on the overflowing, underflowing, truncation or NaN/Inf usage of floating point numbers in C at runtime. A deep dive into the craziness of floating point math in C is out of the scope for this article but it is really interesting and worth an afternoon of learning about, as it is easy to make mistakes with.

Conclusion

When trying to find vulnerabilities, any help from automated tools or instrumentation is a huge win. Besides the integer vulnerability classes mentioned above, there are loads of other interesting flags and instrumentation, such as the well known ASAN (Address Sanitizer) to help find use after free bugs or the lesser known use after scope vulnerability on stack memory. There are many other compilation flags and instrumentation options to help find specific bugs classes as well; this article only focuses on finding integer related vulnerability classes.

I hope the knowledge of these compilation warnings and dynamic instrumentation helps you find many bugs in the future! Feel free to reach out to me (contact information is in the footer) if you have any questions or comments about this article. Cheers from Maxwell “ꓘ” Dulin.

许多漏洞的根本原因是数字处理不当。标准的 int 类型可以从0x7fffff 一直到 -0 x80000000(注意负数) ,并带有整数溢出。或者,它可以被截断并将数字从正变为负。在 c 语言中,整数可能是一场噩梦,多年来已经导致了许多内存损坏漏洞。最近有不少发现引起了我的注意,这些发现都属于整数 bug 类。其中一些问题是 Qualys 在 Linux 内核中发现的整数截断错误,GHSL 在 BSD 管理程序中发现的符号转换错误(问题2) ,以及 Checkpoint 在缓冲区分配大小上发现的 Kindle 中的整数溢出。然后,我的黑客朋友 seiraib 发现了一个整数溢出 bug fuzzing,但是我们找不到溢出实际发生在哪里。所有这些都让我感到疑惑: “这些 bug 类能够在编译时被发现,还是在运行时立即崩溃?”是的,他们可以!GCC 和 Clang 有编译标志,可以在编译时找到几个 bug 类。此外,还有一些运行时保护可能会导致崩溃,从而使根源问题更容易解决。这篇博文是关于通过编译器警告和动态插装找到漏洞的。所有带有额外示例的源代码片段都可以在 mdulin2/integer _ compilation _ flags 中找到。目标是以程序不希望在未来引起内存损坏的方式更改一个数字。C 语言中有三个数字(但主要是整数)漏洞类将被关注: Overflow/underflow: 如果超过 c 语言中类型的最大值或最小值,Integers 将简单地进行包装。例如,有符号整数的溢出将从0x7fff 一直到 -0x80000000。下溢流向相反的方向,而是在减去值的时候发生。截断: 缩小一个数字的存储容量。例如,从 uint64 t 到 uint32 t 会将数字的存储容量减少一半。这有可能大大改变数字的值和符号。Signedness 转换: 数字可以是有符号的,也可以是无符号的(例如无符号整型和整型)。当从负有符号数转换为无符号数或从非常大的无符号数转换为有符号数时,在这两者之间进行转换会产生可怕的后果。例如,将无符号整数0xFFFFFFFF 转换为有符号整数将是-1,而不是非常大的数字。静态分析这些 bug 类中的两个可以在编译时通过特定的编译标志来确定。尽管这种情况可能会出现大量不可利用的事件,但找到漏洞还是有很好的线索。截断图1-截断代码图2-Signedness 转换代码在编译期间使用 Wconversion 标志将输出警告“可能改变值的隐式转换”。这是直接引用截断和转换错误!例如,截断案例的代码可以在图1中看到。此代码的 long 类型正在转换为 int 类型。因为这会将存储容量从64位更改为32位,所以有可能导致损坏。使用 Wconversion 标志编译此代码时,将出现提及截断问题的警告消息!此警告见图3。当执行从 size _ t (unsignedlong)到 int 的转换时,可以使用这个确切的错误消息来查找上面提到的 Linux 内核错误。图3-trunation 警告消息另一个需要考虑的有趣项目是 float 和 double。由于 double 的大小是 float 的2倍,因此可以通过相同的编译标志检测到相同类型的截断。代码中的一个例子如下所示。信号 Wconversion 标志也可以用于检测信号转换错误。当从有符号整数转换为无符号整数或从无符号整数转换为有符号整数时,就会发生这种情况; 上面的图2显示了这两个问题。下面的图4显示了编译时错误消息的一个示例。图4-Signedness 转换警告消息 Static Analysis Wrap Up 这些标志只检查隐式转换。有时,值需要从无符号整数转换为有符号整数,以便进行一些数学运算,这是有效的,也是预期的 c。当一个数字被显式转储时,这些错误消息将不会显示出来。为了找到明确的强制转换,可以使用诸如 CodeQL 之类的东西,手动复查或动态测试。虽然到目前为止我们只使用 Wconversion 进行静态分析,但是实际上有很多标志组成了这个单个标志。例如,Wsign-conversion 标志警告可能更改整数值符号的隐式转换。有关这些的更多信息,请访问 GCC 文档。但是,tldr; 只是使用 Wconversion 在编译时捕捉所有这些问题。动态仪表静态分析很可能出现大多数假阳性和一些真正的错误。但是,动态分析,总会发现真正的错误,如果代码路径可以被触发。下面,我们将展示检测选项,用于崩溃/通知整数相关的错误。与讨论 bug 类不同,我们将讨论 GCC 和 Clang 中的一些特定标志。Ftrapv 此选项为加法、减法和乘法操作上的有符号溢出生成陷阱。同样,由于这是动态测试,这将导致程序在发生这种情况时崩溃!这段代码的一个例子可以在下面看到: int main (){ int a = 0x7fff; a = a + 1; printf (“ Value:% d n”,a) ; }当 a = a + 1行执行时,上面的代码将导致整数溢出,因为 c 中整数的最大大小是0x7fff。会发生什么?程序被终止,并且从未到达 print 语句。通过在溢出发生时强制崩溃,我们可以保证检测到 bug,并且更容易找到溢出的原因。这是非常有用的时候,模糊和试图根源崩溃!还应该注意的是,这个标志也检测有符号整数底流。Fsanitize = integer (未初始化的行为消毒器)用来发现整数错误真是太神奇了!这个标志是特定于 Clang 的整数相关的错误。虽然 ftrapv 只捕获有符号整数溢出,但 fsanitize = integer 将在无符号和有符号整数溢出(有符号整数溢出和无符号整数溢出)上崩溃。这意味着所有的整数溢出,无论符号,通过加法,减法或乘法,将在运行时捕获!该死,这是一个很大的进步。除了发现程序中的溢出/下溢之外,我们还可以发现另外两个与此标志相关的 bug 类: trunation (implicit-signed-integer-trunation & implicit-unsigned-integer-trunciation)和 conversion (implicit-integer-sign-change)。与上面的静态方法不同,必须发生错误的数学操作才能触发 UBSAN 来崩溃程序。让我们来看看它的实际应用吧!我们将使用原始的签名问题(图1)。当我们运行这段代码时,带有 LONG _ max (0x7fffffff)值的 LONG LONG 将被切成两半(截断) ,因为要转换为整数。因此,这将是0xffffff 或-1作为一个有符号整数。由于添加了额外的工具,程序在发生截断时崩溃!对我们来说很好,这个插装不需要更改符号; 它检查 long 内部的值是否不适合 int。这次崩溃可以在下面的图5中看到。图5- 截断转换崩溃我们遗漏了什么吗?我们已经提到了整数溢出/底流、整数截断和符号转换问题的动态和静态检查。然而,动态工具还缺少一样东西: 浮点数学。当一个 float 在 c 中溢出时,它会变成 infinity 或 inf。奇怪的是,浮点数从来没有真正包装,因为如何浮点数学处理精度; 它只是去无限!在这里可以看到这种溢出的一个例子。另一个未被发现的错误是浮动截断。例如,从 double 到 float 的转换在运行时不会被捕获。有一个具有误导性的 UBSAN 标志(- fsanitize = float-cast-overflow) ,它只能发现错误的 double/float 到 integer 转换,而不能发现浮点数之间的截断错误。这方面的一个例子可以在这里看到。了解漂浮物成为 inf 和 NaN 可能是有用的,因为一些疯狂的问题,如杰克贝克南传播错误在虚幻游戏引擎确实发生。但是,目前没有检测到在运行时 c 中浮点数的溢出、溢出、截断或 NaN/Inf 使用情况。深入探讨 c 语言中的浮点数学的疯狂之处不在本文讨论范围之内,但它确实很有趣,值得花一个下午的时间学习,因为它很容易出错。结论当试图找到漏洞时,任何来自自动化工具或仪器的帮助都是一个巨大的胜利。除了上面提到的整数漏洞类之外,还有许多其他有趣的标志和工具,例如众所周知的 ASAN (地址清理器) ,用于帮助查找在发现 bug 或者在堆栈内存的范围漏洞之后使用的漏洞。还有许多其他编译标志和检测选项可以帮助查找特定的 bug 类; 本文只关注于查找与整数相关的漏洞类。我希望这些编译警告和动态检测的知识能够帮助您在将来找到许多 bug!如果你对这篇文章有任何问题或评论,请随时联系我(联系方式在页脚)。杜林“麦克斯威尔”欢呼。