用AddressSanitizer查找bug:来自开源项目的模式

地址消毒剂(ASan)是 在Visual Studio 2019版本16.9中正式发布 . 我们最近使用此功能 在MSVC编译器中查找并修复一个bug . 为了进一步验证ASan实现的有用性,我们还将其用于一系列广泛使用的开放源代码项目,在这些项目中发现了Boost、azureiotcsdk和OpenSSL中的bug。在本文中,我们通过描述我们发现的bug的类型以及它们在这些项目中的表现来展示我们的发现。我们提供了到GitHub提交的链接,这些bug是在那里修复的,这样您就可以很有帮助地了解所涉及的代码更改。如果你不熟悉ASan是什么以及如何使用它,你可能想看看 地址消毒剂文档 在深入研究本文之前。

null

Boost和渴望迭代器

急切迭代器 指向容器边界外的元素,然后取消引用的元素。以下代码示例显示了此错误内存访问模式的示例:

template <typename Iter>
int ComputeSum(Iter b, Iter e)
{
    int sum = 0;

    for (; b <= e; ++b) {
        // ERROR: will dereference the 'end' iterator
        // due to the use of the '<=' operator above.
        sum += *b;
    }

    return sum;
}

有时,急切的迭代器可能会错误地出现在更复杂的循环中,例如 do_length Boost的UTF-8转换方面实现的函数,如下所示:

int utf8_codecvt_facet::do_length(
    std::mbstate_t &,
    const char * from,
    const char * from_end, 
    std::size_t max_limit
) const
#if BOOST_WORKAROUND(__IBMCPP__, BOOST_TESTED_AT(600))
        throw()
#endif
{ 
    int last_octet_count=0;
    std::size_t char_count = 0;
    const char* from_next = from;

    while (from_next+last_octet_count <= from_end && char_count <= max_limit) {
        from_next += last_octet_count;
        last_octet_count = (get_octet_count(*from_next));
        ++char_count;
    }
    return static_cast<int>(from_next-from);
}

这里,less或equal运算符用于正确设置 from_next from_end 当后者指向UTF-8字符边界时。但是,这也会导致终止迭代器被取消引用的错误。使用ASan生成此代码并在Visual Studio中进行调试将导致ASan在预期位置中断:

Screenshot of a debugging session in Visual Studio showing an AddressSanitizer global buffer overflow error in the 'do_length' function at line 'last_octet_count = (get_octet_count(*from_next));'

我们让推进小组知道了这个问题,他们很快就解决了 提交了对GitHub的修复 .

azureiotcsdk:一个数组及其长度常量

当一个常量用于跟踪数组的长度但长度不正确时,数组与其长度常量之间就会发生不一致。当在内存复制操作中使用长度常量时,这可能会导致内存访问错误。下面的简单示例说明了问题:

#include <cstring>

unsigned char GLOBAL_BUFFER[] = { 1,2,3,4,5 };
constexpr size_t BUF_SIZE = 6;

void CopyGlobalBuffer(unsigned char* dst)
{
    // ERROR: AddressSanitizer: global-buffer-overflow
    std::memcpy(dst, GLOBAL_BUFFER, BUF_SIZE);
}

我们在Azure IoT C SDK中发现了此错误的一个实例,其中字符串的长度常量与实际长度不匹配:

static const unsigned char* TWIN_REPORTED_PROPERTIES = 
    (const unsigned char*)
    "{ "reportedStateProperty0": "reportedStateProperty0", "
    ""reportedStateProperty1": "reportedStateProperty1" }";

static int TWIN_REPORTED_PROPERTIES_LENGTH = 117;

价值 TWIN_REPORTED_PROPERTIES_LENGTH 常量是117,而 TWIN_REPORTED_PROPERTIES 字符串为107,在使用复制字符串时导致全局缓冲区溢出 memcpy . 使用ASan构建此代码并使用visualstudio进行调试在调用 memcpy ,在一个名为 CONSTBUFFER_Create_Internal :

Screenshot of a debugging session in Visual Studio showing an AddressSanitizer error in the 'CONSTBUFFER_Create_Internal' function on line '(void)memcpy(temp, source, size);'

这并没有立即告诉我们错误的来源,但由于Visual Studio中的ASan集成,可以使用“调用堆栈”窗口遍历堆栈并找到传递错误大小值的函数:

Screenshot of the Call Stack window from a debugging session in Visual Studio. The call stack contains the following functions: CONSTBUFFER_Create_Internal, real_CONSTBUFFER_Create, send_one_report_patch, twin_msgr_do_work_started_with_EXPIRED_in_progress_patches_success, RunTests, and main.

这个案子的罪魁祸首是 send_one_report_patch 函数,已通过 TWIN_REPORTED_PROPERTIES TWIN_REPORTED_PROPERTIES_LENGTH 一个间接调用 CONSTBUFFER_Create_Internal :

