summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorMagne Mahre <magne.mahre@oracle.com>2011-10-07 14:08:31 +0200
committerMagne Mahre <magne.mahre@oracle.com>2011-10-07 14:08:31 +0200
commite02c3d7fb7e59bcede73392ab0da5a5228859e0f (patch)
tree6b78785fe9d18efb7a0fff1422ca1168662e0834 /sql
parent41dc3049281c1718e49d80eaf0cf068def9519b5 (diff)
downloadmariadb-git-e02c3d7fb7e59bcede73392ab0da5a5228859e0f.tar.gz
BUG#12589870 CRASHES WITH MULTIQUERY PACKET + USE<DB> + QUERY CACHE
A buffer large enough to hold the query _plus_ some additional data is allocated before parsing is started. The additional data is used by the query cache, and consists of the name of the current database and a set of flags. When a packet containing multiple SQL statements is sent to the server and one of the statements changes the current database (a "USE <db>" statement), and the name of the new current database is longer than of the previous, there is not enough space in the buffer for the new name, and we write out over the buffer boundary. The fix adds an extra field to store the number of bytes allocated to the database name in the buffer. If the current database name changes, and the new name is longer than the previous one, we refuse to cache the query.
Diffstat (limited to 'sql')
-rw-r--r--sql/sp_head.cc12
-rw-r--r--sql/sql_cache.cc33
-rw-r--r--sql/sql_parse.cc21
3 files changed, 58 insertions, 8 deletions
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index 95a7eda0540..70ffb0a36ab 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -1029,12 +1029,22 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str)
/*
Allocate additional space at the end of the new query string for the
query_cache_send_result_to_client function.
+
+ The query buffer layout is:
+ buffer :==
+ <statement> The input statement(s)
+ '\0' Terminating null char
+ <length> Length of following current database name (size_t)
+ <db_name> Name of current database
+ <flags> Flags struct
*/
- buf_len= qbuf.length() + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE + 1;
+ buf_len= qbuf.length() + 1 + sizeof(size_t) + thd->db_length +
+ QUERY_CACHE_FLAGS_SIZE + 1;
if ((pbuf= (char *) alloc_root(thd->mem_root, buf_len)))
{
memcpy(pbuf, qbuf.ptr(), qbuf.length());
pbuf[qbuf.length()]= 0;
+ *(size_t *)(pbuf+qbuf.length()+1)= thd->db_length;
}
else
DBUG_RETURN(TRUE);
diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc
index b791428eef0..4800fdedbe5 100644
--- a/sql/sql_cache.cc
+++ b/sql/sql_cache.cc
@@ -1241,8 +1241,8 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
/* Key is query + database + flag */
if (thd->db_length)
{
- memcpy(thd->query() + thd->query_length() + 1, thd->db,
- thd->db_length);
+ memcpy(thd->query() + thd->query_length() + 1 + sizeof(size_t),
+ thd->db, thd->db_length);
DBUG_PRINT("qcache", ("database: %s length: %u",
thd->db, (unsigned) thd->db_length));
}
@@ -1251,7 +1251,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d",
DBUG_PRINT("qcache", ("No active database"));
}
tot_length= thd->query_length() + thd->db_length + 1 +
- QUERY_CACHE_FLAGS_SIZE;
+ sizeof(size_t) + QUERY_CACHE_FLAGS_SIZE;
/*
We should only copy structure (don't use it location directly)
because of alignment issue
@@ -1462,7 +1462,28 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length)
goto err;
}
}
+ {
+ /*
+ We have allocated buffer space (in alloc_query) to hold the
+ SQL statement(s) + the current database name + a flags struct.
+ If the database name has changed during execution, which might
+ happen if there are multiple statements, we need to make
+ sure the new current database has a name with the same length
+ as the previous one.
+ */
+ size_t *db_len= (size_t *) (sql + query_length + 1);
+ if (thd->db_length != *db_len)
+ {
+ /*
+ We should probably reallocate the buffer in this case,
+ but for now we just leave it uncached
+ */
+ DBUG_PRINT("qcache",
+ ("Current database has changed since start of query"));
+ goto err;
+ }
+ }
/*
Try to obtain an exclusive lock on the query cache. If the cache is
disabled or if a full cache flush is in progress, the attempt to
@@ -1484,10 +1505,12 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length)
Query_cache_block *query_block;
- tot_length= query_length + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE;
+ tot_length= query_length + 1 + sizeof(size_t) +
+ thd->db_length + QUERY_CACHE_FLAGS_SIZE;
+
if (thd->db_length)
{
- memcpy(sql+query_length+1, thd->db, thd->db_length);
+ memcpy(sql + query_length + 1 + sizeof(size_t), thd->db, thd->db_length);
DBUG_PRINT("qcache", ("database: '%s' length: %u",
thd->db, (unsigned)thd->db_length));
}
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index b5864fef19d..55509a31f77 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -1912,13 +1912,30 @@ bool alloc_query(THD *thd, const char *packet, uint packet_length)
pos--;
packet_length--;
}
- /* We must allocate some extra memory for query cache */
+ /* We must allocate some extra memory for query cache
+
+ The query buffer layout is:
+ buffer :==
+ <statement> The input statement(s)
+ '\0' Terminating null char (1 byte)
+ <length> Length of following current database name (size_t)
+ <db_name> Name of current database
+ <flags> Flags struct
+ */
if (! (query= (char*) thd->memdup_w_gap(packet,
packet_length,
- 1 + thd->db_length +
+ 1 + sizeof(size_t) + thd->db_length +
QUERY_CACHE_FLAGS_SIZE)))
return TRUE;
query[packet_length]= '\0';
+ /*
+ Space to hold the name of the current database is allocated. We
+ also store this length, in case current database is changed during
+ execution. We might need to reallocate the 'query' buffer
+ */
+ size_t *len = (size_t *) (query + packet_length + 1);
+ *len= thd->db_length;
+
thd->set_query(query, packet_length);
/* Reclaim some memory */