Commit 51dddb3a authored by unknown's avatar unknown
Browse files

Merge moonlight.intranet:/home/tomash/src/mysql_ab/mysql-5.0

into  moonlight.intranet:/home/tomash/src/mysql_ab/mysql-5.0-bug18630


sql/item_func.cc:
  Auto merged
sql/item_func.h:
  Auto merged
sql/sql_parse.cc:
  Auto merged
sql/sql_trigger.cc:
  Auto merged
parents f60d9a3e 06bf59ad
Loading
Loading
Loading
Loading
+52 −0
Original line number Diff line number Diff line
@@ -451,3 +451,55 @@ SELECT Host,User,Password FROM mysql.user WHERE User='user19857';
Host	User	Password
localhost	user19857	*82DC221D557298F6CE9961037DB1C90604792F5C
DROP USER user19857@localhost;
DROP TABLE IF EXISTS t1;
DROP VIEW IF EXISTS v1;
DROP FUNCTION IF EXISTS f_suid;
DROP PROCEDURE IF EXISTS p_suid;
DROP FUNCTION IF EXISTS f_evil;
DELETE FROM mysql.user WHERE user LIKE 'mysqltest\_%';
DELETE FROM mysql.db WHERE user LIKE 'mysqltest\_%';
DELETE FROM mysql.tables_priv WHERE user LIKE 'mysqltest\_%';
DELETE FROM mysql.columns_priv WHERE user LIKE 'mysqltest\_%';
FLUSH PRIVILEGES;
CREATE TABLE t1 (i INT);
CREATE FUNCTION f_suid(i INT) RETURNS INT SQL SECURITY DEFINER RETURN 0;
CREATE PROCEDURE p_suid(IN i INT) SQL SECURITY DEFINER SET @c:= 0;
CREATE USER mysqltest_u1@localhost;
GRANT EXECUTE ON test.* TO mysqltest_u1@localhost;
CREATE DEFINER=mysqltest_u1@localhost FUNCTION f_evil () RETURNS INT
SQL SECURITY INVOKER
BEGIN
SET @a:= CURRENT_USER();
SET @b:= (SELECT COUNT(*) FROM t1);
RETURN @b;
END|
CREATE SQL SECURITY INVOKER VIEW v1 AS SELECT f_evil();
SELECT COUNT(*) FROM t1;
ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1'
SELECT f_evil();
ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1'
SELECT @a, @b;
@a	@b
mysqltest_u1@localhost	NULL
SELECT f_suid(f_evil());
ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1'
SELECT @a, @b;
@a	@b
mysqltest_u1@localhost	NULL
CALL p_suid(f_evil());
ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1'
SELECT @a, @b;
@a	@b
mysqltest_u1@localhost	NULL
SELECT * FROM v1;
ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 'v1'
SELECT @a, @b;
@a	@b
mysqltest_u1@localhost	NULL
DROP VIEW v1;
DROP FUNCTION f_evil;
DROP USER mysqltest_u1@localhost;
DROP PROCEDURE p_suid;
DROP FUNCTION f_suid;
DROP TABLE t1;
End of 5.0 tests.
+77 −1
Original line number Diff line number Diff line
@@ -790,4 +790,80 @@ SELECT Host,User,Password FROM mysql.user WHERE User='user19857';

DROP USER user19857@localhost;

# End of 5.0 bugs.
--disconnect con1root
--connection default


#
# BUG#18630: Arguments of suid routine calculated in wrong security
# context
#
# Arguments of suid routines were calculated in definer's security
# context instead of caller's context thus creating security hole.
#
--disable_warnings
DROP TABLE IF EXISTS t1;
DROP VIEW IF EXISTS v1;
DROP FUNCTION IF EXISTS f_suid;
DROP PROCEDURE IF EXISTS p_suid;
DROP FUNCTION IF EXISTS f_evil;
--enable_warnings
DELETE FROM mysql.user WHERE user LIKE 'mysqltest\_%';
DELETE FROM mysql.db WHERE user LIKE 'mysqltest\_%';
DELETE FROM mysql.tables_priv WHERE user LIKE 'mysqltest\_%';
DELETE FROM mysql.columns_priv WHERE user LIKE 'mysqltest\_%';
FLUSH PRIVILEGES;

CREATE TABLE t1 (i INT);
CREATE FUNCTION f_suid(i INT) RETURNS INT SQL SECURITY DEFINER RETURN 0;
CREATE PROCEDURE p_suid(IN i INT) SQL SECURITY DEFINER SET @c:= 0;

CREATE USER mysqltest_u1@localhost;
# Thanks to this grant statement privileges of anonymous users on
# 'test' database are not applicable for mysqltest_u1@localhost.
GRANT EXECUTE ON test.* TO mysqltest_u1@localhost;

delimiter |;
CREATE DEFINER=mysqltest_u1@localhost FUNCTION f_evil () RETURNS INT
  SQL SECURITY INVOKER
BEGIN
  SET @a:= CURRENT_USER();
  SET @b:= (SELECT COUNT(*) FROM t1);
  RETURN @b;
END|
delimiter ;|

CREATE SQL SECURITY INVOKER VIEW v1 AS SELECT f_evil();

connect (conn1, localhost, mysqltest_u1,,);

--error ER_TABLEACCESS_DENIED_ERROR
SELECT COUNT(*) FROM t1;

--error ER_TABLEACCESS_DENIED_ERROR
SELECT f_evil();
SELECT @a, @b;

--error ER_TABLEACCESS_DENIED_ERROR
SELECT f_suid(f_evil());
SELECT @a, @b;

--error ER_TABLEACCESS_DENIED_ERROR
CALL p_suid(f_evil());
SELECT @a, @b;

--error ER_TABLEACCESS_DENIED_ERROR
SELECT * FROM v1;
SELECT @a, @b;

disconnect conn1;
connection default;

DROP VIEW v1;
DROP FUNCTION f_evil;
DROP USER mysqltest_u1@localhost;
DROP PROCEDURE p_suid;
DROP FUNCTION f_suid;
DROP TABLE t1;

--echo End of 5.0 tests.
+28 −56
Original line number Diff line number Diff line
@@ -4830,7 +4830,9 @@ Item_func_sp::execute_impl(THD *thd, Field *return_value_fld)
{
  bool err_status= TRUE;
  Sub_statement_state statement_state;
  Security_context *save_security_ctx= thd->security_ctx, *save_ctx_func;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
  Security_context *save_security_ctx= thd->security_ctx;
#endif

  DBUG_ENTER("Item_func_sp::execute_impl");

@@ -4841,7 +4843,7 @@ Item_func_sp::execute_impl(THD *thd, Field *return_value_fld)
    thd->security_ctx= context->security_ctx;
  }
#endif
  if (find_and_check_access(thd, EXECUTE_ACL, &save_ctx_func))
  if (find_and_check_access(thd))
    goto error;

  /*
@@ -4853,13 +4855,11 @@ Item_func_sp::execute_impl(THD *thd, Field *return_value_fld)
  err_status= m_sp->execute_function(thd, args, arg_count, return_value_fld);
  thd->restore_sub_statement_state(&statement_state);

#ifndef NO_EMBEDDED_ACCESS_CHECKS
  sp_restore_security_context(thd, save_ctx_func);
error:
#ifndef NO_EMBEDDED_ACCESS_CHECKS
  thd->security_ctx= save_security_ctx;
#else
error:
#endif

  DBUG_RETURN(err_status);
}

@@ -4976,70 +4976,38 @@ Item_func_sp::tmp_table_field(TABLE *t_arg)
  SYNOPSIS
    find_and_check_access()
    thd           thread handler
    want_access   requested access
    save          backup of security context

  RETURN
    FALSE    Access granted
    TRUE     Requested access can't be granted or function doesn't exists
	     In this case security context is not changed and *save = 0

  NOTES
    Checks if requested access to function can be granted to user.
    If function isn't found yet, it searches function first.
    If function can't be found or user don't have requested access
    error is raised.
    If security context sp_ctx is provided and access can be granted then
    switch back to previous context isn't performed.
    In case of access error or if context is not provided then
    find_and_check_access() switches back to previous security context.
*/

bool
Item_func_sp::find_and_check_access(THD *thd, ulong want_access,
                                    Security_context **save)
Item_func_sp::find_and_check_access(THD *thd)
{
  bool res= TRUE;

  *save= 0;                                     // Safety if error
  if (! m_sp && ! (m_sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, m_name,
                                         &thd->sp_func_cache, TRUE)))
  {
    my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "FUNCTION", m_name->m_qname.str);
    goto error;
    return TRUE;
  }

#ifndef NO_EMBEDDED_ACCESS_CHECKS
  if (check_routine_access(thd, want_access,
			   m_sp->m_db.str, m_sp->m_name.str, 0, FALSE))
    goto error;

  sp_change_security_context(thd, m_sp, save);
  /*
    If we changed context to run as another user, we need to check the
    access right for the new context again as someone may have deleted
    this person the right to use the procedure

    TODO:
      Cache if the definer has the right to use the object on the first
      usage and only reset the cache if someone does a GRANT statement
      that 'may' affect this.
  */
  if (*save &&
      check_routine_access(thd, want_access,
  if (check_routine_access(thd, EXECUTE_ACL,
			   m_sp->m_db.str, m_sp->m_name.str, 0, FALSE))
  {
    sp_restore_security_context(thd, *save);
    *save= 0;                                   // Safety
    goto error;
  }
    return TRUE;
#endif
  res= FALSE;                                   // no error

error:
  return res;
  return FALSE;
}


bool
Item_func_sp::fix_fields(THD *thd, Item **ref)
{
@@ -5050,19 +5018,23 @@ Item_func_sp::fix_fields(THD *thd, Item **ref)
  {
    /*
      Here we check privileges of the stored routine only during view
      creation, in order to validate the view. A runtime check is perfomed
      in Item_func_sp::execute(), and this method is not called during
      context analysis. We do not need to restore the security context
      changed in find_and_check_access because all view structures created
      in CREATE VIEW are not used for execution.  Notice, that during view
      creation we do not infer into stored routine bodies and do not check
      privileges of its statements, which would probably be a good idea
      especially if the view has SQL SECURITY DEFINER and the used stored
      procedure has SQL SECURITY DEFINER
      creation, in order to validate the view.  A runtime check is
      perfomed in Item_func_sp::execute(), and this method is not
      called during context analysis.  Notice, that during view
      creation we do not infer into stored routine bodies and do not
      check privileges of its statements, which would probably be a
      good idea especially if the view has SQL SECURITY DEFINER and
      the used stored procedure has SQL SECURITY DEFINER.
    */
    Security_context *save_ctx;
    if (!(res= find_and_check_access(thd, EXECUTE_ACL, &save_ctx)))
      sp_restore_security_context(thd, save_ctx);
    res= find_and_check_access(thd);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
    Security_context *save_secutiry_ctx;
    if (!res && !(res= set_routine_security_ctx(thd, m_sp, false,
                                                &save_secutiry_ctx)))
    {
      sp_restore_security_context(thd, save_secutiry_ctx);
    }
#endif /* ! NO_EMBEDDED_ACCESS_CHECKS */
  }
  return res;
}
+1 −2
Original line number Diff line number Diff line
@@ -1465,8 +1465,7 @@ class Item_func_sp :public Item_func
    { context= (Name_resolution_context *)cntx; return FALSE; }

  void fix_length_and_dec();
  bool find_and_check_access(THD * thd, ulong want_access,
                             Security_context **backup);
  bool find_and_check_access(THD * thd);
  virtual enum Functype functype() const { return FUNC_SP; }

  bool fix_fields(THD *thd, Item **ref);
+186 −4
Original line number Diff line number Diff line
@@ -1097,6 +1097,7 @@ sp_head::execute(THD *thd)

  thd->restore_active_arena(&execute_arena, &backup_arena);

  thd->spcont->pop_all_cursors(); // To avoid memory leaks after an error

  /* Restore all saved */
  old_packet.swap(thd->packet);
@@ -1158,6 +1159,161 @@ sp_head::execute(THD *thd)
               m_first_instance->m_first_free_instance->m_recursion_level ==
               m_recursion_level + 1));
  m_first_instance->m_first_free_instance= this;

  DBUG_RETURN(err_status);
}


