From 796e9f8156010dea2d5202c6cf287f92dfe797fa Mon Sep 17 00:00:00 2001 From: Vladimir Dubrovin <3proxy@3proxy.ru> Date: Wed, 14 Oct 2026 20:15:01 +0300 Subject: [PATCH] Fix memory leaks found under AddressSanitizer - readconfig() kept its line buffer and argument array on every error return, so each refused reload leaked them. - A filter refusing a connection in filter_client (TLS: a failed handshake) was not kept, so its filter_clear never ran and the state it made was lost. makefilters() now clears it, and empties the slot first: a pooled worker's slot may hold the previous connection's. - ssl_filter_close() freed the client CA paths but not the server ones, for every TLS service set up. - Compiled patterns were never freed: an ACL's (pcre: and regex: host names), an httpsrv rule's. copyacl() shared them between the configuration's ACL and each service's copy; each copy now compiles its own, and freeacl() and freehttprules() free them. What LeakSanitizer still reports is OpenSSL's per-thread random generator state of pooled threads, released only by OpenSSL at exit. Co-Authored-By: Claude Opus 5.5 --- src/conf.c | 17 ++++++++++++----- src/httpsrv.c | 4 ++++ src/proxymain.c | 28 +++++++++++++++++++++++++++- src/ssl.c | 3 +++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/conf.c b/src/conf.c index e8565a2..2b2d94d 100644 --- a/src/conf.c +++ b/src/conf.c @@ -2334,6 +2334,7 @@ int readconfig(FILE * fp){ if( !(buf = malloc(bufsize)) || ! (argv = malloc((NPARAMS + 1) * sizeof(unsigned char *))) ) { fprintf(stderr, "No memory for configuration"); + if(buf) free(buf); return(10); } for (linenum = 1; fgets((char *)buf, STRINGBUF, fp); linenum++){ @@ -2343,7 +2344,8 @@ int readconfig(FILE * fp){ argc = parsestr (buf, argv, NPARAMS-1, &buf, &inbuf, &bufsize); if(argc < 1) { fprintf(stderr, "Parse error line %d\n", linenum); - return(11); + res = 11; + goto DONE; } argv[argc] = NULL; if(!strcmp((char *)argv[0], "end") && argc == 1) { @@ -2355,23 +2357,28 @@ int readconfig(FILE * fp){ if(!strcmp((char *)argv[0], (char *)cm->command)){ if(argc < cm->minargs || (cm->maxargs && argc > cm->maxargs)){ fprintf(stderr, "Command: '%s' wrong number of arguments , line %d\n", argv[0], linenum); - return(linenum); + res = linenum; + goto DONE; } res = (*cm->handler)(argc, argv); if(res > 0){ fprintf(stderr, "Command: '%s' failed with code %d, line %d\n", argv[0], res, linenum); - return(linenum); + res = linenum; + goto DONE; } if(!res) break; } } if(res != 1)continue; fprintf(stderr, "Unknown command: '%s' line %d\n", argv[0], linenum); - return(linenum); + res = linenum; + goto DONE; } + res = 0; +DONE: free(buf); free(argv); - return 0; + return res; } diff --git a/src/httpsrv.c b/src/httpsrv.c index 080f33f..36a6b47 100644 --- a/src/httpsrv.c +++ b/src/httpsrv.c @@ -1122,6 +1122,10 @@ void freehttprules(struct httprule *rule) next = rule->next; if(rule->host.name) free(rule->host.name); if(rule->url.name) free(rule->url.name); +#ifdef WITH_PCRE + if(rule->host.re) pcre_pattern_free(rule->host.re); + if(rule->url.re) pcre_pattern_free(rule->url.re); +#endif if(rule->params) free(rule->params); if(rule->ctype) free(rule->ctype); if(rule->hdrs) free(rule->hdrs); diff --git a/src/proxymain.c b/src/proxymain.c index 1db0220..eff6ac7 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -1853,6 +1853,21 @@ struct ace * copyacl (struct ace *ac){ if(hst->name) { hst->name = (unsigned char*)strdup((char *)hst->name); if(!hst->name) { + hst->re = NULL; + hst->next = NULL; + goto ERRORDSTNAMES; + } + } + /* each copy compiles its own pattern, freed with it */ + if(hst->re){ +#ifdef WITH_PCRE + char err[256]; + + hst->re = pcre_pattern_compile(hst->name, err, sizeof(err)); +#else + hst->re = NULL; +#endif + if(!hst->re) { hst->next = NULL; goto ERRORDSTNAMES; } @@ -1970,9 +1985,17 @@ FILTER_ACTION makefilters (struct srvparam *srv, struct clientparam *param){ for(i=0; infilters; i++){ if(!srv->filter[i].filter_client)continue; + /* the slot may hold what the connection before left in it */ + param->filters[param->nfilters].data = NULL; action = (*srv->filter[i].filter_client)(srv->filter[i].data, param, ¶m->filters[param->nfilters].data); if(action == PASS) continue; - if(action > CONTINUE) return action; + if(action > CONTINUE) { + /* a filter refusing the connection is not kept, but it may + have made a state for it */ + if(srv->filter[i].filter_clear && param->filters[param->nfilters].data) + (*srv->filter[i].filter_clear)(param->filters[param->nfilters].data); + return action; + } param->filters[param->nfilters].filter = srv->filter + i; if(srv->filter[i].filter_request)param->reqfilters[param->nreqfilters++] = param->filters + param->nfilters; if(srv->filter[i].filter_connect)param->connectfilters[param->nconnectfilters++] = param->filters + param->nfilters; @@ -2013,6 +2036,9 @@ void freeacl(struct ace *ac){ } for(hst = ac->dstnames; hst; hst = (struct hostname *)itfree(hst, hst->next)){ if(hst->name)free(hst->name); +#ifdef WITH_PCRE + if(hst->re)pcre_pattern_free(hst->re); +#endif } for(ch = ac->chains; ch; ch = (struct chain *) itfree(ch, ch->next)){ if(ch->extuser) free(ch->extuser); diff --git a/src/ssl.c b/src/ssl.c index 0317557..4ff98f6 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -920,6 +920,9 @@ static void ssl_filter_close(void *fo){ free(CONFIG->client_ca_file); free(CONFIG->client_ca_dir); free(CONFIG->client_ca_store); + free(CONFIG->server_ca_file); + free(CONFIG->server_ca_dir); + free(CONFIG->server_ca_store); free(CONFIG->client_sni); if(CONFIG->client_alpn_protos.protos_len){ free(CONFIG->client_alpn_protos.protos);