Description:
After version 4.0.1 MySQL ignores variable max_user_connections (at least
if per-hour limits are not set), thus any user can take over all MySQL
connections - a serious DoS attack at shared hosting servers!
Looks like MySQL 3.23.x releases don't have this bug. 4.1 and 5.0 might have it.
Tracking cause of the bug a couple of other potentially serious problems were discovered, like uninitialized data, race condition.
How to repeat:
specify max_user_connections=10 variable for mysqld. The value will be correctly reflected by SHOW GLOBAL VARIABLES but mysqld will happily let opening 90+ connections for a single user.
Suggested fix:
This patch fixes all discovered problems. It made against 4.0.13, but looks like the code was not changed since 4.0.2.
--- sql/sql_parse.cc-old 2003-05-14 16:24:34.000000000 -0400
+++ sql/sql_parse.cc 2003-05-23 02:09:30.000000000 -0400
@@ -140,6 +140,7 @@
DBUG_ASSERT(user != 0);
DBUG_ASSERT(host != 0);
+ memset(temp_user, 0, sizeof(temp_user));
user_len=strlen(user);
host_len=strlen(host);
temp_len= (strmov(strmov(temp_user, user)+1, host) - temp_user)+1;
@@ -161,7 +162,13 @@
uc->user_len= user_len;
uc->host=uc->user + uc->user_len + 1;
uc->len = temp_len;
- uc->connections = 1;
+ /* if per-hour limits are set then initial value 1 will */
+ /* prevent object from being removed when client closes all */
+ /* connections ensuring that stats are not lost */
+ /* but if we only track maximum number of simultaneous connections */
+ /* then initial value of 0 will allow reclaiming object memory upon */
+ /* disconnect and thus keeping hashtable much smaller */
+ uc->connections = (mqh->questions || mqh->updates || mqh->connections)?1:0;
uc->questions=uc->updates=uc->conn_per_hour=0;
uc->user_resources=*mqh;
if (max_user_connections && mqh->connections > max_user_connections)
@@ -257,10 +264,10 @@
db ? db : (char*) "");
thd->db_access=0;
/* Don't allow user to connect if he has done too many queries */
- if ((ur.questions || ur.updates || ur.connections) &&
+ if ((ur.questions || ur.updates || ur.connections || max_user_connections) &&
get_or_create_user_conn(thd,user,thd->host_or_ip,&ur))
return -1;
- if (thd->user_connect && thd->user_connect->user_resources.connections &&
+ if (thd->user_connect && ( max_user_connections || thd->user_connect->user_resources.connections ) &&
check_for_max_user_connections(thd->user_connect))
return -1;
if (db && db[0])
@@ -314,7 +321,6 @@
error=1;
goto end;
}
- uc->connections++;
if (uc->user_resources.connections &&
uc->conn_per_hour++ >= uc->user_resources.connections)
{
@@ -324,6 +330,7 @@
error=1;
goto end;
}
+ uc->connections++;
end:
DBUG_RETURN(error);
}
@@ -336,7 +343,9 @@
{
/* Last connection for user; Delete it */
(void) pthread_mutex_lock(&LOCK_user_conn);
- (void) hash_delete(&hash_user_connections,(byte*) uc);
+ if (!uc->connections) { /* avoid race condition by checking again with mutex held */
+ (void) hash_delete(&hash_user_connections,(byte*) uc);
+ }
(void) pthread_mutex_unlock(&LOCK_user_conn);
}
DBUG_VOID_RETURN;