#ifndef NO_EMBEDDED_ACCESS_CHECKS
/*
  set_routine_security_ctx() changes routine security context, and
  checks if there is an EXECUTE privilege in new context.  If there is
  no EXECUTE privilege, it changes the context back and returns a
  error.

  SYNOPSIS
    set_routine_security_ctx()
      thd         thread handle
      sp          stored routine to change the context for
      is_proc     TRUE is procedure, FALSE if function
      save_ctx    pointer to an old security context
   
  RETURN
    TRUE if there was a error, and the context wasn't changed.
    FALSE if the context was changed.
*/

bool
set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc,
                         Security_context **save_ctx)
{
  *save_ctx= 0;
  if (sp_change_security_context(thd, sp, save_ctx))
    return TRUE;

  /*
    If we changed context to run as another user, we need to check the
    access right for the new context again as someone may have revoked
    the right to use the procedure from this user.

    TODO:
      Cache if the definer has the right to use the object on the
      first usage and only reset the cache if someone does a GRANT
      statement that 'may' affect this.
  */
  if (*save_ctx &&
      check_routine_access(thd, EXECUTE_ACL,
                           sp->m_db.str, sp->m_name.str, is_proc, FALSE))
  {
    sp_restore_security_context(thd, *save_ctx);
    *save_ctx= 0;
    return TRUE;
  }

  return FALSE;
}
#endif // ! NO_EMBEDDED_ACCESS_CHECKS


/*
  Execute a trigger:
   - changes security context for triggers
   - switch to new memroot
   - call sp_head::execute
   - restore old memroot
   - restores security context

  SYNOPSIS
    sp_head::execute_trigger()
      thd               Thread handle
      db                database name
      table             table name
      grant_info        GRANT_INFO structure to be filled with
                        information about definer's privileges
                        on subject table
   
  RETURN
    FALSE  on success
    TRUE   on error
*/

bool
sp_head::execute_trigger(THD *thd, const char *db, const char *table,
                         GRANT_INFO *grant_info)
{
  sp_rcontext *octx = thd->spcont;
  sp_rcontext *nctx = NULL;
  bool err_status= FALSE;
  MEM_ROOT call_mem_root;
  Query_arena call_arena(&call_mem_root, Query_arena::INITIALIZED_FOR_SP);
  Query_arena backup_arena;

  DBUG_ENTER("sp_head::execute_trigger");
  DBUG_PRINT("info", ("trigger %s", m_name.str));

#ifndef NO_EMBEDDED_ACCESS_CHECKS
  Security_context *save_ctx;
  if (sp_change_security_context(thd, this, &save_ctx))
    DBUG_RETURN(TRUE);

  /*
    NOTE: TRIGGER_ACL should be used here.
  */
  if (check_global_access(thd, SUPER_ACL))
  {
    sp_restore_security_context(thd, save_ctx);
    DBUG_RETURN(TRUE);
  }

  /*
    Fetch information about table-level privileges to GRANT_INFO
    structure for subject table. Check of privileges that will use it
    and information about column-level privileges will happen in
    Item_trigger_field::fix_fields().
  */
  fill_effective_table_privileges(thd, grant_info, db, table);
#endif // NO_EMBEDDED_ACCESS_CHECKS

  /*
    Prepare arena and memroot for objects which lifetime is whole
    duration of trigger call (sp_rcontext, it's tables and items,
    sp_cursor and Item_cache holders for case expressions).  We can't
    use caller's arena/memroot for those objects because in this case
    some fixed amount of memory will be consumed for each trigger
    invocation and so statements which involve lot of them will hog
    memory.

    TODO: we should create sp_rcontext once per command and reuse it
    on subsequent executions of a trigger.
  */
  init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0);
  thd->set_n_backup_active_arena(&call_arena, &backup_arena);

  if (!(nctx= new sp_rcontext(m_pcont, 0, octx)) ||
      nctx->init(thd))
  {
    err_status= TRUE;
    goto err_with_cleanup;
  }

