Commit bb681dbc authored by unknown's avatar unknown

Bug #31153 calling stored procedure crashes server if available memory is low

When the server was out of memory it crashed because of invalid memory access.

This patch adds detection for failed memory allocations and make the server
output a proper error message.


sql/mysqld.cc:
  Don't try to push_warning from within push_warning. It will cause a recursion
  until the stack is consumed.
  
  If my_net_init fails (for example: because of OOM) the temporary vio object 
  might have been attached to the thd object already. This will cause a double
  free on the vio object when the thd object is deleted later on and the server
  will crash.
sql/sp_head.cc:
  Added check for out-of-memory on a 'new' operation.
  Refactored reset_lex method to return a error state code instead of void.
  Initialize the mem-root with init_sql_alloc to get a basic error handler for
  memory allocation problems. This alone won't prevent the server from crashing,
  NULL pointers have to be accounted for as well.
sql/sp_head.h:
  Use the throw() clause in operator new, to indicate to the compiler that
  memory allocation can fail and return NULL, so that the compiler should
  generate code to check for NULL before invoking C++ constructors, to be
  crash safe.
sql/sql_base.cc:
  Use init_sql_alloc to get basic out-of-memory error handling.
sql/sql_lex.h:
  Use the throw() clause in operator new, to indicate to the compiler that
  memory allocation can fail and return NULL, so that the compiler should
  generate code to check for NULL before invoking C++ constructors, to be
  crash safe.
sql/sql_prepare.cc:
  Use init_sql_alloc to get basic out-of-memory error handling.
sql/sql_yacc.yy:
  Check for memory allocation failures where it matters.
parent f4b6234c
......@@ -2489,7 +2489,12 @@ static int my_message_sql(uint error, const char *str, myf MyFlags)
thd->query_error= 1; // needed to catch query errors during replication
if (!thd->no_warnings_for_error)
{
thd->no_warnings_for_error= TRUE;
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, error, str);
thd->no_warnings_for_error= FALSE;
}
/*
thd->lex->current_select == 0 if lex structure is not inited
(not query command (COM_QUERY))
......@@ -4295,8 +4300,13 @@ pthread_handler_t handle_connections_sockets(void *arg __attribute__((unused)))
sock == unix_sock ? VIO_LOCALHOST: 0)) ||
my_net_init(&thd->net,vio_tmp))
{
if (vio_tmp)
vio_delete(vio_tmp);
/*
Only delete the temporary vio if we didn't already attach it to the
NET object. The destructor in THD will delete any initialized net
structure.
*/
if (vio_tmp && thd->net.vio != vio_tmp)
vio_delete(vio_tmp);
else
{
(void) shutdown(new_sock, SHUT_RDWR);
......
......@@ -411,14 +411,16 @@ check_routine_name(LEX_STRING ident)
*/
void *
sp_head::operator new(size_t size)
sp_head::operator new(size_t size) throw()
{
DBUG_ENTER("sp_head::operator new");
MEM_ROOT own_root;
sp_head *sp;
init_alloc_root(&own_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
init_sql_alloc(&own_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
sp= (sp_head *) alloc_root(&own_root, size);
if (sp == NULL)
return NULL;
sp->main_mem_root= own_root;
DBUG_PRINT("info", ("mem_root 0x%lx", (ulong) &sp->mem_root));
DBUG_RETURN(sp);
......@@ -429,6 +431,10 @@ sp_head::operator delete(void *ptr, size_t size)
{
DBUG_ENTER("sp_head::operator delete");
MEM_ROOT own_root;
if (ptr == NULL)
DBUG_VOID_RETURN;
sp_head *sp= (sp_head *) ptr;
/* Make a copy of main_mem_root as free_root will free the sp */
......@@ -472,6 +478,9 @@ sp_head::init(LEX *lex)
lex->spcont= m_pcont= new sp_pcontext();
if (!lex->spcont)
DBUG_VOID_RETURN;
/*
Altough trg_table_fields list is used only in triggers we init for all
types of stored procedures to simplify reset_lex()/restore_lex() code.
......@@ -973,7 +982,7 @@ sp_head::execute(THD *thd)
DBUG_RETURN(TRUE);
/* init per-instruction memroot */
init_alloc_root(&execute_mem_root, MEM_ROOT_BLOCK_SIZE, 0);
init_sql_alloc(&execute_mem_root, MEM_ROOT_BLOCK_SIZE, 0);
DBUG_ASSERT(!(m_flags & IS_INVOKED));
m_flags|= IS_INVOKED;
......@@ -1795,16 +1804,29 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
}
// Reset lex during parsing, before we parse a sub statement.
void
/**
@brief Reset lex during parsing, before we parse a sub statement.
@param thd Thread handler.
@return Error state
@retval true An error occurred.
@retval false Success.
*/
bool
sp_head::reset_lex(THD *thd)
{
DBUG_ENTER("sp_head::reset_lex");
LEX *sublex;
LEX *oldlex= thd->lex;
sublex= new (thd->mem_root)st_lex_local;
if (sublex == 0)
DBUG_RETURN(TRUE);
thd->lex= sublex;
(void)m_lex.push_front(oldlex);
thd->lex= sublex= new st_lex;
/* Reset most stuff. */
lex_start(thd);
......@@ -1827,7 +1849,7 @@ sp_head::reset_lex(THD *thd)
sublex->interval_list.empty();
sublex->type= 0;
DBUG_VOID_RETURN;
DBUG_RETURN(FALSE);
}
// Restore lex during parsing, after we have parsed a sub statement.
......@@ -3703,7 +3725,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex,
if (!(table= (TABLE_LIST *)thd->calloc(sizeof(TABLE_LIST))))
{
my_error(ER_OUTOFMEMORY, MYF(0), sizeof(TABLE_LIST));
thd->fatal_error();
return NULL;
}
table->db_length= strlen(db);
......
......@@ -191,10 +191,10 @@ public:
Security_context m_security_ctx;
static void *
operator new(size_t size);
operator new(size_t size) throw ();
static void
operator delete(void *ptr, size_t size);
operator delete(void *ptr, size_t size) throw ();
sp_head();
......@@ -254,7 +254,7 @@ public:
}
// Resets lex in 'thd' and keeps a copy of the old one.
void
bool
reset_lex(THD *thd);
// Restores lex in 'thd' from our copy, but keeps some status from the
......
......@@ -80,7 +80,6 @@ bool Prelock_error_handler::safely_trapped_errors()
return ((m_handled_errors > 0) && (m_unhandled_errors == 0));
}
TABLE *unused_tables; /* Used by mysql_test */
HASH open_cache; /* Used by mysql_test */
......@@ -2643,7 +2642,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
temporary mem_root for new .frm parsing.
TODO: variables for size
*/
init_alloc_root(&new_frm_mem, 8024, 8024);
init_sql_alloc(&new_frm_mem, 8024, 8024);
thd->current_tablenr= 0;
restart:
......
......@@ -1283,11 +1283,11 @@ typedef struct st_lex : public Query_tables_list
struct st_lex_local: public st_lex
{
static void *operator new(size_t size)
static void *operator new(size_t size) throw()
{
return (void*) sql_alloc((uint) size);
}
static void *operator new(size_t size, MEM_ROOT *mem_root)
static void *operator new(size_t size, MEM_ROOT *mem_root) throw()
{
return (void*) alloc_root(mem_root, (uint) size);
}
......@@ -1303,3 +1303,4 @@ extern void lex_start(THD *thd);
extern void lex_end(LEX *lex);
extern int MYSQLlex(void *arg, void *yythd);
extern char *skip_rear_comments(CHARSET_INFO *cs, char *begin, char *end);
......@@ -2659,7 +2659,7 @@ Prepared_statement::Prepared_statement(THD *thd_arg, Protocol *protocol_arg)
last_errno(0),
flags((uint) IS_IN_USE)
{
init_alloc_root(&main_mem_root, thd_arg->variables.query_alloc_block_size,
init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size,
thd_arg->variables.query_prealloc_size);
*last_error= '\0';
}
......
......@@ -1628,6 +1628,8 @@ create_function_tail:
}
/* Order is important here: new - reset - init */
sp= new sp_head();
if (sp == NULL)
MYSQL_YYABORT;
sp->reset_thd_mem_root(thd);
sp->init(lex);
sp->init_sp_name(thd, lex->spname);
......@@ -1929,7 +1931,8 @@ sp_decl:
{
LEX *lex= Lex;
lex->sphead->reset_lex(YYTHD);
if (lex->sphead->reset_lex(YYTHD))
MYSQL_YYABORT;
lex->spcont->declare_var_boundary($2);
}
type
......@@ -1937,6 +1940,10 @@ sp_decl:
{
LEX *lex= Lex;
sp_pcontext *pctx= lex->spcont;
if (pctx == 0)
{
MYSQL_YYABORT;
}
uint num_vars= pctx->context_var_count();
enum enum_field_types var_type= (enum enum_field_types) $4;
Item *dflt_value_item= $5;
......@@ -2022,12 +2029,15 @@ sp_decl:
{
i= new sp_instr_hreturn(sp->instructions(), ctx,
ctx->current_var_count());
if (i == NULL )
MYSQL_YYABORT;
sp->add_instr(i);
}
else
{ /* EXIT or UNDO handler, just jump to the end of the block */
i= new sp_instr_hreturn(sp->instructions(), ctx, 0);
if (i == NULL)
MYSQL_YYABORT;
sp->add_instr(i);
sp->push_backpatch(i, lex->spcont->last_label()); /* Block end */
}
......@@ -2055,7 +2065,9 @@ sp_decl:
}
i= new sp_instr_cpush(sp->instructions(), ctx, $5,
ctx->current_cursor_count());
sp->add_instr(i);
if ( i==NULL )
MYSQL_YYABORT;
sp->add_instr(i);
ctx->push_cursor(&$2);
$$.vars= $$.conds= $$.hndlrs= 0;
$$.curs= 1;
......@@ -2064,7 +2076,8 @@ sp_decl:
sp_cursor_stmt:
{
Lex->sphead->reset_lex(YYTHD);
if(Lex->sphead->reset_lex(YYTHD))
MYSQL_YYABORT;
/* We use statement here just be able to get a better
error message. Using 'select' works too, but will then
......@@ -2230,7 +2243,8 @@ sp_proc_stmt:
LEX *lex= thd->lex;
Lex_input_stream *lip= thd->m_lip;
lex->sphead->reset_lex(thd);
if (lex->sphead->reset_lex(thd))
MYSQL_YYABORT;
lex->sphead->m_tmp_query= lip->tok_start;
}
statement
......@@ -2255,9 +2269,10 @@ sp_proc_stmt:
lex->var_list.is_empty());
if (lex->sql_command != SQLCOM_SET_OPTION)
{
sp_instr_stmt *i=new sp_instr_stmt(sp->instructions(),
sp_instr_stmt *i= new sp_instr_stmt(sp->instructions(),
lex->spcont, lex);
if (i == NULL)
MYSQL_YYABORT;
/*
Extract the query statement from the tokenizer. The
end is either lex->ptr, if there was no lookahead,
......@@ -2275,7 +2290,10 @@ sp_proc_stmt:
sp->restore_lex(thd);
}
| RETURN_SYM
{ Lex->sphead->reset_lex(YYTHD); }
{
if(Lex->sphead->reset_lex(YYTHD))
MYSQL_YYABORT;
}
expr
{
LEX *lex= Lex;
......@@ -2292,6 +2310,8 @@ sp_proc_stmt:
i= new sp_instr_freturn(sp->instructions(), lex->spcont, $3,
sp->m_return_field_def.sql_type, lex);
if (i == NULL)
MYSQL_YYABORT;
sp->add_instr(i);
sp->m_flags|= sp_head::HAS_RETURN;
}
......@@ -2334,12 +2354,23 @@ sp_proc_stmt:
uint n;
n= ctx->diff_handlers(lab->ctx, TRUE); /* Exclusive the dest. */
if (n)
sp->add_instr(new sp_instr_hpop(ip++, ctx, n));
{
sp_instr_hpop *hpop= new sp_instr_hpop(ip++, ctx, n);
if (hpop == NULL)
MYSQL_YYABORT;
sp->add_instr(hpop);
}
n= ctx->diff_cursors(lab->ctx, TRUE); /* Exclusive the dest. */
if (n)
sp->add_instr(new sp_instr_cpop(ip++, ctx, n));
{
sp_instr_cpop *cpop= new sp_instr_cpop(ip++, ctx, n);
sp->add_instr(cpop);
}
i= new sp_instr_jump(ip, ctx);
if (i == NULL)
MYSQL_YYABORT;
sp->push_backpatch(i, lab); /* Jumping forward */
sp->add_instr(i);
}
......@@ -2364,11 +2395,19 @@ sp_proc_stmt:
n= ctx->diff_handlers(lab->ctx, FALSE); /* Inclusive the dest. */
if (n)
sp->add_instr(new sp_instr_hpop(ip++, ctx, n));
{
sp_instr_hpop *hpop= new sp_instr_hpop(ip++, ctx, n);
sp->add_instr(hpop);
}
n= ctx->diff_cursors(lab->ctx, FALSE); /* Inclusive the dest. */
if (n)
sp->add_instr(new sp_instr_cpop(ip++, ctx, n));
{
sp_instr_cpop *cpop= new sp_instr_cpop(ip++, ctx, n);
sp->add_instr(cpop);
}
i= new sp_instr_jump(ip, ctx, lab->ip); /* Jump back */
if (i == NULL)
MYSQL_YYABORT;
sp->add_instr(i);
}
}
......@@ -2385,6 +2424,8 @@ sp_proc_stmt:
MYSQL_YYABORT;
}
i= new sp_instr_copen(sp->instructions(), lex->spcont, offset);
if (i == NULL)
MYSQL_YYABORT;
sp->add_instr(i);
}
| FETCH_SYM sp_opt_fetch_noise ident INTO
......@@ -2400,6 +2441,8 @@ sp_proc_stmt:
MYSQL_YYABORT;
}
i= new sp_instr_cfetch(sp->instructions(), lex->spcont, offset);
if (i == NULL)
MYSQL_YYABORT;
sp->add_instr(i);
}
sp_fetch_list
......@@ -2417,6 +2460,8 @@ sp_proc_stmt:
MYSQL_YYABORT;
}
i= new sp_instr_cclose(sp->instructions(), lex->spcont, offset);
if (i == NULL)
MYSQL_YYABORT;
sp->add_instr(i);
}
;
......@@ -2472,7 +2517,10 @@ sp_fetch_list:
;
sp_if:
{ Lex->sphead->reset_lex(YYTHD); }
{
if (Lex->sphead->reset_lex(YYTHD))
MYSQL_YYABORT;
}
expr THEN_SYM
{
LEX *lex= Lex;
......@@ -2481,7 +2529,8 @@ sp_if:
uint ip= sp->instructions();
sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, ctx,
$2, lex);
if (i == NULL)
MYSQL_YYABORT;
sp->push_backpatch(i, ctx->push_label((char *)"", 0));
sp->add_cont_backpatch(i);
sp->add_instr(i);
......@@ -2493,7 +2542,8 @@ sp_if:
sp_pcontext *ctx= Lex->spcont;
uint ip= sp->instructions();
sp_instr_jump *i = new sp_instr_jump(ip, ctx);
if (i == NULL)
MYSQL_YYABORT;
sp->add_instr(i);
sp->backpatch(ctx->pop_label());
sp->push_backpatch(i, ctx->push_label((char *)"", 0));
......@@ -2522,7 +2572,8 @@ simple_case_stmt:
{
LEX *lex= Lex;
case_stmt_action_case(lex);
lex->sphead->reset_lex(YYTHD); /* For expr $3 */
if (lex->sphead->reset_lex(YYTHD))
MYSQL_YYABORT; /* For expr $3 */
}
expr
{
......@@ -2571,7 +2622,8 @@ searched_when_clause_list:
simple_when_clause:
WHEN_SYM
{
Lex->sphead->reset_lex(YYTHD); /* For expr $3 */
if (Lex->sphead->reset_lex(YYTHD))
MYSQL_YYABORT; /* For expr $3 */
}
expr
{
......@@ -2592,7 +2644,8 @@ simple_when_clause:
searched_when_clause:
WHEN_SYM
{
Lex->sphead->reset_lex(YYTHD); /* For expr $3 */
if (Lex->sphead->reset_lex(YYTHD))
MYSQL_YYABORT; /* For expr $3 */
}
expr
{
......@@ -2616,6 +2669,8 @@ else_clause_opt:
uint ip= sp->instructions();
sp_instr_error *i= new sp_instr_error(ip, lex->spcont,
ER_SP_CASE_NOT_FOUND);
if (i == NULL)
MYSQL_YYABORT;
sp->add_instr(i);
}
| ELSE sp_proc_stmts1
......@@ -2685,11 +2740,21 @@ sp_unlabeled_control:
sp->backpatch(ctx->last_label()); /* We always have a label */
if ($3.hndlrs)
sp->add_instr(new sp_instr_hpop(sp->instructions(), ctx,
$3.hndlrs));
{
sp_instr_hpop *hpop= new sp_instr_hpop(sp->instructions(), ctx,
$3.hndlrs);
if (hpop == NULL)
MYSQL_YYABORT;
sp->add_instr(hpop);
}
if ($3.curs)
sp->add_instr(new sp_instr_cpop(sp->instructions(), ctx,
$3.curs));
{
sp_instr_cpop *cpop= new sp_instr_cpop(sp->instructions(), ctx,
$3.curs);
if (cpop == NULL)
MYSQL_YYABORT;
sp->add_instr(cpop);
}
lex->spcont= ctx->pop_context();
}
| LOOP_SYM
......@@ -2699,11 +2764,15 @@ sp_unlabeled_control:
uint ip= lex->sphead->instructions();
sp_label_t *lab= lex->spcont->last_label(); /* Jumping back */
sp_instr_jump *i = new sp_instr_jump(ip, lex->spcont, lab->ip);
if (i == NULL)
MYSQL_YYABORT;
lex->sphead->add_instr(i);
}
| WHILE_SYM
{ Lex->sphead->reset_lex(YYTHD); }
{
if (Lex->sphead->reset_lex(YYTHD))
MYSQL_YYABORT;
}
expr DO_SYM
{
LEX *lex= Lex;
......@@ -2711,7 +2780,8 @@ sp_unlabeled_control:
uint ip= sp->instructions();
sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, lex->spcont,
$3, lex);
if (i == NULL)
MYSQL_YYABORT;
/* Jumping forward */
sp->push_backpatch(i, lex->spcont->last_label());
sp->new_cont_backpatch(i);
......@@ -2724,12 +2794,16 @@ sp_unlabeled_control:
uint ip= lex->sphead->instructions();
sp_label_t *lab= lex->spcont->last_label(); /* Jumping back */
sp_instr_jump *i = new sp_instr_jump(ip, lex->spcont, lab->ip);
if (i == NULL)
MYSQL_YYABORT;
lex->sphead->add_instr(i);
lex->sphead->do_cont_backpatch();
}
| REPEAT_SYM sp_proc_stmts1 UNTIL_SYM
{ Lex->sphead->reset_lex(YYTHD); }
{
if (Lex->sphead->reset_lex(YYTHD))
MYSQL_YYABORT;
}
expr END REPEAT_SYM
{
LEX *lex= Lex;
......@@ -2738,6 +2812,8 @@ sp_unlabeled_control:
sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, lex->spcont,
$5, lab->ip,
lex);
if (i == NULL)
MYSQL_YYABORT;
lex->sphead->add_instr(i);
lex->sphead->restore_lex(YYTHD);
/* We can shortcut the cont_backpatch here */
......@@ -4271,6 +4347,8 @@ select_init2:
select_part2
{
LEX *lex= Lex;
if (lex == NULL)
MYSQL_YYABORT;
SELECT_LEX * sel= lex->current_select;
if (lex->current_select->set_braces(0))
{
......@@ -4624,6 +4702,8 @@ predicate:
$7->push_front($5);
$7->push_front($1);
Item_func_in *item = new (YYTHD->mem_root) Item_func_in(*$7);
if (item == NULL)
MYSQL_YYABORT;
item->negate();
$$= item;
}
......@@ -5084,7 +5164,8 @@ simple_expr:
{
LEX *lex= Lex;
sp_name *name= new sp_name($1, $3, true);
if (name == NULL)
MYSQL_YYABORT;
name->init_qname(YYTHD);
sp_add_used_routine(lex, YYTHD, name, TYPE_ENUM_FUNCTION);
if ($5)
......@@ -5199,8 +5280,9 @@ simple_expr:
if (lex->copy_db_to(&db.str, &db.length))
MYSQL_YYABORT;
sp_name *name= new sp_name(db, $1, false);
if (name)
name->init_qname(thd);
if (name == NULL)
MYSQL_YYABORT;
name->init_qname(thd);
sp_add_used_routine(lex, YYTHD, name, TYPE_ENUM_FUNCTION);
if ($4)
......@@ -8422,7 +8504,8 @@ option_type_value:
QQ: May be we should simply prohibit group assignments in SP?
*/
Lex->sphead->reset_lex(thd);
if (Lex->sphead->reset_lex(thd))
MYSQL_YYABORT;
lex= thd->lex;
/* Set new LEX as if we at start of set rule. */
......@@ -8587,6 +8670,8 @@ sys_option_value:
it= spv->dflt;
else
it= new Item_null();
if (it == NULL)
MYSQL_YYABORT;
sp_set= new sp_instr_set(lex->sphead->instructions(), ctx,
spv->offset, it, spv->type, lex, TRUE);
lex->sphead->add_instr(sp_set);
......@@ -9832,6 +9917,8 @@ sp_tail:
/* Order is important here: new - reset - init */
sp= new sp_head();
if (sp == NULL)
MYSQL_YYABORT;
sp->reset_thd_mem_root(YYTHD);
sp->init(lex);
sp->m_type= TYPE_ENUM_PROCEDURE;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment