replace orderedmap for connection headers with linear list

it turned out that a hashmap isn't the right datastructure, as the
special-case header Set-Cookie not only can, but is even heavily
recommended to be used multiple times.

we now use a dumb list as a key-value store for this purpose, but
restrict it to max 256 entries so the linear search can always be
completed in reasonable time in case of an attack.

closes #403
This commit is contained in:
rofl0r 2022-05-02 12:36:04 +00:00
parent 26db3f6cc9
commit 1cd92e5ecb
10 changed files with 165 additions and 186 deletions

View File

@ -50,7 +50,7 @@ tinyproxy_SOURCES = \
base64.c base64.h \
sblist.c sblist.h \
hsearch.c hsearch.h \
orderedmap.c orderedmap.h \
pseudomap.c pseudomap.h \
loop.c loop.h \
mypoll.c mypoll.h \
connect-ports.c connect-ports.h

View File

@ -1,115 +0,0 @@
#ifdef HAVE_CONFIG_H
# include <config.h>
#else
# define _ALL_SOURCE
# define _GNU_SOURCE
#endif
#include <string.h>
#include "sblist.h"
#include "orderedmap.h"
static void orderedmap_destroy_contents(struct orderedmap *o) {
char **p, *q;
size_t i;
htab_value *v;
if(!o) return;
if(o->values) {
while(sblist_getsize(o->values)) {
p = sblist_get(o->values, 0);
if(p) free(*p);
sblist_delete(o->values, 0);
}
sblist_free(o->values);
}
if(o->map) {
i = 0;
while((i = htab_next(o->map, i, &q, &v)))
free(q);
htab_destroy(o->map);
}
}
struct orderedmap *orderedmap_create(size_t nbuckets) {
struct orderedmap o = {0}, *new;
o.values = sblist_new(sizeof(void*), 32);
if(!o.values) goto oom;
o.map = htab_create(nbuckets);
if(!o.map) goto oom;
new = malloc(sizeof o);
if(!new) goto oom;
memcpy(new, &o, sizeof o);
return new;
oom:;
orderedmap_destroy_contents(&o);
return 0;
}
void* orderedmap_destroy(struct orderedmap *o) {
orderedmap_destroy_contents(o);
free(o);
return 0;
}
int orderedmap_append(struct orderedmap *o, const char *key, char *value) {
size_t index;
char *nk, *nv;
nk = nv = 0;
nk = strdup(key);
nv = strdup(value);
if(!nk || !nv) goto oom;
index = sblist_getsize(o->values);
if(!sblist_add(o->values, &nv)) goto oom;
if(!htab_insert(o->map, nk, HTV_N(index))) {
sblist_delete(o->values, index);
goto oom;
}
return 1;
oom:;
free(nk);
free(nv);
return 0;
}
char* orderedmap_find(struct orderedmap *o, const char *key) {
char **p;
htab_value *v = htab_find(o->map, key);
if(!v) return 0;
p = sblist_get(o->values, v->n);
return p?*p:0;
}
int orderedmap_remove(struct orderedmap *o, const char *key) {
size_t i;
char *lk;
char *sk;
char **sv;
htab_value *lv, *v = htab_find2(o->map, key, &sk);
if(!v) return 0;
sv = sblist_get(o->values, v->n);
free(*sv);
sblist_delete(o->values, v->n);
i = 0;
while((i = htab_next(o->map, i, &lk, &lv))) {
if(lv->n > v->n) lv->n--;
}
htab_delete(o->map, key);
free(sk);
return 1;
}
size_t orderedmap_next(struct orderedmap *o, size_t iter, char** key, char** value) {
size_t h_iter;
htab_value* hval;
char **p;
if(iter < sblist_getsize(o->values)) {
h_iter = 0;
while((h_iter = htab_next(o->map, h_iter, key, &hval))) {
if(hval->n == iter) {
p = sblist_get(o->values, iter);
*value = p?*p:0;
return iter+1;
}
}
}
return 0;
}

View File

@ -1,20 +0,0 @@
#ifndef ORDEREDMAP_H
#define ORDEREDMAP_H
#include <stdlib.h>
#include "sblist.h"
#include "hsearch.h"
typedef struct orderedmap {
sblist* values;
struct htab *map;
} *orderedmap;
struct orderedmap *orderedmap_create(size_t nbuckets);
void* orderedmap_destroy(struct orderedmap *o);
int orderedmap_append(struct orderedmap *o, const char *key, char *value );
char* orderedmap_find(struct orderedmap *o, const char *key);
int orderedmap_remove(struct orderedmap *o, const char *key);
size_t orderedmap_next(struct orderedmap *o, size_t iter, char** key, char** value);
#endif

