Package: asterisk / 1:1.6.2.9-2+squeeze12

AST-2011-005 Patch series | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
From: Matthew Nicholson <mnicholson@digium.com>
Date: Tue, 5 Apr 2011 14:13:07 +0000
Subject: Limit the number of unauthenticated manager and their time
Bug: https://issues.asterisk.org/view.php?id=18996
Origin: http://svnview.digium.com/svn/asterisk?view=rev&rev=312764

Keeping many open manager connections at the initial unauthenticated
stage can cause Asterisk to exhaust available CPU and memory resources.

The manager interface is disabled by default in upstream, but enabled
by default (listening on localhost only) in the version in Debian 5.0 (Lenny)
and 6.0 (Squeeze).

There is a followup patch AST-2011-005-p2

CVE: CVE-2011-1507

See also http://downloads.asterisk.org/pub/security/AST-2011-005.html

---
 configs/manager.conf.sample |   11 ++++++
 main/manager.c              |   76 +++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 84 insertions(+), 3 deletions(-)

--- a/configs/manager.conf.sample
+++ b/configs/manager.conf.sample
@@ -25,6 +25,17 @@ enabled = no
 ;webenabled = yes
 port = 5038
 
+; authtimeout specifies the maximum number of seconds a client has to
+; authenticate.  If the client does not authenticate beofre this timeout
+; expires, the client will be disconnected. (default: 30 seconds)
+
+;authtimeout = 30
+
+; authlimit specifies the maximum number of unauthenticated sessions that will
+; be allowed to connect at any given time.
+
+;authlimit = 50
+
 ;httptimeout = 60
 ; a) httptimeout sets the Max-Age of the http cookie
 ; b) httptimeout is the amount of time the webserver waits 
--- a/main/manager.c
+++ b/main/manager.c
@@ -126,6 +126,8 @@ static const int DEFAULT_DISPLAYCONNECTS
 static const int DEFAULT_TIMESTAMPEVENTS	= 0;	/*!< Default setting for timestampevents */	
 static const int DEFAULT_HTTPTIMEOUT 		= 60;	/*!< Default manager http timeout */
 static const int DEFAULT_BROKENEVENTSACTION	= 0;	/*!< Default setting for brokeneventsaction */
+static const int DEFAULT_AUTHTIMEOUT		= 30;	/*!< Default setting for authtimeout */
+static const int DEFAULT_AUTHLIMIT		= 50;	/*!< Default setting for authlimit */
 
 static int displayconnects;
 static int allowmultiplelogin = 1;
@@ -134,9 +136,12 @@ static int httptimeout;
 static int broken_events_action;
 static int manager_enabled = 0;
 static int webmanager_enabled = 0;
+static int authtimeout;
+static int authlimit;
 
 static int block_sockets;
 static int num_sessions;
+static int unauth_sessions = 0;
 
 static int manager_debug;	/*!< enable some debugging code in the manager */
 
