Skip to content

Commit 281a760

Browse files
committed
Remove DLL resolve tracking (Windows Defender false positive)
The DLL resolve feature (f92fd66) contained literal strings GetProcAddress, dlsym, LoadLibrary that triggered Windows Defender Wacatac.B!ml heuristic detection on the v0.5.4 binary. These are the exact API names malware uses for dynamic API resolution. Removed the feature entirely — it served a niche use case (C/C++ projects with dynamic DLL loading) and the false positive blocks all Windows users from using the tool. Fixes #89.
1 parent 63e6141 commit 281a760

3 files changed

Lines changed: 6 additions & 233 deletions

File tree

src/pipeline/pass_calls.c

Lines changed: 3 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ int cbm_pipeline_pass_calls(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *file
294294

295295
/* Additional pattern-based edge passes run after normal call resolution */
296296
cbm_pipeline_pass_fastapi_depends(ctx, files, file_count);
297-
cbm_pipeline_pass_dll_resolve(ctx, files, file_count);
298297

299298
return 0;
300299
}
@@ -439,164 +438,6 @@ void cbm_pipeline_pass_fastapi_depends(cbm_pipeline_ctx_t *ctx, const cbm_file_i
439438
}
440439
}
441440

442-
/* ── DLL resolve tracking ────────────────────────────────────────── */
443-
/* Scans C/C++ function source for dynamic DLL resolution patterns
444-
* (GetProcAddress, dlsym, Resolve) and creates CALLS edges to synthetic
445-
* stub nodes, enabling call graph tracking across DLL boundaries. */
446-
447-
// NOLINTNEXTLINE(misc-include-cleaner) — cbm_file_info_t provided by standard header
448-
void cbm_pipeline_pass_dll_resolve(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
449-
int file_count) {
450-
cbm_regex_t getproc_re;
451-
cbm_regex_t dlsym_re;
452-
cbm_regex_t resolve_re;
453-
454-
if (cbm_regcomp(&getproc_re,
455-
"GetProcAddress[AW]?\\([^,]+,[[:space:]]*\"([A-Za-z_][A-Za-z0-9_]*)\"",
456-
CBM_REG_EXTENDED) != 0) {
457-
return;
458-
}
459-
if (cbm_regcomp(&dlsym_re, "dlsym\\([^,]+,[[:space:]]*\"([A-Za-z_][A-Za-z0-9_]*)\"",
460-
CBM_REG_EXTENDED) != 0) {
461-
cbm_regfree(&getproc_re);
462-
return;
463-
}
464-
if (cbm_regcomp(&resolve_re, "[.>]Resolve\\([[:space:]]*\"([A-Za-z_][A-Za-z0-9_]*)\"",
465-
CBM_REG_EXTENDED) != 0) {
466-
cbm_regfree(&getproc_re);
467-
cbm_regfree(&dlsym_re);
468-
return;
469-
}
470-
471-
cbm_regex_t *patterns[] = {&getproc_re, &dlsym_re, &resolve_re};
472-
473-
int edge_count = 0;
474-
for (int i = 0; i < file_count; i++) {
475-
if (files[i].language != CBM_LANG_C && files[i].language != CBM_LANG_CPP) {
476-
continue;
477-
}
478-
if (cbm_pipeline_check_cancel(ctx)) {
479-
break;
480-
}
481-
482-
CBMFileResult *result = ctx->result_cache ? ctx->result_cache[i] : NULL;
483-
if (!result) {
484-
continue;
485-
}
486-
487-
/* Early bail: check if any call targets a DLL resolution function */
488-
bool has_dll_call = false;
489-
for (int c = 0; c < result->calls.count; c++) {
490-
const char *name = result->calls.items[c].callee_name;
491-
if (!name) {
492-
continue;
493-
}
494-
if (strcmp(name, "GetProcAddress") == 0 || strcmp(name, "GetProcAddressA") == 0 ||
495-
strcmp(name, "GetProcAddressW") == 0 || strcmp(name, "dlsym") == 0 ||
496-
strstr(name, "Resolve") != NULL) {
497-
has_dll_call = true;
498-
break;
499-
}
500-
}
501-
if (!has_dll_call) {
502-
continue;
503-
}
504-
505-
int source_len = 0;
506-
char *source = read_file(files[i].path, &source_len);
507-
if (!source) {
508-
continue;
509-
}
510-
511-
char *module_qn = cbm_pipeline_fqn_module(ctx->project_name, files[i].rel_path);
512-
513-
for (int d = 0; d < result->defs.count; d++) {
514-
CBMDefinition *def = &result->defs.items[d];
515-
if (!def->qualified_name || def->start_line == 0 || def->end_line == 0) {
516-
continue;
517-
}
518-
if (strcmp(def->label, "Function") != 0 && strcmp(def->label, "Method") != 0) {
519-
continue;
520-
}
521-
522-
/* Extract function body from source */
523-
const char *p = source;
524-
int line = 1;
525-
while (*p && line < def->start_line) {
526-
if (*p == '\n') {
527-
line++;
528-
}
529-
p++;
530-
}
531-
const char *body_start = p;
532-
while (*p && line < def->end_line) {
533-
if (*p == '\n') {
534-
line++;
535-
}
536-
p++;
537-
}
538-
size_t body_len = (size_t)(p - body_start);
539-
char *body = malloc(body_len + 1);
540-
if (!body) {
541-
continue;
542-
}
543-
memcpy(body, body_start, body_len);
544-
body[body_len] = '\0';
545-
546-
/* Match each DLL resolution pattern */
547-
for (int pi = 0; pi < 3; pi++) {
548-
cbm_regmatch_t match[2];
549-
const char *scan = body;
550-
while (cbm_regexec(patterns[pi], scan, 2, match, 0) == 0) {
551-
int fn_len = match[1].rm_eo - match[1].rm_so;
552-
char func_name[256];
553-
if (fn_len >= (int)sizeof(func_name)) {
554-
fn_len = (int)sizeof(func_name) - 1;
555-
}
556-
memcpy(func_name, scan + match[1].rm_so, (size_t)fn_len);
557-
func_name[fn_len] = '\0';
558-
559-
/* Create edge to synthetic DLL stub node */
560-
char target_qn[512];
561-
snprintf(target_qn, sizeof(target_qn), "%s.dll.external.%s", module_qn,
562-
func_name);
563-
564-
const cbm_gbuf_node_t *src_node =
565-
cbm_gbuf_find_by_qn(ctx->gbuf, def->qualified_name);
566-
if (src_node) {
567-
/* Create stub node if it doesn't exist */
568-
if (!cbm_gbuf_find_by_qn(ctx->gbuf, target_qn)) {
569-
char stub_props[256];
570-
snprintf(stub_props, sizeof(stub_props),
571-
"{\"stub\":true,\"source\":\"dll_resolve\","
572-
"\"dll_function\":\"%s\"}",
573-
func_name);
574-
cbm_gbuf_upsert_node(ctx->gbuf, "Function", func_name, target_qn,
575-
files[i].rel_path, (int)def->start_line,
576-
(int)def->start_line, stub_props);
577-
}
578-
const cbm_gbuf_node_t *tgt_node = cbm_gbuf_find_by_qn(ctx->gbuf, target_qn);
579-
if (tgt_node) {
580-
cbm_gbuf_insert_edge(ctx->gbuf, src_node->id, tgt_node->id, "CALLS",
581-
"{\"confidence\":0.85,\"strategy\":\"dll_resolve\""
582-
"}");
583-
edge_count++;
584-
}
585-
}
586-
scan += match[0].rm_eo;
587-
}
588-
}
589-
free(body);
590-
}
591-
592-
free(module_qn);
593-
free(source);
594-
}
595-
596-
cbm_regfree(&getproc_re);
597-
cbm_regfree(&dlsym_re);
598-
cbm_regfree(&resolve_re);
599-
if (edge_count > 0) {
600-
cbm_log_info("pass.dll_resolve", "edges", itoa_log(edge_count));
601-
}
602-
}
441+
/* DLL resolve tracking removed — the string literals (GetProcAddress, dlsym,
442+
* LoadLibrary) triggered Windows Defender Wacatac.B!ml false positive.
443+
* See: https://github.com/DeusData/codebase-memory-mcp/issues/89 */

