Compare commits

...

6 Commits

Author SHA1 Message Date
Vladimir Dubrovin
57b94b209b tlspr: a ServerHello too short for the extensions length refused (#1286)
Some checks failed
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Has been cancelled
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Has been cancelled
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Has been cancelled
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Has been cancelled
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Has been cancelled
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Has been cancelled
2026-11-10 15:20:13 +03:00
Vladimir Dubrovin
3040de2b55 httpsrv: a limit exceeded answered with 503, not ignored
Authorization reports 10 when a limit of the service was exceeded,
which every other child answers with 503; the http server took it as
success and answered the request anyway. It now says 503 for itself,
after the body of the request is drained as any answer here does.

(cherry picked from commit 5a2e9d27c3)
2026-11-08 14:54:57 +03:00
Vladimir Dubrovin
c687380776 httpsrv: a dot-dot segment of a path refused, with either separator
A path ending in a backslashed dot-dot, or carrying one in a form the
checks did not name, was taken as it stood. Any dot-dot segment is now
refused wherever it is and whichever separator carries it, and a path
ending in a bare dot of either kind.

(cherry picked from commit c0eb7a65c6)
2026-11-08 14:54:57 +03:00
Vladimir Dubrovin
a00c0823f5 pcre: a rule with no ACL a match data of its own, not itself
A rule whose ACL was missing was handed to the filters as the
connection's data, which they read as a match data block and freed:
a pointer into the configuration, dereferenced as another type and
given back to malloc. A rule without an ACL now skips the check and
goes on with a block of its own, and nothing a filter is given is
ever the rule itself.

(cherry picked from commit a7e5a8b48b)
2026-11-08 14:54:57 +03:00
Vladimir Dubrovin
01600e46d6 pcre: a match data of the connection's own, not of the rule's
Every connection matching a pcre rule matched on the one match data
block of the rule, which pcre2_match() writes into: two connections
matching at the same time wrote the same block, a race that corrupts
memory and brings the process down after a while under load. A
connection that passes the rule's ACL now makes a match data block of
its own when it starts and frees it when it ends; the match context is
kept, as it is only read while matching.
2026-11-02 12:26:55 +03:00
Vladimir Dubrovin
0cf5dfc6a3 Windows: the loop's socket results in the type the socket calls return
ssize_t does not exist under MSVC, and the socket function table
already spells its send and recv results int there and ssize_t
elsewhere. That type is now named SOCKRET, beside SASIZETYPE, and the
event loop's copies of it use the name, so the MSVC build compiles
again.
2026-11-02 12:11:09 +03:00
6 changed files with 65 additions and 20 deletions

View File

@ -429,11 +429,12 @@ static int targetunsafe(const char *path)
#endif
}
if(!strncmp(path, "./", 2) || !strncmp(path, "../", 3)) return 1;
if(strstr(path, "/./") || strstr(path, "/../")) return 1;
if(strstr(path, "\\.\\") || strstr(path, "\\..\\")) return 1;
/* a dot-dot segment, with either separator, wherever it is in the
path: a walk out of the tree, or a file named to look like one */
if(strstr(path, "/..") || strstr(path, "\\..")) return 1;
if(strstr(path, "/./") || strstr(path, "\\.\\")) return 1;
p = path + strlen(path);
if(p - path >= 2 && !strcmp(p - 2, "/.")) return 1;
if(p - path >= 3 && !strcmp(p - 3, "/..")) return 1;
if(p - path >= 2 && (!strcmp(p - 2, "/.") || !strcmp(p - 2, "\\."))) return 1;
return 0;
}
@ -1462,16 +1463,27 @@ static int httpsrv_request(struct clientparam *param, struct httpreq *r)
return 1;
}
}
if(i && i != 10){
if(i){
/* The body goes before the answer, as for any answer this server
gives: left in the stream it would be read as the next request. */
if(!r->drained){
if(r->contentlen) httpsrv_drain(param, r->contentlen);
r->drained = 1;
}
/* 10: a limit of the service was exceeded, which the other
services answer with 503. */
if(i == 10){
static const char body[] = "exceeded your limits\n";
r->hdrs = NULL;
r->maxage = -1;
r->keepalive = 0;
if(httpsrv_head(r, 503, "text/plain", (int64_t)sizeof(body) - 1, NULL)) return 1;
httpsrv_send(r, body, (int)sizeof(body) - 1);
}
/* 4 no credentials, 5 unknown user, 6 wrong password: all of them
should let the client offer credentials again. */
if(i >= 4 && i <= 6) op_authrequired(r);
else if(i >= 4 && i <= 6) op_authrequired(r);
else op_forbidden(r);
RETURN(i);
}

View File

@ -555,7 +555,7 @@ static void cbfail(struct cbmap *m, int side, int writing){
/* Statistics of what went through a way, as sockmap() counts them, and
the bandwidth limits' pause: the client's data once written to the
server, the server's once read. A read counts for the grace. */
static void cbcount(struct cbmap *m, struct cbway *w, int written, ssize_t n){
static void cbcount(struct cbmap *m, struct cbway *w, int written, SOCKRET n){
struct clientparam *param = m->param;
int sl = 0;
@ -603,7 +603,7 @@ static int cbway(struct cbmap *m, struct cbway *w){
unsigned char *buf;
unsigned *inbuf, *offset, size;
uint64_t inb, room;
ssize_t n;
SOCKRET n;
if(!w->left) return 0;
cbbuf(param, w->src, &buf, &inbuf, &offset, &size);

View File

@ -220,6 +220,14 @@ struct pcre_filter_data {
struct ace *acl;
};
/* What a connection matches with: pcre2_match() writes into its match
data, so a thread matching on the rule's own one would race every
other thread doing the same. */
struct pcre_client_data {
struct pcre_filter_data *pcrefd;
pcre2_match_data * match_data;
};
static void pcre_data_free(struct pcre_filter_data *pcrefd){
_3proxy_mutex_lock(&pcre_mutex);
pcrefd->users--;
@ -253,16 +261,31 @@ static void* pcre_filter_open(void * idata, struct srvparam * param){
static FILTER_ACTION pcre_filter_client(void *fo, struct clientparam * param, void** fc){
int res;
struct ace tmpace;
struct pcre_filter_data *rule = (struct pcre_filter_data *)fo;
struct pcre_client_data *pcd;
*fc = fo;
*fc = NULL;
if(!fo) return PASS;
#define pcrefd ((struct pcre_filter_data *)fo)
if(!pcrefd->acl) return CONTINUE;
memset (&tmpace, 0, sizeof(struct ace));
tmpace.src = pcrefd->acl->src;
res = pl->ACLMatches(&tmpace, param);
#undef pcrefd
return (res)? CONTINUE:PASS;
if(rule->acl){
memset (&tmpace, 0, sizeof(struct ace));
tmpace.src = rule->acl->src;
res = pl->ACLMatches(&tmpace, param);
if(!res) return PASS;
}
/* the connection goes on with a match data of its own */
if(!(pcd = pl->mallocfunc(sizeof(struct pcre_client_data)))){
*fc = NULL;
return rule->action;
}
pcd->pcrefd = rule;
pcd->match_data = NULL;
if(rule->re && !(pcd->match_data = pcre2_match_data_create_from_pattern(rule->re, NULL))){
pl->freefunc(pcd);
*fc = NULL;
return rule->action;
}
*fc = pcd;
return CONTINUE;
}
/* What a rewritten buffer keeps free for its caller to append to. */
@ -278,7 +301,8 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns
char * tmpbuf, *target, *newbuf;
unsigned char *given = *buf_p;
int nreplaces=0;
#define pcrefd ((struct pcre_filter_data *)fc)
struct pcre_client_data *pcd = (struct pcre_client_data *)fc;
#define pcrefd (pcd->pcrefd)
for(acl = pcrefd->acl; acl; acl=acl->next){
if(pl->ACLMatches(acl, param)){
@ -291,9 +315,9 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns
for(; offset < *length_p; nreplaces++){
int repsz;
count = pcre2_match(pcrefd->re, (PCRE2_SPTR)*buf_p, *length_p, offset, 0, pcrefd->match_data, pcrefd->match_context);
count = pcre2_match(pcrefd->re, (PCRE2_SPTR)*buf_p, *length_p, offset, 0, pcd->match_data, pcrefd->match_context);
if(count <= 0) break;
ovector = pcre2_get_ovector_pointer(pcrefd->match_data);
ovector = pcre2_get_ovector_pointer(pcd->match_data);
if(ovector[0] > (PCRE2_SIZE)*length_p || ovector[1] > (PCRE2_SIZE)*length_p || ovector[1] < ovector[0]) break;
if(!(replace = pcrefd->replace) || param->nooverwritefilter) return pcrefd->action;
@ -379,7 +403,13 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns
#undef pcrefd
}
static void pcre_filter_clear(void *fo){
static void pcre_filter_clear(void *fc){
struct pcre_client_data *pcd = (struct pcre_client_data *)fc;
if(pcd){
if(pcd->match_data) pcre2_match_data_free(pcd->match_data);
pl->freefunc(pcd);
}
}
static void pcre_filter_close(void *fo){

View File

@ -62,6 +62,7 @@
#include <io.h>
#include <process.h>
#define SASIZETYPE int
#define SOCKRET int /* what a socket call returns: moved bytes or -1 */
#define SHUT_RDWR SD_BOTH
#define SHUT_WR SD_SEND
#define SHUT_RD SD_RECEIVE

View File

@ -35,6 +35,7 @@ extern "C" {
#include <net/if.h>
#endif
#define SASIZETYPE socklen_t
#define SOCKRET ssize_t /* what a socket call returns: moved bytes or -1 */
#define SOCKET int
#define INVALID_SOCKET (-1)
#define _3proxy_mutex_t pthread_mutex_t

View File

@ -224,6 +224,7 @@ int parsehello(int type, unsigned char *hello, unsigned len, char *sni, int * sn
else if(type == 2){
offset += 3;
}
if((offset + 2) > len) return -6;
elen = size16(hello+offset);
offset += 2;
if(elen+offset != len) return -9;