static void send_one_report_patch(TWIN_MESSENGER_HANDLE handle, time_t current_time)
{
    const unsigned char* buffer = (unsigned char*)TWIN_REPORTED_PROPERTIES;
    size_t size = TWIN_REPORTED_PROPERTIES_LENGTH;
    CONSTBUFFER_HANDLE report = real_CONSTBUFFER_Create(buffer, size);

    umock_c_reset_all_calls();
    set_twin_messenger_report_state_async_expected_calls(report, current_time);
    (void)twin_messenger_report_state_async(handle, report, 
        TEST_on_report_state_complete_callback, NULL);

    real_CONSTBUFFER_DecRef(report);
}

我们通过使用 sizeof 运算符将长度常量设置为始终反映字符串实际大小的值。你可以找到我们的 GitHub上的bug修复提交 .

OpenSSL和Shapeshifting类型

当类型的大小因预处理器定义的不同而变化时,就产生了变形类型。如果假定该类型具有特定的大小,则可能会发生内存访问错误。下面是一个简单的例子:

#include <cstdint>
#include <cstring>
#include <array>

#ifdef BIGGER_INT
typedef int64_t MyInt;
#else
typedef int32_t MyInt;
#endif

MyInt GLOBAL_BUFFER[] = { 1,2,3,4,5 };

void SizeTypeExample()
{
    int localBuffer[std::size(GLOBAL_BUFFER)];

    // ERROR: AddressSanitizer: stack-buffer-overflow
    std::memcpy(localBuffer, GLOBAL_BUFFER, sizeof(GLOBAL_BUFFER));
}

如果 BIGGER_INT 定义了 memcpy 操作可能会触发堆栈缓冲区溢出,因为 localBuffer 变量假设 MyInt 大小与 int . 在 test_param_time_t OpenSSL测试:

static int test_param_time_t(int n)
{
    time_t in, out;
    unsigned char buf[MAX_LEN], cmp[sizeof(size_t)];
    const size_t len = raw_values[n].len >= sizeof(size_t)
                       ? sizeof(time_t) : raw_values[n].len;
    OSSL_PARAM param = OSSL_PARAM_time_t("a", NULL);

    memset(buf, 0, sizeof(buf));
    le_copy(buf, raw_values[n].value, sizeof(in));
    memcpy(&in, buf, sizeof(in));
    param.data = &out;
    if (!TEST_true(OSSL_PARAM_set_time_t(&param, in)))
        return 0;
    le_copy(cmp, &out, sizeof(out));
    if (!TEST_mem_eq(cmp, len, raw_values[n].value, len))
        return 0;
    in = 0;
    if (!TEST_true(OSSL_PARAM_get_time_t(&param, &in)))
        return 0;
    le_copy(cmp, &in, sizeof(in));
    if (!TEST_mem_eq(cmp, sizeof(in), raw_values[n].value, sizeof(in)))
        return 0;
    param.data = &out;
    return test_param_type_extra(&param, raw_values[n].value, sizeof(size_t));
}

在这里, size_t 假定与 time_t ,但根据编译的体系结构,情况并非总是如此。复制时 out cmp 使用 le_copy 函数,则复制操作的大小为 sizeof(time_t) 但是 cmp 缓冲区已用大小初始化 size_t . 在使用ASan构建OpenSSL测试并使用visualstudio进行调试时,调试器在内部出现ASan错误 le_copy :

Screenshot of a debugging session in Visual Studio, showing an AddressSanitizer error in the 'le_copy' function on line 'memcpy(out, in, len)'.

再次感谢VS中的ASan集成,我们能够使用调用堆栈窗口找到bug的实际来源:调用堆栈 test_param_time_t 功能:

Screenshot of the Call Stack window from a Visual Studio debugging session. The call stack contains the following functions: le_copy, test_param_time_t, run_tests, and main.

我们让OpenSSL团队知道了这个错误,并进行了修复 在GitHub上提交 .

今天试试消毒剂!

在本文中,我们分享了如何使用AddressSanitizer在各种开源项目中发现bug。我们希望这将激励您在自己的代码库中尝试这个特性。您是否在项目中发现了急切的迭代器、变形类型或数组/长度常量不一致?让我们知道在下面的评论,在Twitter上 (@VisualC) ),或通过电子邮件 visualcpp@microsoft.com .

本文包含以下来源的代码段:

utf8u编码vtu facet.ipp 文件, Boost C++库 ,版权所有(c)2001 Ronald Garcia和Andrew Lumsdaine,根据 Boost软件许可证1.0版 .

Azure IoT C SDK和库 ,版权所有(c)Microsoft Corporation,根据 麻省理工许可证 .

Azure C共享实用程序 ,版权所有(c)Microsoft Corporation,根据 麻省理工许可证 .

参数api测试.c 文件, OpenSSL ,版权所有2019-2021 The OpenSSL Project Authors,版权所有(c)2019 Oracle和/或其附属公司,根据 Apache许可证2.0 .

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享