• Win32 debug heap assertion after reading a cached filter filter while

    From Rob Swindell@VERT to GitLab note in main/sbbs on Mon Mar 16 02:16:44 2026
    https://gitlab.synchro.net/main/sbbs/-/issues/1099#note_8600

    Here's another clue:

    without changing `filterfile.cpp` (so its `reset()` method did *not* call `strListFree()`), I was able to trigger the assertion (still, only on MSVC builds for Win32-debug) by adding the following lines right after `ip_can.init(&scfg, "ip");` in `mail_server()` - before *any* child threads are spawned!
    ```
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ```
    It would crash in one of the calls to `ip_can.listed()`, when it calls `strListFree()`.

    Another thing I noticed that I think could be relevant is at the time of the crash there was another mailsrvr.dll thread (!) running:
    ```
    ntdll.dll!_NtWaitForAlertByThreadId@8() Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!@RtlpWaitOnAddressWithTimeout@20() Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!_RtlpWaitOnCriticalSection@8() Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!_RtlpEnterCriticalSectionContended@8() Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!_RtlEnterCriticalSection@4() Unknown Non-user code. Symbols loaded without source information.
    mailsrvr.dll!__acrt_lock(__acrt_lock_id _Lock) Line 55 C++ Symbols loaded.
    mailsrvr.dll!heap_alloc_dbg_internal(const unsigned int size, const int block_use, const char * const file_name, const int line_number) Line 309 C++ Symbols loaded.
    mailsrvr.dll!heap_alloc_dbg(const unsigned int size, const int block_use, const char * const file_name, const int line_number) Line 450 C++ Symbols loaded.
    mailsrvr.dll!_calloc_dbg(unsigned int count, unsigned int element_size, int block_use, const char * file_name, int line_number) Line 518 C++ Symbols loaded.
    mailsrvr.dll!__vcrt_getptd_noexit() Line 128 C++ Non-user code. Symbols loaded.
    mailsrvr.dll!__vcrt_thread_attach() Line 155 C++ Non-user code. Symbols loaded.
    mailsrvr.dll!__scrt_dllmain_crt_thread_attach() Line 436 C++ Non-user code. Symbols loaded.
    mailsrvr.dll!dllmain_crt_dispatch(HINSTANCE__ * const instance, const unsigned long reason, void * const reserved) Line 221 C++ Non-user code. Symbols loaded.
    mailsrvr.dll!dllmain_dispatch(HINSTANCE__ * const instance, const unsigned long reason, void * const reserved) Line 276 C++ Non-user code. Symbols loaded.
    mailsrvr.dll!_DllMainCRTStartup(HINSTANCE__ * const instance, const unsigned long reason, void * const reserved) Line 334 C++ Non-user code. Symbols loaded.
    ntdll.dll!_LdrxCallInitRoutine@16() Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!_LdrpCallInitRoutineInternal@16() Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!_LdrpCallInitRoutine@16() Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!_LdrpInitializeThread@4() Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!__LdrpInitialize@8() Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!_LdrpInitializeInternal@8() Unknown Non-user code. Symbols loaded without source information.
    ntdll.dll!LdrInitializeThunk() Unknown Non-user code. Symbols loaded without source information.
    ```
    Apparently trying to allocate 40 bytes for what reason I do not know. but this is consistent. Crashes with either sbbs.exe or sbbsctrl.exe, so we know this is not about Borland C++ being involved (its not used when building/running sbbs.exe) and mixed runtime libraries/heaps.

    It would more reliably crash here (usually in the second call to `listed()`) if the mail server was the only server set to run automatically. This also reduced the total number of threads/noise and allowed me to notice the "extra" mail server thread running in (apparently) `calloc()`) at the time of the crash.

    Microsoft's AppVerifier doesn't catch anything (heap page or otherwise) before the assertion.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell@VERT to GitLab note in main/sbbs on Mon Mar 16 04:47:53 2026
    https://gitlab.synchro.net/main/sbbs/-/issues/1099#note_8601

    More experimentation, I just placed this code at the beginning of `mail_server()` (though this could be any of the servers) and it reliably reproduces the crash. Playing with the `SLEEP()` duration changes to the crash point:
    ```
    startup = (mail_startup_t*)arg;

    SLEEP(200);
    scfg.cache_filter_files = 1;
    SAFECOPY(scfg.ctrl_dir, startup->ctrl_dir);
    SAFECOPY(scfg.text_dir, "../text/");
    ip_can.init(&scfg, "ip");
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ip_can.listed("");
    ip_can.reset();
    ```

    It crashes in one of the `.listed()` calls (in `strListFree()->free`), just like always.

    So it doesn't appear to be caused by any code in the servers.

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell@VERT to GitLab note in main/sbbs on Mon Mar 16 16:46:37 2026
    https://gitlab.synchro.net/main/sbbs/-/issues/1099#note_8602

    Directly calling `mail_server()` from `sbbscon.c` `main()` (not using `_beginthread()`), the exception still happens. So it doesn't have to be a thread.

    Replacing the use of `ip_can` with direct calls to `strListReadFile()` and `strListFree()` does *not* reproduce the issue:
    ```
    str_list_t list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    list = strListReadFile(fp, NULL, 1000);
    strListFree(&list);
    ```

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net