mirror of
https://github.com/NLnetLabs/unbound.git
synced 2025-12-23 16:20:26 -05:00
- Add stream-wait-size: 4m config option to limit the maximum
memory used by waiting tcp and tls stream replies. This avoids a denial of service where these replies use up all of the memory. git-svn-id: file:///svn/unbound/trunk@5046 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
parent
f5dcd84d27
commit
d81e2c654f
11 changed files with 3471 additions and 3359 deletions
|
|
@ -3,6 +3,9 @@
|
||||||
- Unit test for tcp request reorder and timeouts.
|
- Unit test for tcp request reorder and timeouts.
|
||||||
- Unit tests for ssl out of order processing.
|
- Unit tests for ssl out of order processing.
|
||||||
- Fix that multiple dns fragments can be carried in one TLS frame.
|
- Fix that multiple dns fragments can be carried in one TLS frame.
|
||||||
|
- Add stream-wait-size: 4m config option to limit the maximum
|
||||||
|
memory used by waiting tcp and tls stream replies. This avoids
|
||||||
|
a denial of service where these replies use up all of the memory.
|
||||||
|
|
||||||
17 January 2018: Wouter
|
17 January 2018: Wouter
|
||||||
- For caps-for-id fallback, use the whitelist to avoid timeout
|
- For caps-for-id fallback, use the whitelist to avoid timeout
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@ server:
|
||||||
# Suggested values are 512 to 4096. Default is 4096. 65536 disables it.
|
# Suggested values are 512 to 4096. Default is 4096. 65536 disables it.
|
||||||
# max-udp-size: 4096
|
# max-udp-size: 4096
|
||||||
|
|
||||||
|
# max memory to use for stream(tcp and tls) waiting result buffers.
|
||||||
|
# stream-wait-size: 4m
|
||||||
|
|
||||||
# buffer size for handling DNS data. No messages larger than this
|
# buffer size for handling DNS data. No messages larger than this
|
||||||
# size can be sent or received, by UDP or TCP. In bytes.
|
# size can be sent or received, by UDP or TCP. In bytes.
|
||||||
# msg-buffer-size: 65552
|
# msg-buffer-size: 65552
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
#include "sldns/sbuffer.h"
|
#include "sldns/sbuffer.h"
|
||||||
#include "services/mesh.h"
|
#include "services/mesh.h"
|
||||||
#include "util/fptr_wlist.h"
|
#include "util/fptr_wlist.h"
|
||||||
|
#include "util/locks.h"
|
||||||
|
|
||||||
#ifdef HAVE_NETDB_H
|
#ifdef HAVE_NETDB_H
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
|
|
@ -75,6 +76,13 @@
|
||||||
/** number of simultaneous requests a client can have */
|
/** number of simultaneous requests a client can have */
|
||||||
#define TCP_MAX_REQ_SIMULTANEOUS 32
|
#define TCP_MAX_REQ_SIMULTANEOUS 32
|
||||||
|
|
||||||
|
/** lock on the counter of stream buffer memory */
|
||||||
|
static lock_basic_type stream_wait_count_lock;
|
||||||
|
/** size (in bytes) of stream wait buffers */
|
||||||
|
static size_t stream_wait_count = 0;
|
||||||
|
/** is the lock initialised for stream wait buffers */
|
||||||
|
static int stream_wait_lock_inited = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debug print of the getaddrinfo returned address.
|
* Debug print of the getaddrinfo returned address.
|
||||||
* @param addr: the address returned.
|
* @param addr: the address returned.
|
||||||
|
|
@ -1269,6 +1277,10 @@ listen_create(struct comm_base* base, struct listen_port* ports,
|
||||||
free(front);
|
free(front);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
if(!stream_wait_lock_inited) {
|
||||||
|
lock_basic_init(&stream_wait_count_lock);
|
||||||
|
stream_wait_lock_inited = 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* create comm points as needed */
|
/* create comm points as needed */
|
||||||
while(ports) {
|
while(ports) {
|
||||||
|
|
@ -1358,6 +1370,10 @@ listen_delete(struct listen_dnsport* front)
|
||||||
#endif
|
#endif
|
||||||
sldns_buffer_free(front->udp_buff);
|
sldns_buffer_free(front->udp_buff);
|
||||||
free(front);
|
free(front);
|
||||||
|
if(stream_wait_lock_inited) {
|
||||||
|
stream_wait_lock_inited = 0;
|
||||||
|
lock_basic_destroy(&stream_wait_count_lock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct listen_port*
|
struct listen_port*
|
||||||
|
|
@ -1560,6 +1576,10 @@ void tcp_req_info_clear(struct tcp_req_info* req)
|
||||||
item = req->done_req_list;
|
item = req->done_req_list;
|
||||||
while(item) {
|
while(item) {
|
||||||
nitem = item->next;
|
nitem = item->next;
|
||||||
|
lock_basic_lock(&stream_wait_count_lock);
|
||||||
|
stream_wait_count -= (sizeof(struct tcp_req_done_item)
|
||||||
|
+item->len);
|
||||||
|
lock_basic_unlock(&stream_wait_count_lock);
|
||||||
free(item->buf);
|
free(item->buf);
|
||||||
free(item);
|
free(item);
|
||||||
item = nitem;
|
item = nitem;
|
||||||
|
|
@ -1638,6 +1658,9 @@ tcp_req_info_pop_done(struct tcp_req_info* req)
|
||||||
struct tcp_req_done_item* item;
|
struct tcp_req_done_item* item;
|
||||||
log_assert(req->num_done_req > 0 && req->done_req_list);
|
log_assert(req->num_done_req > 0 && req->done_req_list);
|
||||||
item = req->done_req_list;
|
item = req->done_req_list;
|
||||||
|
lock_basic_lock(&stream_wait_count_lock);
|
||||||
|
stream_wait_count -= (sizeof(struct tcp_req_done_item)+item->len);
|
||||||
|
lock_basic_unlock(&stream_wait_count_lock);
|
||||||
req->done_req_list = req->done_req_list->next;
|
req->done_req_list = req->done_req_list->next;
|
||||||
req->num_done_req --;
|
req->num_done_req --;
|
||||||
return item;
|
return item;
|
||||||
|
|
@ -1788,6 +1811,19 @@ tcp_req_info_add_result(struct tcp_req_info* req, uint8_t* buf, size_t len)
|
||||||
{
|
{
|
||||||
struct tcp_req_done_item* last = NULL;
|
struct tcp_req_done_item* last = NULL;
|
||||||
struct tcp_req_done_item* item;
|
struct tcp_req_done_item* item;
|
||||||
|
size_t space;
|
||||||
|
|
||||||
|
/* see if we have space */
|
||||||
|
space = sizeof(struct tcp_req_done_item) + len;
|
||||||
|
lock_basic_lock(&stream_wait_count_lock);
|
||||||
|
if(stream_wait_count + space > stream_wait_max) {
|
||||||
|
lock_basic_unlock(&stream_wait_count_lock);
|
||||||
|
verbose(VERB_ALGO, "drop stream reply, no space left, in stream-wait-size");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
stream_wait_count += stream_wait_max;
|
||||||
|
lock_basic_unlock(&stream_wait_count_lock);
|
||||||
|
|
||||||
/* find last element */
|
/* find last element */
|
||||||
last = req->done_req_list;
|
last = req->done_req_list;
|
||||||
while(last && last->next)
|
while(last && last->next)
|
||||||
|
|
@ -1796,6 +1832,7 @@ tcp_req_info_add_result(struct tcp_req_info* req, uint8_t* buf, size_t len)
|
||||||
/* create new element */
|
/* create new element */
|
||||||
item = (struct tcp_req_done_item*)malloc(sizeof(*item));
|
item = (struct tcp_req_done_item*)malloc(sizeof(*item));
|
||||||
if(!item) {
|
if(!item) {
|
||||||
|
log_err("malloc failure, for stream result list");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
item->next = NULL;
|
item->next = NULL;
|
||||||
|
|
@ -1803,6 +1840,7 @@ tcp_req_info_add_result(struct tcp_req_info* req, uint8_t* buf, size_t len)
|
||||||
item->buf = memdup(buf, len);
|
item->buf = memdup(buf, len);
|
||||||
if(!item->buf) {
|
if(!item->buf) {
|
||||||
free(item);
|
free(item);
|
||||||
|
log_err("malloc failure, adding reply to stream result list");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1842,6 +1880,18 @@ tcp_req_info_send_reply(struct tcp_req_info* req)
|
||||||
/* queue up the answer behind the others already pending */
|
/* queue up the answer behind the others already pending */
|
||||||
if(!tcp_req_info_add_result(req, sldns_buffer_begin(req->spool_buffer),
|
if(!tcp_req_info_add_result(req, sldns_buffer_begin(req->spool_buffer),
|
||||||
sldns_buffer_limit(req->spool_buffer))) {
|
sldns_buffer_limit(req->spool_buffer))) {
|
||||||
log_err("malloc failure adding reply to stream result list");
|
/* drop the connection, we are out of resources */
|
||||||
|
comm_point_drop_reply(&req->cp->repinfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t tcp_req_info_get_stream_buffer_size(void)
|
||||||
|
{
|
||||||
|
size_t s;
|
||||||
|
if(!stream_wait_lock_inited)
|
||||||
|
return stream_wait_count;
|
||||||
|
lock_basic_lock(&stream_wait_count_lock);
|
||||||
|
s = stream_wait_count;
|
||||||
|
lock_basic_unlock(&stream_wait_count_lock);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -364,4 +364,7 @@ void tcp_req_info_send_reply(struct tcp_req_info* req);
|
||||||
*/
|
*/
|
||||||
int tcp_req_info_handle_read_close(struct tcp_req_info* req);
|
int tcp_req_info_handle_read_close(struct tcp_req_info* req);
|
||||||
|
|
||||||
|
/** get the size of currently used tcp stream wait buffers (in bytes) */
|
||||||
|
size_t tcp_req_info_get_stream_buffer_size(void);
|
||||||
|
|
||||||
#endif /* LISTEN_DNSPORT_H */
|
#endif /* LISTEN_DNSPORT_H */
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,8 @@ uid_t cfg_uid = (uid_t)-1;
|
||||||
gid_t cfg_gid = (gid_t)-1;
|
gid_t cfg_gid = (gid_t)-1;
|
||||||
/** for debug allow small timeout values for fast rollovers */
|
/** for debug allow small timeout values for fast rollovers */
|
||||||
int autr_permit_small_holddown = 0;
|
int autr_permit_small_holddown = 0;
|
||||||
|
/** size (in bytes) of stream wait buffers max */
|
||||||
|
size_t stream_wait_max = 4 * 1024 * 1024;
|
||||||
|
|
||||||
/** global config during parsing */
|
/** global config during parsing */
|
||||||
struct config_parser_state* cfg_parser = 0;
|
struct config_parser_state* cfg_parser = 0;
|
||||||
|
|
@ -140,6 +142,7 @@ config_create(void)
|
||||||
cfg->outgoing_num_tcp = 2; /* leaves 64-52=12 for: 4if,1stop,thread4 */
|
cfg->outgoing_num_tcp = 2; /* leaves 64-52=12 for: 4if,1stop,thread4 */
|
||||||
cfg->incoming_num_tcp = 2;
|
cfg->incoming_num_tcp = 2;
|
||||||
#endif
|
#endif
|
||||||
|
cfg->stream_wait_size = 4 * 1024 * 1024;
|
||||||
cfg->edns_buffer_size = 4096; /* 4k from rfc recommendation */
|
cfg->edns_buffer_size = 4096; /* 4k from rfc recommendation */
|
||||||
cfg->msg_buffer_size = 65552; /* 64 k + a small margin */
|
cfg->msg_buffer_size = 65552; /* 64 k + a small margin */
|
||||||
cfg->msg_cache_size = 4 * 1024 * 1024;
|
cfg->msg_cache_size = 4 * 1024 * 1024;
|
||||||
|
|
@ -491,6 +494,7 @@ int config_set_option(struct config_file* cfg, const char* opt,
|
||||||
else S_NUMBER_NONZERO("outgoing-range:", outgoing_num_ports)
|
else S_NUMBER_NONZERO("outgoing-range:", outgoing_num_ports)
|
||||||
else S_SIZET_OR_ZERO("outgoing-num-tcp:", outgoing_num_tcp)
|
else S_SIZET_OR_ZERO("outgoing-num-tcp:", outgoing_num_tcp)
|
||||||
else S_SIZET_OR_ZERO("incoming-num-tcp:", incoming_num_tcp)
|
else S_SIZET_OR_ZERO("incoming-num-tcp:", incoming_num_tcp)
|
||||||
|
else S_MEMSIZE("stream-wait-size:", stream_wait_size)
|
||||||
else S_SIZET_NONZERO("edns-buffer-size:", edns_buffer_size)
|
else S_SIZET_NONZERO("edns-buffer-size:", edns_buffer_size)
|
||||||
else S_SIZET_NONZERO("msg-buffer-size:", msg_buffer_size)
|
else S_SIZET_NONZERO("msg-buffer-size:", msg_buffer_size)
|
||||||
else S_MEMSIZE("msg-cache-size:", msg_cache_size)
|
else S_MEMSIZE("msg-cache-size:", msg_cache_size)
|
||||||
|
|
@ -877,6 +881,7 @@ config_get_option(struct config_file* cfg, const char* opt,
|
||||||
else O_DEC(opt, "outgoing-range", outgoing_num_ports)
|
else O_DEC(opt, "outgoing-range", outgoing_num_ports)
|
||||||
else O_DEC(opt, "outgoing-num-tcp", outgoing_num_tcp)
|
else O_DEC(opt, "outgoing-num-tcp", outgoing_num_tcp)
|
||||||
else O_DEC(opt, "incoming-num-tcp", incoming_num_tcp)
|
else O_DEC(opt, "incoming-num-tcp", incoming_num_tcp)
|
||||||
|
else O_MEM(opt, "stream-wait-size", stream_wait_size)
|
||||||
else O_DEC(opt, "edns-buffer-size", edns_buffer_size)
|
else O_DEC(opt, "edns-buffer-size", edns_buffer_size)
|
||||||
else O_DEC(opt, "msg-buffer-size", msg_buffer_size)
|
else O_DEC(opt, "msg-buffer-size", msg_buffer_size)
|
||||||
else O_MEM(opt, "msg-cache-size", msg_cache_size)
|
else O_MEM(opt, "msg-cache-size", msg_cache_size)
|
||||||
|
|
@ -1910,6 +1915,7 @@ config_apply(struct config_file* config)
|
||||||
UNKNOWN_SERVER_NICENESS = config->unknown_server_time_limit;
|
UNKNOWN_SERVER_NICENESS = config->unknown_server_time_limit;
|
||||||
log_set_time_asc(config->log_time_ascii);
|
log_set_time_asc(config->log_time_ascii);
|
||||||
autr_permit_small_holddown = config->permit_small_holddown;
|
autr_permit_small_holddown = config->permit_small_holddown;
|
||||||
|
stream_wait_max = config->stream_wait_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void config_lookup_uid(struct config_file* cfg)
|
void config_lookup_uid(struct config_file* cfg)
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,8 @@ struct config_file {
|
||||||
|
|
||||||
/** EDNS buffer size to use */
|
/** EDNS buffer size to use */
|
||||||
size_t edns_buffer_size;
|
size_t edns_buffer_size;
|
||||||
|
/** size of the stream wait buffers, max */
|
||||||
|
size_t stream_wait_size;
|
||||||
/** number of bytes buffer size for DNS messages */
|
/** number of bytes buffer size for DNS messages */
|
||||||
size_t msg_buffer_size;
|
size_t msg_buffer_size;
|
||||||
/** size of the message cache */
|
/** size of the message cache */
|
||||||
|
|
@ -575,6 +577,8 @@ extern uid_t cfg_uid;
|
||||||
extern gid_t cfg_gid;
|
extern gid_t cfg_gid;
|
||||||
/** debug and enable small timeouts */
|
/** debug and enable small timeouts */
|
||||||
extern int autr_permit_small_holddown;
|
extern int autr_permit_small_holddown;
|
||||||
|
/** size (in bytes) of stream wait buffers max */
|
||||||
|
extern size_t stream_wait_max;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stub config options
|
* Stub config options
|
||||||
|
|
|
||||||
4411
util/configlexer.c
4411
util/configlexer.c
File diff suppressed because it is too large
Load diff
|
|
@ -262,6 +262,7 @@ directory{COLON} { YDVAR(1, VAR_DIRECTORY) }
|
||||||
logfile{COLON} { YDVAR(1, VAR_LOGFILE) }
|
logfile{COLON} { YDVAR(1, VAR_LOGFILE) }
|
||||||
pidfile{COLON} { YDVAR(1, VAR_PIDFILE) }
|
pidfile{COLON} { YDVAR(1, VAR_PIDFILE) }
|
||||||
root-hints{COLON} { YDVAR(1, VAR_ROOT_HINTS) }
|
root-hints{COLON} { YDVAR(1, VAR_ROOT_HINTS) }
|
||||||
|
stream-wait-size{COLON} { YDVAR(1, VAR_STREAM_WAIT_SIZE) }
|
||||||
edns-buffer-size{COLON} { YDVAR(1, VAR_EDNS_BUFFER_SIZE) }
|
edns-buffer-size{COLON} { YDVAR(1, VAR_EDNS_BUFFER_SIZE) }
|
||||||
msg-buffer-size{COLON} { YDVAR(1, VAR_MSG_BUFFER_SIZE) }
|
msg-buffer-size{COLON} { YDVAR(1, VAR_MSG_BUFFER_SIZE) }
|
||||||
msg-cache-size{COLON} { YDVAR(1, VAR_MSG_CACHE_SIZE) }
|
msg-cache-size{COLON} { YDVAR(1, VAR_MSG_CACHE_SIZE) }
|
||||||
|
|
|
||||||
2329
util/configparser.c
2329
util/configparser.c
File diff suppressed because it is too large
Load diff
|
|
@ -304,7 +304,8 @@ extern int yydebug;
|
||||||
VAR_LOG_SERVFAIL = 514,
|
VAR_LOG_SERVFAIL = 514,
|
||||||
VAR_DENY_ANY = 515,
|
VAR_DENY_ANY = 515,
|
||||||
VAR_UNKNOWN_SERVER_TIME_LIMIT = 516,
|
VAR_UNKNOWN_SERVER_TIME_LIMIT = 516,
|
||||||
VAR_LOG_TAG_QUERYREPLY = 517
|
VAR_LOG_TAG_QUERYREPLY = 517,
|
||||||
|
VAR_STREAM_WAIT_SIZE = 518
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
/* Tokens. */
|
/* Tokens. */
|
||||||
|
|
@ -568,6 +569,7 @@ extern int yydebug;
|
||||||
#define VAR_DENY_ANY 515
|
#define VAR_DENY_ANY 515
|
||||||
#define VAR_UNKNOWN_SERVER_TIME_LIMIT 516
|
#define VAR_UNKNOWN_SERVER_TIME_LIMIT 516
|
||||||
#define VAR_LOG_TAG_QUERYREPLY 517
|
#define VAR_LOG_TAG_QUERYREPLY 517
|
||||||
|
#define VAR_STREAM_WAIT_SIZE 518
|
||||||
|
|
||||||
/* Value type. */
|
/* Value type. */
|
||||||
#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
|
#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
|
||||||
|
|
@ -578,7 +580,7 @@ union YYSTYPE
|
||||||
|
|
||||||
char* str;
|
char* str;
|
||||||
|
|
||||||
#line 582 "util/configparser.h" /* yacc.c:1909 */
|
#line 584 "util/configparser.h" /* yacc.c:1909 */
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef union YYSTYPE YYSTYPE;
|
typedef union YYSTYPE YYSTYPE;
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,7 @@ extern struct config_parser_state* cfg_parser;
|
||||||
%token VAR_ALLOW_NOTIFY VAR_TLS_WIN_CERT VAR_TCP_CONNECTION_LIMIT
|
%token VAR_ALLOW_NOTIFY VAR_TLS_WIN_CERT VAR_TCP_CONNECTION_LIMIT
|
||||||
%token VAR_FORWARD_NO_CACHE VAR_STUB_NO_CACHE VAR_LOG_SERVFAIL VAR_DENY_ANY
|
%token VAR_FORWARD_NO_CACHE VAR_STUB_NO_CACHE VAR_LOG_SERVFAIL VAR_DENY_ANY
|
||||||
%token VAR_UNKNOWN_SERVER_TIME_LIMIT VAR_LOG_TAG_QUERYREPLY
|
%token VAR_UNKNOWN_SERVER_TIME_LIMIT VAR_LOG_TAG_QUERYREPLY
|
||||||
|
%token VAR_STREAM_WAIT_SIZE
|
||||||
|
|
||||||
%%
|
%%
|
||||||
toplevelvars: /* empty */ | toplevelvars toplevelvar ;
|
toplevelvars: /* empty */ | toplevelvars toplevelvar ;
|
||||||
|
|
@ -263,7 +264,8 @@ content_server: server_num_threads | server_verbosity | server_port |
|
||||||
server_tls_cert_bundle | server_tls_additional_port | server_low_rtt |
|
server_tls_cert_bundle | server_tls_additional_port | server_low_rtt |
|
||||||
server_fast_server_permil | server_fast_server_num | server_tls_win_cert |
|
server_fast_server_permil | server_fast_server_num | server_tls_win_cert |
|
||||||
server_tcp_connection_limit | server_log_servfail | server_deny_any |
|
server_tcp_connection_limit | server_log_servfail | server_deny_any |
|
||||||
server_unknown_server_time_limit | server_log_tag_queryreply
|
server_unknown_server_time_limit | server_log_tag_queryreply |
|
||||||
|
server_stream_wait_size
|
||||||
;
|
;
|
||||||
stubstart: VAR_STUB_ZONE
|
stubstart: VAR_STUB_ZONE
|
||||||
{
|
{
|
||||||
|
|
@ -1127,6 +1129,14 @@ server_ip_freebind: VAR_IP_FREEBIND STRING_ARG
|
||||||
free($2);
|
free($2);
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
server_stream_wait_size: VAR_STREAM_WAIT_SIZE STRING_ARG
|
||||||
|
{
|
||||||
|
OUTYY(("P(server_stream_wait_size:%s)\n", $2));
|
||||||
|
if(!cfg_parse_memsize($2, &cfg_parser->cfg->stream_wait_size))
|
||||||
|
yyerror("memory size expected");
|
||||||
|
free($2);
|
||||||
|
}
|
||||||
|
;
|
||||||
server_edns_buffer_size: VAR_EDNS_BUFFER_SIZE STRING_ARG
|
server_edns_buffer_size: VAR_EDNS_BUFFER_SIZE STRING_ARG
|
||||||
{
|
{
|
||||||
OUTYY(("P(server_edns_buffer_size:%s)\n", $2));
|
OUTYY(("P(server_edns_buffer_size:%s)\n", $2));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue