C addrinfo结构损坏。但是堆仍然有效

时间:2018-11-14 12:48:07

标签: c winsock memory-corruption

我尝试创建一个套接字并连接到远程主机。我使用GetAddrInfo从域中解析远程主机。哪个工作正常。通话后,我得到一个具有正确值的工作addrinfo结构。但是在某些情况下,该结构在调用connect()之前已损坏。

struct addrinfoW sa = { 0 };
ZeroMemory(&sa, sizeof(sa));
lookup_host(host, &sa);

int sock = 0;
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == -1) {
    return -1;
}
HeapValidate(GetProcessHeap(), HEAP_NO_SERIALIZE, NULL);
if(connect(sock, sa->ai_addr, sa->ai_addrlen) < 0) {
    HeapValidate(GetProcessHeap(), HEAP_NO_SERIALIZE, NULL);
#ifdef _DEBUG
    printf("Error: %d\n", GetLastError());
#endif // _DEBUG

    return -2;
}

其中lookup_host定义为:

struct addrinfoW hints = { 0 };
struct addrinfoW *res;
int errcode;
ZeroMemory(&hints, sizeof(struct addrinfoW));
//ZeroMemory(res, sizeof(struct addrinfo));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags |= AI_CANONNAME;
errcode = GetAddrInfo(host, L"80", &hints, &res);//GetAddrInfoExW(L"google.de", L"80", NS_ALL, NULL, &hints, &res, NULL, NULL, NULL, NULL);  //GetAddrInfoEX(L"google.de", L"80", &hints, &res);
win_free(mbHost);
if (errcode != 0)
{
    //perror("getaddrinfo");
    return -1;
}
void *ptr = 0;
while (res)
{
    switch (res->ai_family)
    {
    case AF_INET:
        ptr = &((struct sockaddr_in *) res->ai_addr)->sin_addr;
        break;
    case AF_INET6:
        ptr = &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
        break;
    }
    CopyMemory(out, res, sizeof(struct addrinfoW));
    break;
    res = res->ai_next;
}
FreeAddrInfo(res);

因此,这在我的四核Win10笔记本电脑上运行良好。但是,如果我添加例如

MessageBoxA(NULL, "After sock", "HTTP", MB_ICONWARNING|MB_CANCELTRYCONTINUE | MB_DEFBUTTON2);
在connect()调用之前

调用,并在调试器中检查addrinfo结构是否损坏。例如,有时我可以看到应为“ google.com”的ai_canonname被“ After Sock”覆盖。但是此后堆仍然有效。 所以我不知道从哪里开始调试。可能是某些其他缓冲区或结构在某处溢出吗?

3 个答案:

答案 0 :(得分:0)

您(res)每次执行行res时都将指针res = res->ai_next;移至原始节点以外的其他节点,但是随后您在修改后的指针结尾调用FreeAddrInfo(res); ,而不是通过GetAddrInfo分配的那个。

此外,尽管您没有提供lookup_host函数的开始,但是假设inout是ptr,您可以将其从分配的emory中设置为sin_addr的地址,然后在函数末尾释放内存,因此可能会被其他东西使用。

答案 1 :(得分:0)

这是因为您在使用结果之前先释放了与结果相关的所有内存。例如,ai_canonname是指向从堆分配的字符串的指针。在退出lookup_host之前,包含其字节的内存被标记为可重用。您的CopyMemory将复制指针,但不复制其指向的字节。

注意,您应该发布整个lookup_host,包括函数定义。

您需要找到一种避免调用FreeAddrInfo的方法,直到完成操作为止。要么做一个更深层次的struct副本,其中包括您将很快发现的所有指向的东西,就会变成一个兔子洞。

我要做的是提供在lookup_host

内部调用的回调函数

答案 2 :(得分:0)

您的lookup_host()没有正确地将地址数据返回给呼叫者,甚至没有关闭。 addrinfo包含指向addrinfo之外的数据的指针。但是,您只是将addrinfo本身复制到了调用方,但没有指向它的任何数据。

请尝试以下类似操作:

int lookup_host(const wchar_t *host, int family, void *sa)
{
    struct addrinfoW hints;
    struct addrinfoW *res, *addr;
    int errcode, addrlen, returnval;
    void *ptr;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = family;
    hints.ai_socktype = SOCK_STREAM;

    errcode = GetAddrInfoW(host, L"80", &hints, &res);
    //GetAddrInfoExW(host, L"80", NS_ALL, NULL, &hints, &res, NULL, NULL, NULL, NULL);
    if (errcode != 0)
    {
        //perror("getaddrinfo");
        return -1;
    }

    returnval = 0;

    for(addr = res; addr != NULL; addr = addr->ai_next)
    {
        switch (addr->ai_family)
        {
            case AF_INET:
                ptr = &((struct sockaddr_in *) addr->ai_addr)->sin_addr;
                addrlen = sizeof(struct sockaddr_in);
                break;

            case AF_INET6:
                ptr = &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
                addrlen = sizeof(struct sockaddr_in6);
                break;

            default:
                continue;
        }

        CopyMemory(sa, ptr, addrlen);
        returnval = 1;
        break;
    }

    FreeAddrInfoW(res);

    return returnval;
}

...

struct sockaddr_in sa;

if (lookup_host(host, AF_INET, &sa) != 1) {
    return -1;
}

SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
    return -1;
}

if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
    #ifdef _DEBUG
    printf("Error: %d\n", GetLastError());
    #endif // _DEBUG

    closesocket(sock);
    return -2;
}

...

但是,请注意,以上仅返回找到的第一个地址。主机名可以解析为多个IP,并且您的PC可能没有通往所有IP的路由。因此,您应该尝试连接到报告的每个地址,直到成功为止,例如:

int lookup_host(const wchar_t *host, int family, struct addrinfoW **addrs)
{
    struct addrinfoW hints;
    int errcode;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = family;
    hints.ai_socktype = SOCK_STREAM;

    errcode = GetAddrInfoW(host, L"80", &hints, addrs);
    //GetAddrInfoExW(host, L"80", NS_ALL, NULL, &hints, addrs, NULL, NULL, NULL, NULL);
    if (errcode != 0)
    {
        //perror("getaddrinfo");
        return -1;
    }

    return 0;
}

...

struct addrInfoW *res, *addr;

if (lookup_host(host, AF_INET, &res) < 0) { // or AF_INET6, or AF_UNSPEC
    return -1;
}

sock = INVALID_SOCKET;

for(addr = res; addr != NULL; addr = addr->ai_next)
{
    sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
    if (sock == INVALID_SOCKET) {
        break;
    }

    if (connect(sock, addr->ai_addr, addr->ai_addrlen) == 0) {
        break;
    }

    #ifdef _DEBUG
    printf("Error: %d\n", GetLastError());
    #endif // _DEBUG

    closesocket(sock);
    sock = INVALID_SOCKET;
}

FreeAddrInfoW(res);

if (sock == INVALID_SOCKET) {
    return -2;
}

...