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 <noreply@anthropic.com>
(cherry picked from commit 796e9f8156)
This commit is contained in:
Vladimir Dubrovin 2026-10-14 20:15:01 +03:00
parent 9651848bf1
commit 292a855f34
4 changed files with 46 additions and 6 deletions

View File

@ -2294,6 +2294,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++){
@ -2303,7 +2304,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) {
@ -2315,23 +2317,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;
}

View File

@ -1124,6 +1124,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);

View File

@ -1745,6 +1745,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;
}
@ -1868,9 +1883,17 @@ FILTER_ACTION makefilters (struct srvparam *srv, struct clientparam *param){
for(i=0; i<srv->nfilters; 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, &param->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;
@ -1911,6 +1934,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);

View File

@ -867,6 +867,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);