src/pipeline/pipeline_internal.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,6 @@ int cbm_pipeline_pass_calls(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *file
358358
/* Sub-passes called from pass_calls: pattern-based edge extraction */
359359
void cbm_pipeline_pass_fastapi_depends(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
360360
int file_count);
361-
void cbm_pipeline_pass_dll_resolve(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
362-
int file_count);
363361

364362
int cbm_pipeline_pass_usages(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files, int file_count);
365363

tests/test_pipeline.c

Lines changed: 3 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -4795,73 +4795,8 @@ TEST(pipeline_fastapi_depends_edges) {
47954795
PASS();
47964796
}
47974797

4798-
/* ═══════════════════════════════════════════════════════════════════
4799-
* DLL resolve edge tracking (PR #66, fix #29)
4800-
* ═══════════════════════════════════════════════════════════════════ */
4801-
4802-
TEST(pipeline_dll_resolve_edges) {
4803-
/* GetProcAddress(handle, "Func") should produce a CALLS edge to a
4804-
* synthetic DLL stub node. */
4805-
const char *files[] = {"loader.c"};
4806-
const char *contents[] = {
4807-
"#include <windows.h>\n\n"
4808-
"void load_plugin(HMODULE hDll) {\n"
4809-
" void *fn = GetProcAddress(hDll, \"PluginInit\");\n"
4810-
" if (fn) ((void(*)())fn)();\n"
4811-
"}\n"};
4812-
if (setup_lang_repo(files, contents, 1) != 0) {
4813-
SKIP("tmpdir");
4814-
}
4815-
char db[512];
4816-
snprintf(db, sizeof(db), "%s/test.db", g_lang_tmpdir);
4817-
cbm_pipeline_t *p = cbm_pipeline_new(g_lang_tmpdir, db, CBM_MODE_FULL);
4818-
ASSERT_NOT_NULL(p);
4819-
ASSERT_EQ(cbm_pipeline_run(p), 0);
4820-
4821-
cbm_store_t *s = cbm_store_open_path(db);
4822-
ASSERT_NOT_NULL(s);
4823-
const char *proj = cbm_pipeline_project_name(p);
4824-
4825-
/* Check for dll_resolve CALLS edge */
4826-
cbm_edge_t *edges = NULL;
4827-
int edge_count = 0;
4828-
cbm_store_find_edges_by_type(s, proj, "CALLS", &edges, &edge_count);
4829-
4830-
bool found_dll_edge = false;
4831-
for (int i = 0; i < edge_count; i++) {
4832-
if (edges[i].properties_json && strstr(edges[i].properties_json, "dll_resolve")) {
4833-
found_dll_edge = true;
4834-
break;
4835-
}
4836-
}
4837-
if (edges) {
4838-
cbm_store_free_edges(edges, edge_count);
4839-
}
4840-
ASSERT_TRUE(found_dll_edge);
4841-
4842-
/* Check for stub node with dll_function property */
4843-
cbm_node_t *nodes = NULL;
4844-
int node_count = 0;
4845-
cbm_store_find_nodes_by_label(s, proj, "Function", &nodes, &node_count);
4846-
4847-
bool found_stub = false;
4848-
for (int i = 0; i < node_count; i++) {
4849-
if (nodes[i].properties_json && strstr(nodes[i].properties_json, "PluginInit") &&
4850-
strstr(nodes[i].properties_json, "dll_resolve")) {
4851-
found_stub = true;
4852-
break;
4853-
}
4854-
}
4855-
if (nodes) {
4856-
cbm_store_free_nodes(nodes, node_count);
4857-
}
4858-
ASSERT_TRUE(found_stub);
4859-
4860-
cbm_store_close(s);
4861-
cbm_pipeline_free(p);
4862-
teardown_lang_repo();
4863-
PASS();
4864-
}
4798+
/* DLL resolve test removed — feature removed due to Windows Defender
4799+
* false positive (Wacatac.B!ml). See issue #89. */
48654800

48664801
/* ═══════════════════════════════════════════════════════════════════
48674802
* Incremental reindex
@@ -5188,9 +5123,8 @@ SUITE(pipeline) {
51885123
RUN_TEST(githistory_coupling_skips_large_commits);
51895124
RUN_TEST(githistory_coupling_limits_output);
51905125
/* Incremental reindex */
5191-
/* FastAPI Depends + DLL resolve edge tracking (PR #66 port) */
5126+
/* FastAPI Depends edge tracking (PR #66 port) */
51925127
RUN_TEST(pipeline_fastapi_depends_edges);
5193-
RUN_TEST(pipeline_dll_resolve_edges);
51945128
/* Incremental */
51955129
RUN_TEST(incremental_full_then_noop);
51965130
RUN_TEST(incremental_detects_changed_file);

0 commit comments

Comments
 (0)