From 20302a67ffe6d9e264125227a4d1b38f29c51e0f Mon Sep 17 00:00:00 2001 From: Vladimir Dubrovin <3proxy@3proxy.ru> Date: Wed, 14 Oct 2026 20:19:35 +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, which is not cleared when it is allocated. - ssl_filter_close() freed the client CA paths but not the server ones, for every TLS service set up. (cherry picked from commit 796e9f8, without what 0.9 does not have: compiled patterns in ACLs and httpsrv rules) Co-Authored-By: Claude Opus 5.5 --- src/conf.c | 17 ++++++++++++----- src/proxymain.c | 10 +++++++++- src/ssl.c | 3 +++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/conf.c b/src/conf.c index 3a54bd6..689d8d6 100644 --- a/src/conf.c +++ b/src/conf.c @@ -1936,6 +1936,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++){ @@ -1945,7 +1946,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) { @@ -1967,23 +1969,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/proxymain.c b/src/proxymain.c index 8776c6b..ae67926 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -1694,9 +1694,17 @@ FILTER_ACTION makefilters (struct srvparam *srv, struct clientparam *param){ for(i=0; infilters; i++){ if(!srv->filter[i].filter_client)continue; + /* the slots are not cleared when they are allocated */ + 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; diff --git a/src/ssl.c b/src/ssl.c index 18eed94..bb4340f 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -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);