97
src/pseudomap.c Normal file
View File

@ -0,0 +1,97 @@
#include "config.h"
#include "pseudomap.h"
#include <strings.h>
#include <stdlib.h>
#include <string.h>
/* this data structure implements a pseudo hashmap.
tinyproxy originally used a hashmap for keeping the key/value pairs
in HTTP requests; however later it turned out that items need to be
returned in order - so we implemented an "orderedmap".
again, later it turned out that there are are special case headers,
namely Set-Cookie that can happen more than once, so a hashmap isn't the
right structure to hold the key-value pairs in HTTP headers.
it's expected that:
1) the number of headers in a HTTP request we have to process is
not big enough to cause a noticable performance drop when we have
to iterate through our list to find the right header; and
2) use of plain HTTP is getting exceedingly extinct by the day, so
in most usecases CONNECT method is used anyway.
*/
/* restrict the number of headers to 256 to prevent an attacker from
launching a denial of service attack. */
#define MAX_SIZE 256
pseudomap *pseudomap_create(void) {
return sblist_new(sizeof(struct pseudomap_entry), 64);
}
void pseudomap_destroy(pseudomap *o) {
while(sblist_getsize(o)) {
/* retrieve latest element, and "shrink" list in place,
so we don't have to constantly rearrange list items
by using sblist_delete(). */
struct pseudomap_entry *e = sblist_get(o, sblist_getsize(o)-1);
free(e->key);
free(e->value);
--o->count;
}
sblist_free(o);
}
int pseudomap_append(pseudomap *o, const char *key, char *value ) {
struct pseudomap_entry e;
if(sblist_getsize(o) >= MAX_SIZE) return 0;
e.key = strdup(key);
e.value = strdup(value);
if(!e.key || !e.value) goto oom;
if(!sblist_add(o, &e)) goto oom;
return 1;
oom:
free(e.key);
free(e.value);
return 0;
}
static size_t pseudomap_find_index(pseudomap *o, const char *key) {
size_t i;
struct pseudomap_entry *e;
for(i = 0; i < sblist_getsize(o); ++i) {
e = sblist_get(o, i);
if(!strcasecmp(key, e->key)) return i;
}
return (size_t)-1;
}
char* pseudomap_find(pseudomap *o, const char *key) {
struct pseudomap_entry *e;
size_t i = pseudomap_find_index(o, key);
if(i == (size_t)-1) return 0;
e = sblist_get(o, i);
return e->value;
}
/* remove *all* entries that match key, to mimic behaviour of hashmap */
int pseudomap_remove(pseudomap *o, const char *key) {
struct pseudomap_entry *e;
size_t i;
int ret = 0;
while((i = pseudomap_find_index(o, key)) != (size_t)-1) {
e = sblist_get(o, i);
free(e->key);
free(e->value);
ret = 1;
}
return ret;
}
size_t pseudomap_next(pseudomap *o, size_t iter, char** key, char** value) {
struct pseudomap_entry *e;
if(iter >= sblist_getsize(o)) return 0;
e = sblist_get(o, iter);
*key = e->key;
*value = e->value;
return iter + 1;
}

22
src/pseudomap.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef PSEUDOMAP_H
#define PSEUDOMAP_H
#include <stdlib.h>
#include "sblist.h"
struct pseudomap_entry {
char *key;
char *value;
};
typedef sblist pseudomap;
pseudomap *pseudomap_create(void);
void pseudomap_destroy(pseudomap *o);
int pseudomap_append(pseudomap *o, const char *key, char *value );
char* pseudomap_find(pseudomap *o, const char *key);
int pseudomap_remove(pseudomap *o, const char *key);
size_t pseudomap_next(pseudomap *o, size_t iter, char** key, char** value);
#endif

View File