@@ -214,6 +219,7 @@ struct mansession_session {
 	int send_events;	/*!<  XXX what ? */
 	struct eventqent *last_ev;	/*!< last event processed. */
 	int writetimeout;	/*!< Timeout for ast_carefulwrite() */
+	time_t authstart;
 	int pending_event;         /*!< Pending events indicator in case when waiting_thread is NULL */
 	AST_LIST_HEAD_NOLOCK(mansession_datastores, ast_datastore) datastores; /*!< Data stores on the session */
 	AST_LIST_ENTRY(mansession_session) list;
@@ -1780,6 +1786,7 @@ static int action_login(struct mansessio
 		return -1;
 	}
 	s->session->authenticated = 1;
+	ast_atomic_fetchadd_int(&unauth_sessions, -1);
 	if (manager_displayconnects(s->session))
 		ast_verb(2, "%sManager '%s' logged on from %s\n", (s->session->managerid ? "HTTP " : ""), s->session->username, ast_inet_ntoa(s->session->sin.sin_addr));
 	ast_log(LOG_EVENT, "%sManager '%s' logged on from %s\n", (s->session->managerid ? "HTTP " : ""), s->session->username, ast_inet_ntoa(s->session->sin.sin_addr));
@@ -3107,6 +3114,8 @@ static int get_input(struct mansession *
 	int res, x;
 	int maxlen = sizeof(s->session->inbuf) - 1;
 	char *src = s->session->inbuf;
+	int timeout = -1;
+	time_t now;
 
 	/*
 	 * Look for \r\n within the buffer. If found, copy to the output
@@ -3134,6 +3143,20 @@ static int get_input(struct mansession *
 	}
 	res = 0;
 	while (res == 0) {
+		/* calculate a timeout if we are not authenticated */
+		if (!s->session->authenticated) {
+			if(time(&now) == -1) {
+				ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno));
+				return -1;
+			}
+
+			timeout = (authtimeout - (now - s->session->authstart)) * 1000;
+			if (timeout < 0) {
+				/* we have timed out */
+				return 0;
+			}
+		}
+
 		/* XXX do we really need this locking ? */
 		ast_mutex_lock(&s->session->__lock);
 		if (s->session->pending_event) {
@@ -3144,7 +3167,7 @@ static int get_input(struct mansession *
 		s->session->waiting_thread = pthread_self();
 		ast_mutex_unlock(&s->session->__lock);
 
-		res = ast_wait_for_input(s->session->fd, -1);	/* return 0 on timeout ? */
+		res = ast_wait_for_input(s->session->fd, timeout);
 
 		ast_mutex_lock(&s->session->__lock);
 		s->session->waiting_thread = AST_PTHREADT_NULL;
@@ -3177,6 +3200,7 @@ static int do_message(struct mansession
 	struct message m = { 0 };
 	char header_buf[sizeof(s->session->inbuf)] = { '\0' };
 	int res;
+	time_t now;
 
 	for (;;) {
 		/* Check if any events are pending and do them if needed */
@@ -3184,6 +3208,17 @@ static int do_message(struct mansession
 			return -1;
 		res = get_input(s, header_buf);
 		if (res == 0) {
+			if (!s->session->authenticated) {
+				if(time(&now) == -1) {
+					ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno));
+					return -1;
+				}
+
+				if (now - s->session->authstart > authtimeout) {
+					ast_log(LOG_EVENT, "Client from %s, failed to authenticate in %d seconds\n", ast_inet_ntoa(s->session->sin.sin_addr), authtimeout);
+					return -1;
+				}
+			}
 			continue;
 		} else if (res > 0) {
 			if (ast_strlen_zero(header_buf))
@@ -3207,13 +3242,22 @@ static int do_message(struct mansession
 static void *session_do(void *data)
 {
 	struct ast_tcptls_session_instance *ser = data;
-	struct mansession_session *session = ast_calloc(1, sizeof(*session));
+	struct mansession_session *session = NULL;
 	struct mansession s = {.session = NULL, };
 	int flags;
 	int res;
 
-	if (session == NULL)
+	if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= authlimit) {
+		fclose(ser->f);
+		ast_atomic_fetchadd_int(&unauth_sessions, -1);
 		goto done;
+	}
+
+	if ((session = ast_calloc(1, sizeof(*session))) == NULL) {
+		fclose(ser->f);
+		ast_atomic_fetchadd_int(&unauth_sessions, -1);
+		goto done;
+	}
 
 	session->writetimeout = 100;
 	session->waiting_thread = AST_PTHREADT_NULL;
@@ -3243,6 +3287,13 @@ static void *session_do(void *data)
 	ast_atomic_fetchadd_int(&num_sessions, 1);
 	AST_LIST_UNLOCK(&sessions);
 
+	if(time(&session->authstart) == -1) {
+		ast_log(LOG_ERROR, "error executing time(): %s; disconnecting client\n", strerror(errno));
+		ast_atomic_fetchadd_int(&unauth_sessions, -1);
+		destroy_session(session);
+		goto done;
+	}
+
 	astman_append(&s, "Asterisk Call Manager/%s\r\n", AMI_VERSION);	/* welcome prompt */
 	for (;;) {
 		if ((res = do_message(&s)) < 0 || s.write_error)
@@ -3254,6 +3305,7 @@ static void *session_do(void *data)
 			ast_verb(2, "Manager '%s' logged off from %s\n", session->username, ast_inet_ntoa(session->sin.sin_addr));
 		ast_log(LOG_EVENT, "Manager '%s' logged off from %s\n", session->username, ast_inet_ntoa(session->sin.sin_addr));
 	} else {
+		ast_atomic_fetchadd_int(&unauth_sessions, -1);
 			if (displayconnects)
 			ast_verb(2, "Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(session->sin.sin_addr));
 		ast_log(LOG_EVENT, "Failed attempt from %s\n", ast_inet_ntoa(session->sin.sin_addr));
@@ -4160,6 +4212,8 @@ static int __init_manager(int reload)
 	block_sockets = DEFAULT_BLOCKSOCKETS;
 	timestampevents = DEFAULT_TIMESTAMPEVENTS;
 	httptimeout = DEFAULT_HTTPTIMEOUT;
+	authtimeout = DEFAULT_AUTHTIMEOUT;
+	authlimit = DEFAULT_AUTHLIMIT;
 
 	if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
 		ast_log(LOG_NOTICE, "Unable to open AMI configuration manager.conf, or configuration is invalid. Asterisk management interface (AMI) disabled.\n");
@@ -4224,6 +4278,22 @@ static int __init_manager(int reload)
 			manager_debug = ast_true(val);
 		} else if (!strcasecmp(var->name, "httptimeout")) {
 			newhttptimeout = atoi(val);
+		} else if (!strcasecmp(var->name, "authtimeout")) {
+			int timeout = atoi(var->value);
+
+			if (timeout < 1) {
+				ast_log(LOG_WARNING, "Invalid authtimeout value '%s', using default value\n", var->value);
+			} else {
+				authtimeout = timeout;
+			}
+		} else if (!strcasecmp(var->name, "authlimit")) {
+			int limit = atoi(var->value);
+
+			if (limit < 1) {
+				ast_log(LOG_WARNING, "Invalid authlimit value '%s', using default value\n", var->value);
+			} else {
+				authlimit = limit;
+			}
 		} else {
 			ast_log(LOG_NOTICE, "Invalid keyword <%s> = <%s> in manager.conf [general]\n",
 				var->name, val);