implement detection and denial of endless connection loops
it is quite easy to bring down a proxy server by forcing it to make connections to one of its own ports, because this will result in an endless loop spawning more and more connections, until all available fds are exhausted. since there's a potentially infinite number of potential DNS/ip addresses resolving to the proxy, it is impossible to detect an endless loop by simply looking at the destination ip address and port. what *is* possible though is to record the ip/port tuples assigned to outgoing connections, and then compare them against new incoming connections. if they match, the sender was the proxy itself and therefore needs to reject that connection. fixes #199.
This commit is contained in:
parent
f6d4da5d81
commit
cd005a94ce
@ -49,6 +49,7 @@ tinyproxy_SOURCES = \
|
|||||||
basicauth.c basicauth.h \
|
basicauth.c basicauth.h \
|
||||||
base64.c base64.h \
|
base64.c base64.h \
|
||||||
sblist.c sblist.h \
|
sblist.c sblist.h \
|
||||||
|
loop.c loop.h \
|
||||||
connect-ports.c connect-ports.h
|
connect-ports.c connect-ports.h
|
||||||
|
|
||||||
EXTRA_tinyproxy_SOURCES = filter.c filter.h \
|
EXTRA_tinyproxy_SOURCES = filter.c filter.h \
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
#include "sblist.h"
|
#include "sblist.h"
|
||||||
|
#include "loop.h"
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
static vector_t listen_fds;
|
static vector_t listen_fds;
|
||||||
@ -87,6 +88,8 @@ void child_main_loop (void)
|
|||||||
|
|
||||||
childs = sblist_new(sizeof (struct child*), config.maxclients);
|
childs = sblist_new(sizeof (struct child*), config.maxclients);
|
||||||
|
|
||||||
|
loop_records_init();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We have to wait for connections on multiple fds,
|
* We have to wait for connections on multiple fds,
|
||||||
* so use select.
|
* so use select.
|
||||||
|
76
src/loop.c
Normal file
76
src/loop.c
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#include <pthread.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "loop.h"
|
||||||
|
#include "conf.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "sblist.h"
|
||||||
|
#include "sock.h"
|
||||||
|
|
||||||
|
struct loop_record {
|
||||||
|
union sockaddr_union addr;
|
||||||
|
time_t tstamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
static sblist *loop_records;
|
||||||
|
static pthread_mutex_t loop_records_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
void loop_records_init(void) {
|
||||||
|
loop_records = sblist_new(sizeof (struct loop_record), 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static void su_to_str(union sockaddr_union *addr, char *buf) {
|
||||||
|
int af = addr->v4.sin_family;
|
||||||
|
unsigned port = ntohs(af == AF_INET ? addr->v4.sin_port : addr->v6.sin6_port);
|
||||||
|
char portb[32];
|
||||||
|
sprintf(portb, ":%u", port);
|
||||||
|
getpeer_information (addr, buf, 256);
|
||||||
|
strcat(buf, portb);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void loop_records_add(union sockaddr_union *addr) {
|
||||||
|
time_t now =time(0);
|
||||||
|
struct loop_record rec;
|
||||||
|
pthread_mutex_lock(&loop_records_lock);
|
||||||
|
rec.tstamp = now;
|
||||||
|
rec.addr = *addr;
|
||||||
|
sblist_add(loop_records, &rec);
|
||||||
|
pthread_mutex_unlock(&loop_records_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TIMEOUT_SECS 15
|
||||||
|
|
||||||
|
int connection_loops (union sockaddr_union *addr) {
|
||||||
|
int ret = 0, af, our_af = addr->v4.sin_family;
|
||||||
|
void *ipdata, *our_ipdata = our_af == AF_INET ? (void*)&addr->v4.sin_addr.s_addr : (void*)&addr->v6.sin6_addr.s6_addr;
|
||||||
|
size_t i, cmp_len = our_af == AF_INET ? sizeof(addr->v4.sin_addr.s_addr) : sizeof(addr->v6.sin6_addr.s6_addr);
|
||||||
|
unsigned port, our_port = ntohs(our_af == AF_INET ? addr->v4.sin_port : addr->v6.sin6_port);
|
||||||
|
time_t now = time(0);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&loop_records_lock);
|
||||||
|
for (i = 0; i < sblist_getsize(loop_records); ) {
|
||||||
|
struct loop_record *rec = sblist_get(loop_records, i);
|
||||||
|
|
||||||
|
if (rec->tstamp + TIMEOUT_SECS < now) {
|
||||||
|
sblist_delete(loop_records, i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
af = rec->addr.v4.sin_family;
|
||||||
|
if (af != our_af) goto next;
|
||||||
|
port = ntohs(af == AF_INET ? rec->addr.v4.sin_port : rec->addr.v6.sin6_port);
|
||||||
|
if (port != our_port) goto next;
|
||||||
|
ipdata = af == AF_INET ? (void*)&rec->addr.v4.sin_addr.s_addr : (void*)&rec->addr.v6.sin6_addr.s6_addr;
|
||||||
|
if (!memcmp(ipdata, our_ipdata, cmp_len)) {
|
||||||
|
ret = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next:
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&loop_records_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
11
src/loop.h
Normal file
11
src/loop.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef LOOP_H
|
||||||
|
#define LOOP_H
|
||||||
|
|
||||||
|
#include "sock.h"
|
||||||
|
|
||||||
|
void loop_records_init(void);
|
||||||
|
void loop_records_add(union sockaddr_union *addr);
|
||||||
|
int connection_loops (union sockaddr_union *addr);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
15
src/reqs.c
15
src/reqs.c
@ -49,6 +49,7 @@
|
|||||||
#include "connect-ports.h"
|
#include "connect-ports.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
#include "basicauth.h"
|
#include "basicauth.h"
|
||||||
|
#include "loop.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Maximum length of a HTTP line
|
* Maximum length of a HTTP line
|
||||||
@ -1560,6 +1561,20 @@ void handle_connection (int fd, union sockaddr_union* addr)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (connection_loops (addr)) {
|
||||||
|
log_message (LOG_CONN,
|
||||||
|
"Prevented endless loop (file descriptor %d): %s",
|
||||||
|
fd, peer_ipaddr);
|
||||||
|
|
||||||
|
indicate_http_error(connptr, 400, "Bad Request",
|
||||||
|
"detail",
|
||||||
|
"You tried to connect to the "
|
||||||
|
"machine the proxy is running on",
|
||||||
|
NULL);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (check_acl (peer_ipaddr, addr, config.access_list) <= 0) {
|
if (check_acl (peer_ipaddr, addr, config.access_list) <= 0) {
|
||||||
update_stats (STAT_DENIED);
|
update_stats (STAT_DENIED);
|
||||||
indicate_http_error (connptr, 403, "Access denied",
|
indicate_http_error (connptr, 403, "Access denied",
|
||||||
|
12
src/sock.c
12
src/sock.c
@ -33,6 +33,7 @@
|
|||||||
#include "sock.h"
|
#include "sock.h"
|
||||||
#include "text.h"
|
#include "text.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
|
#include "loop.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return a human readable error for getaddrinfo() and getnameinfo().
|
* Return a human readable error for getaddrinfo() and getnameinfo().
|
||||||
@ -141,8 +142,17 @@ int opensock (const char *host, int port, const char *bind_to)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0)
|
if (connect (sockfd, res->ai_addr, res->ai_addrlen) == 0) {
|
||||||
|
union sockaddr_union *p = (void*) res->ai_addr, u;
|
||||||
|
int af = res->ai_addr->sa_family;
|
||||||
|
unsigned dport = ntohs(af == AF_INET ? p->v4.sin_port : p->v6.sin6_port);
|
||||||
|
socklen_t slen = sizeof u;
|
||||||
|
if (dport == config.port) {
|
||||||
|
getsockname(sockfd, (void*)&u, &slen);
|
||||||
|
loop_records_add(&u);
|
||||||
|
}
|
||||||
break; /* success */
|
break; /* success */
|
||||||
|
}
|
||||||
|
|
||||||
close (sockfd);
|
close (sockfd);
|
||||||
} while ((res = res->ai_next) != NULL);
|
} while ((res = res->ai_next) != NULL);
|
||||||
|
Loading…
Reference in New Issue
Block a user