@ -33,7 +33,7 @@
#include "conns.h"
#include "filter.h"
#include "hsearch.h"
#include "orderedmap.h"
#include "pseudomap.h"
#include "heap.h"
#include "html-error.h"
#include "log.h"
@ -323,7 +323,7 @@ static int send_ssl_response (struct conn_s *connptr)
* build a new request line. Finally connect to the remote server.
*/
static struct request_s *process_request (struct conn_s *connptr,
orderedmap hashofheaders)
pseudomap *hashofheaders)
{
char *url;
struct request_s *request;
@ -647,7 +647,7 @@ static int add_xtinyproxy_header (struct conn_s *connptr)
* can be retrieved and manipulated later.
*/
static int
add_header_to_connection (orderedmap hashofheaders, char *header, size_t len)
add_header_to_connection (pseudomap *hashofheaders, char *header, size_t len)
{
char *sep;
@ -665,7 +665,7 @@ add_header_to_connection (orderedmap hashofheaders, char *header, size_t len)
/* Calculate the new length of just the data */
len -= sep - header - 1;
return orderedmap_append (hashofheaders, header, sep);
return pseudomap_append (hashofheaders, header, sep);
}
/*
@ -678,7 +678,7 @@ add_header_to_connection (orderedmap hashofheaders, char *header, size_t len)
/*
* Read all the headers from the stream
*/
static int get_all_headers (int fd, orderedmap hashofheaders)
static int get_all_headers (int fd, pseudomap *hashofheaders)
{
char *line = NULL;
char *header = NULL;
@ -771,7 +771,7 @@ static int get_all_headers (int fd, orderedmap hashofheaders)
* Extract the headers to remove. These headers were listed in the Connection
* and Proxy-Connection headers.
*/
static int remove_connection_headers (orderedmap hashofheaders)
static int remove_connection_headers (pseudomap *hashofheaders)
{
static const char *headers[] = {
"connection",
@ -785,7 +785,7 @@ static int remove_connection_headers (orderedmap hashofheaders)
for (i = 0; i != (sizeof (headers) / sizeof (char *)); ++i) {
/* Look for the connection header. If it's not found, return. */
data = orderedmap_find(hashofheaders, headers[i]);
data = pseudomap_find(hashofheaders, headers[i]);
if (!data)
return 0;
@ -806,7 +806,7 @@ static int remove_connection_headers (orderedmap hashofheaders)
*/
ptr = data;
while (ptr < data + len) {
orderedmap_remove (hashofheaders, ptr);
pseudomap_remove (hashofheaders, ptr);
/* Advance ptr to the next token */
ptr += strlen (ptr) + 1;
@ -815,7 +815,7 @@ static int remove_connection_headers (orderedmap hashofheaders)
}
/* Now remove the connection header it self. */
orderedmap_remove (hashofheaders, headers[i]);
pseudomap_remove (hashofheaders, headers[i]);
}
return 0;
@ -825,12 +825,12 @@ static int remove_connection_headers (orderedmap hashofheaders)
* If there is a Content-Length header, then return the value; otherwise, return
* -1.
*/
static long get_content_length (orderedmap hashofheaders)
static long get_content_length (pseudomap *hashofheaders)
{
char *data;
long content_length = -1;
data = orderedmap_find (hashofheaders, "content-length");
data = pseudomap_find (hashofheaders, "content-length");
if (data)
content_length = atol (data);
@ -838,10 +838,10 @@ static long get_content_length (orderedmap hashofheaders)
return content_length;
}
static int is_chunked_transfer (orderedmap hashofheaders)
static int is_chunked_transfer (pseudomap *hashofheaders)
{
char *data;
data = orderedmap_find (hashofheaders, "transfer-encoding");
data = pseudomap_find (hashofheaders, "transfer-encoding");
return data ? !strcmp (data, "chunked") : 0;
}
@ -853,7 +853,7 @@ static int is_chunked_transfer (orderedmap hashofheaders)
* purposes.
*/
static int
write_via_header (int fd, orderedmap hashofheaders,
write_via_header (int fd, pseudomap *hashofheaders,
unsigned int major, unsigned int minor)
{
char hostname[512];
@ -875,14 +875,14 @@ write_via_header (int fd, orderedmap hashofheaders,
* See if there is a "Via" header. If so, again we need to do a bit
* of processing.
*/
data = orderedmap_find (hashofheaders, "via");
data = pseudomap_find (hashofheaders, "via");
if (data) {
ret = write_message (fd,
"Via: %s, %hu.%hu %s (%s/%s)\r\n",
data, major, minor, hostname, PACKAGE,
VERSION);
orderedmap_remove (hashofheaders, "via");
pseudomap_remove (hashofheaders, "via");
} else {
ret = write_message (fd,
"Via: %hu.%hu %s (%s/%s)\r\n",
@ -893,11 +893,6 @@ done:
return ret;
}
/*
* Number of buckets to use internally in the hashmap.
*/
#define HEADER_BUCKETS 32
/*
* Here we loop through all the headers the client is sending. If we
* are running in anonymous mode, we will _only_ send the headers listed
@ -905,7 +900,7 @@ done:
* - rjkaes
*/
static int
process_client_headers (struct conn_s *connptr, orderedmap hashofheaders)
process_client_headers (struct conn_s *connptr, pseudomap *hashofheaders)
{
static const char *skipheaders[] = {
"host",
@ -953,7 +948,7 @@ process_client_headers (struct conn_s *connptr, orderedmap hashofheaders)
* Delete the headers listed in the skipheaders list
*/
for (i = 0; i != (sizeof (skipheaders) / sizeof (char *)); i++) {
orderedmap_remove (hashofheaders, skipheaders[i]);
pseudomap_remove (hashofheaders, skipheaders[i]);
}
/* Send, or add the Via header */
@ -974,7 +969,7 @@ process_client_headers (struct conn_s *connptr, orderedmap hashofheaders)
* Output all the remaining headers to the remote machine.
*/
iter = 0;
while((iter = orderedmap_next(hashofheaders, iter, &data, &header))) {
while((iter = pseudomap_next(hashofheaders, iter, &data, &header))) {
if (!is_anonymous_enabled (config)
|| anonymous_search (config, data) > 0) {
ret =
@ -1029,7 +1024,7 @@ static int process_server_headers (struct conn_s *connptr)
char *response_line;
orderedmap hashofheaders;
pseudomap *hashofheaders;
size_t iter;
char *data, *header;
ssize_t len;
@ -1059,7 +1054,7 @@ retry:
goto retry;
}
hashofheaders = orderedmap_create (HEADER_BUCKETS);
hashofheaders = pseudomap_create ();
if (!hashofheaders) {
safefree (response_line);
return -1;
@ -1071,7 +1066,7 @@ retry:
if (get_all_headers (connptr->server_fd, hashofheaders) < 0) {
log_message (LOG_WARNING,
"Could not retrieve all the headers from the remote server.");
orderedmap_destroy (hashofheaders);
pseudomap_destroy (hashofheaders);
safefree (response_line);
indicate_http_error (connptr, 503,
@ -1090,7 +1085,7 @@ retry:
* Instead we'll free all the memory and return.
*/
if (connptr->protocol.major < 1) {
orderedmap_destroy (hashofheaders);
pseudomap_destroy (hashofheaders);
safefree (response_line);
return 0;
}
@ -1117,7 +1112,7 @@ retry:
* Delete the headers listed in the skipheaders list
*/
for (i = 0; i != (sizeof (skipheaders) / sizeof (char *)); i++) {
orderedmap_remove (hashofheaders, skipheaders[i]);
pseudomap_remove (hashofheaders, skipheaders[i]);
}
/* Send, or add the Via header */
@ -1139,7 +1134,7 @@ retry:
/* Rewrite the HTTP redirect if needed */
if (config->reversebaseurl &&
(header = orderedmap_find (hashofheaders, "location"))) {
(header = pseudomap_find (hashofheaders, "location"))) {
/* Look for a matching entry in the reversepath list */
while (reverse) {
@ -1164,7 +1159,7 @@ retry:
"Rewriting HTTP redirect: %s -> %s%s%s",
header, config->reversebaseurl,
(reverse->path + 1), (header + len));
orderedmap_remove (hashofheaders, "location");
pseudomap_remove (hashofheaders, "location");
}
}
#endif
@ -1173,14 +1168,14 @@ retry:
* All right, output all the remaining headers to the client.
*/
iter = 0;
while ((iter = orderedmap_next(hashofheaders, iter, &data, &header))) {
while ((iter = pseudomap_next(hashofheaders, iter, &data, &header))) {
ret = write_message (connptr->client_fd,
"%s: %s\r\n", data, header);
if (ret < 0)
goto ERROR_EXIT;
}
orderedmap_destroy (hashofheaders);
pseudomap_destroy (hashofheaders);
/* Write the final blank line to signify the end of the headers */
if (safe_write (connptr->client_fd, "\r\n", 2) < 0)
@ -1189,7 +1184,7 @@ retry:
return 0;
ERROR_EXIT:
orderedmap_destroy (hashofheaders);
pseudomap_destroy (hashofheaders);
return -1;
}
@ -1579,7 +1574,7 @@ void handle_connection (struct conn_s *connptr, union sockaddr_union* addr)
int got_headers = 0, fd = connptr->client_fd;
size_t i;
struct request_s *request = NULL;
orderedmap hashofheaders = NULL;
pseudomap *hashofheaders = NULL;
char sock_ipaddr[IP_LENGTH];
char peer_ipaddr[IP_LENGTH];
@ -1638,7 +1633,7 @@ void handle_connection (struct conn_s *connptr, union sockaddr_union* addr)
/*
* The "hashofheaders" store the client's headers.
*/
hashofheaders = orderedmap_create (HEADER_BUCKETS);
hashofheaders = pseudomap_create ();
if (hashofheaders == NULL) {
update_stats (STAT_BADCONN);
indicate_http_error (connptr, 503, "Internal error",
@ -1667,12 +1662,12 @@ void handle_connection (struct conn_s *connptr, union sockaddr_union* addr)
if (config->basicauth_list != NULL) {
char *authstring;
int failure = 1, stathost_connect = 0;
authstring = orderedmap_find (hashofheaders, "proxy-authorization");
authstring = pseudomap_find (hashofheaders, "proxy-authorization");
if (!authstring && config->stathost) {
authstring = orderedmap_find (hashofheaders, "host");
authstring = pseudomap_find (hashofheaders, "host");
if (authstring && !strncmp(authstring, config->stathost, strlen(config->stathost))) {
authstring = orderedmap_find (hashofheaders, "authorization");
authstring = pseudomap_find (hashofheaders, "authorization");
stathost_connect = 1;
} else authstring = 0;
}
@ -1701,7 +1696,7 @@ e401:
NULL);
HC_FAIL();
}
orderedmap_remove (hashofheaders, "proxy-authorization");
pseudomap_remove (hashofheaders, "proxy-authorization");
}
/*
@ -1712,7 +1707,7 @@ e401:
for (i = 0; i < sblist_getsize (config->add_headers); i++) {
http_header_t *header = sblist_get (config->add_headers, i);
orderedmap_append (hashofheaders, header->name, header->value);
pseudomap_append (hashofheaders, header->name, header->value);
}
request = process_request (connptr, hashofheaders);
@ -1790,7 +1785,7 @@ e401:
done:
free_request_struct (request);
orderedmap_destroy (hashofheaders);
pseudomap_destroy (hashofheaders);
conn_destroy_contents (connptr);
return;
#undef HC_FAIL

View File

@ -127,7 +127,7 @@ void free_reversepath_list (struct reversepath *reverse)
/*
* Rewrite the URL for reverse proxying.
*/
char *reverse_rewrite_url (struct conn_s *connptr, orderedmap hashofheaders,
char *reverse_rewrite_url (struct conn_s *connptr, pseudomap *hashofheaders,
char *url, int *status)
{
char *rewrite_url = NULL;
@ -153,7 +153,7 @@ char *reverse_rewrite_url (struct conn_s *connptr, orderedmap hashofheaders,
sprintf (rewrite_url, "%s%s", reverse->url, url + lrp);
}
} else if (config->reversemagic
&& (cookie = orderedmap_find (hashofheaders,
&& (cookie = pseudomap_find (hashofheaders,
"cookie"))) {
/* No match - try the magical tracking cookie next */

View File

@ -22,7 +22,7 @@
#define TINYPROXY_REVERSE_PROXY_H
#include "conns.h"
#include "orderedmap.h"
#include "pseudomap.h"
struct reversepath {
struct reversepath *next;
@ -38,7 +38,7 @@ extern struct reversepath *reversepath_get (char *url,
struct reversepath *reverse);
void free_reversepath_list (struct reversepath *reverse);
extern char *reverse_rewrite_url (struct conn_s *connptr,
orderedmap hashofheaders, char *url,
pseudomap *hashofheaders, char *url,
int *status);
#endif

View File

@ -53,7 +53,7 @@ static int build_url (char **url, const char *host, int port, const char *path)
}
int
do_transparent_proxy (struct conn_s *connptr, orderedmap hashofheaders,
do_transparent_proxy (struct conn_s *connptr, pseudomap *hashofheaders,
struct request_s *request, struct config_s *conf,
char **url)
{
@ -62,7 +62,7 @@ do_transparent_proxy (struct conn_s *connptr, orderedmap hashofheaders,
size_t ulen = strlen (*url);
size_t i;
data = orderedmap_find (hashofheaders, "host");
data = pseudomap_find (hashofheaders, "host");
if (!data) {
union sockaddr_union dest_addr;
const void *dest_inaddr;

View File

@ -26,11 +26,11 @@
#ifdef TRANSPARENT_PROXY
#include "conns.h"
#include "orderedmap.h"
#include "pseudomap.h"
#include "reqs.h"
extern int do_transparent_proxy (struct conn_s *connptr,
orderedmap hashofheaders,
pseudomap *hashofheaders,
struct request_s *request,
struct config_s *config, char **url);