#ifndef DBUG_OFF
  nctx->sp= this;
#endif

  thd->spcont= nctx;

  err_status= execute(thd);

err_with_cleanup:
  thd->restore_active_arena(&call_arena, &backup_arena);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
  sp_restore_security_context(thd, save_ctx);
#endif // NO_EMBEDDED_ACCESS_CHECKS
  delete nctx;
  call_arena.free_items();
  free_root(&call_mem_root, MYF(0));
  thd->spcont= octx;

  DBUG_RETURN(err_status);
}

@@ -1165,8 +1321,12 @@ sp_head::execute(THD *thd)
/*
  Execute a function:
   - evaluate parameters
   - changes security context for SUID routines
   - switch to new memroot
   - call sp_head::execute
   - restore old memroot
   - evaluate the return value
   - restores security context

  SYNOPSIS
    sp_head::execute_function()
@@ -1293,6 +1453,15 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,
  }
  thd->spcont= nctx;

#ifndef NO_EMBEDDED_ACCESS_CHECKS
  Security_context *save_security_ctx;
  if (set_routine_security_ctx(thd, this, FALSE, &save_security_ctx))
  {
    err_status= TRUE;
    goto err_with_cleanup;
  }
#endif

  binlog_save_options= thd->options;
  if (need_binlog_call)
  {
@@ -1333,7 +1502,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,
    reset_dynamic(&thd->user_var_events);
  }

  if (m_type == TYPE_ENUM_FUNCTION && !err_status)
  if (!err_status)
  {
    /* We need result only in function but not in trigger */

@@ -1344,8 +1513,9 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,
    }
  }


  nctx->pop_all_cursors();	// To avoid memory leaks after an error
#ifndef NO_EMBEDDED_ACCESS_CHECKS
  sp_restore_security_context(thd, save_security_ctx);
#endif

err_with_cleanup:
  delete nctx;
@@ -1368,8 +1538,10 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,

  The function does the following steps:
   - Set all parameters 
   - changes security context for SUID routines
   - call sp_head::execute
   - copy back values of INOUT and OUT parameters
   - restores security context

  RETURN
    FALSE  on success
@@ -1490,6 +1662,12 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)

  thd->spcont= nctx;

#ifndef NO_EMBEDDED_ACCESS_CHECKS
  Security_context *save_security_ctx= 0;
  if (!err_status)
    err_status= set_routine_security_ctx(thd, this, TRUE, &save_security_ctx);
#endif

  if (!err_status)
    err_status= execute(thd);

@@ -1534,10 +1712,14 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
    }
  }

#ifndef NO_EMBEDDED_ACCESS_CHECKS
  if (save_security_ctx)
    sp_restore_security_context(thd, save_security_ctx);
#endif

  if (!save_spcont)
    delete octx;

  nctx->pop_all_cursors();	// To avoid memory leaks after an error
  delete nctx;
  thd->spcont= save_spcont;

Loading