hyperscan icon indicating copy to clipboard operation
hyperscan copied to clipboard

Memory leak / invalid pointer when serializing database in Windows built with Cygwin

Open estebanpw opened this issue 2 years ago • 0 comments

Hello,

I believe I have found a bug when running Hyperscan on Windows. On linux I have not been able to reproduce the error. It happens when I try to free a serialized database without a custom memory allocator. When I set a custom memory allocator (which is only a malloc and a free) it is able to free without further problem, however without defining this custom allocator it crashes. By using DrMemory I was able to find the following memory errors:

Error #2: INVALID HEAP ARGUMENT to free 0x0000000002069f30
# 0 replace_free               [D:\a\drmemory\drmemory\common\alloc_replace.c:2710]
# 1 main   
Note: @0:00:07.101 in thread 18456
Note: refers to -1 byte(s) before next malloc
Note: next higher malloc: 0x0000000002069f30-0x000000000206b3e0
Note: refers to -5296 byte(s) beyond last valid byte in prior malloc
Note: prev lower malloc:  0x0000000002069f30-0x000000000206b3e0

Error #3: LEAK 5296 direct bytes 0x0000000002069f30-0x000000000206b3e0 + 0 indirect bytes
# 0 replace_malloc                      [D:\a\drmemory\drmemory\common\alloc_replace.c:2580]
# 1 hs_runtime.dll!hs_serialize_database+0x93     (0x00007ffa94882b44 <hs_runtime.dll+0xc2b44>)
# 2 main   

(Note that Error #1 is unrelated)

It seems I am not able to free the serialized database returned by hs_serialize_database. Nevertheless, if I set the custom allocator to the following one:

void * allocator(size_t bytes)
{
    return (void *) malloc(bytes);
}

void deallocator(void * ptr)
{
    free(ptr);
}

[...]
hs_err = hs_set_misc_allocator(allocator, deallocator);
    if(hs_err != HS_SUCCESS)
        throw std::runtime_error("ERROR: Could not use allocator");
[...]

Then no leak or invalid heap argument is reported and the program finishes correctly.

Version of the build and reproducible example

I have built Hyperscan 5.4.0 in Cygwin64 version 3.3.4(0.341/5/3), Windows 11 Enterprise version 21H2 and GCC version x86_64-w64-mingw32-g++ 11.2.0. An isolated (hopefully reproducible) example is shown below:

#include <iostream>
#include <vector>
#include <hs.h>

void * allocator(size_t bytes)
{
    return (void *) malloc(bytes);
}

void deallocator(void * ptr)
{
    free(ptr);
}

int main(int ac, char ** av)
{
    // Vectors to hold the patterns expressions, flags and IDs
    std::vector<const char *> cstr_patterns;
    std::vector<unsigned> patterns_flags;
    std::vector<unsigned> patterns_ids;

    std::string p1 = "A random regex pattern";
    std::string p2 = "value: [0-9]+";

    cstr_patterns.push_back(p1.c_str());
    cstr_patterns.push_back(p2.c_str());
    patterns_flags.push_back(HS_FLAG_DOTALL | HS_FLAG_MULTILINE | HS_FLAG_SOM_LEFTMOST);
    patterns_flags.push_back(HS_FLAG_DOTALL | HS_FLAG_MULTILINE | HS_FLAG_SOM_LEFTMOST);
    patterns_ids.push_back(0);
    patterns_ids.push_back(1);

    // Database structures
    hs_database_t * db_block = NULL;
    hs_compile_error_t * compile_err = NULL;

    std::cout << "Begin compilation" << std::endl;
    int err = 0;
    
    // Compile
    err = hs_compile_multi(cstr_patterns.data(), patterns_flags.data(),
                    patterns_ids.data(), cstr_patterns.size(), HS_MODE_BLOCK,
                    NULL, &db_block, &compile_err);
    
    // Check if there were any errors
    if (err != HS_SUCCESS)
    {
        std::string error_msg;
        if (compile_err->expression < 0)
            error_msg.append("ERROR: "+std::string(compile_err->message)+"\n"); 
        else
            error_msg.append("ERROR: Pattern [id: "+std::string(std::to_string(compile_err->expression))+"] "+std::string(cstr_patterns[compile_err->expression])
                + " failed with error "+std::string(compile_err->message)+"\n");
        hs_free_compile_error(compile_err);
        throw std::runtime_error(error_msg);
    }
    else
        hs_free_compile_error(compile_err);

    std::cout << "Finished compilation" << std::endl;
    
    // Set custom allocator
    hs_error_t hs_err;
    hs_err = hs_set_misc_allocator(allocator, deallocator);
    if(hs_err != HS_SUCCESS)
        throw std::runtime_error("ERROR: Could not use allocator");
    

    std::cout << "Begin serialization " << std::endl;

    // Serialize DB
    size_t serialized_size;
    char * serialized_db = NULL;

    hs_err = hs_serialize_database(db_block, &serialized_db, &serialized_size);
    if(hs_err != HS_SUCCESS)
        throw std::runtime_error("ERROR: Could not serialize db");

    std::cout << "Finished serialization " << std::endl;
    
    // Free database
    std::cout << "Freeing database " << std::endl;
    hs_err = hs_free_database(db_block);
    if(hs_err != HS_SUCCESS)
        throw std::runtime_error("ERROR: Could not free db");
    
    // Do something .........

    // Free serialized db
    std::cout << "Freeing serialized database" << std::endl;
    free(serialized_db);
    
    std::cout << "Finished" << std::endl;

    return 0;
}

Also the full compilation line (on cygwin):

 /bin/x86_64-w64-mingw32-g++.exe -static -g -fno-inline -fno-omit-frame-pointer -o isolated.exe -I/<path-to-hyperscan>/hyperscan/src/ -I/<path-to-hyperscan>/hyperscan/ isolated.cpp /<path-to-hyperscan>/hyperscan/bin/hs.dll /<path-to-hyperscan>/hyperscan/bin/hs_runtime.dll

In order to reproduce the error, simply comment the lines regarding the setting of the hs_set_misc_allocator. I believe the problem is that the pointer returned by char *out = hs_misc_alloc(length); in database.c is not freeable by the default system free.

Thank you for your support, Esteban

estebanpw avatar Apr 20 '22 09:04 estebanpw