CVE-2016-6663 (C, published 2016-12-13T21:59Z)
Description: value: Race condition in Oracle MySQL before 5.5.52, 5.6.x before 5.6.33, 5.7.x before 5.7.15, and 8.x before 8.0.1; MariaDB before 5.5.52, 10.0.x before 10.0.28, and 10.1.x before 10.1.18; Percona Server before 5.5.51-38.2, 5.6.x before 5.6.32-78-1, and 5.7.x before 5.7.14-8; and Percona XtraDB Cluster before 5.5.41-37.0, 5.6.x before 5.6.32-25.17, and 5.7.x before 5.7.14-26.17 allows local users with certain permissions to gain privileges by leveraging use of my_copystat by REPAIR TABLE to repair a MyISAM table.
Severity: MEDIUM (CVSS v3 base score: 7)
Weakness: CWE-362 \u2014 Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
The product contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently.
Repository: https://github.com/mysql/mysql-server
Files changed:
include/my_sys.h
include/myisam.h
mysys/my_redel.c
storage/myisam/ha_myisam.cc
storage/myisam/mi_check.c
storage/myisam/myisamchk.c
Commit message: Bug#24388746: PRIVILEGE ESCALATION AND RACE CONDITION USING CREATE TABLE

During REPAIR TABLE of a MyISAM table, a temporary data file (.TMD)
is created. When repair finishes, this file is renamed to the original
.MYD file. The problem was that during this rename, we copied the
stats from the old file to the new file with chmod/chown. If a user
managed to replace the temporary file before chmod/chown was executed,
it was possible to get an arbitrary file with the privileges of the
mysql user.

This patch fixes the problem by not copying stats from the old
file to the new file. This is not needed as the new file was
created with the correct stats. This fix only changes server
behavior - external utilities such as myisamchk still does
chmod/chown.

No test case provided since the problem involves synchronization
with file system operations.


Vulnerable code:
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
   Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
	      char * name, int rep_quick);
int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name);
		      const char * name, int rep_quick);
		      const char * name, int rep_quick);
/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
  if (my_copystat(org_name,tmp_name,MyFlags) < 0)
    goto end;
   Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
            param.testflag & T_QUICK);
            param.testflag & T_QUICK);
			param.testflag & T_QUICK);
      error=mi_sort_index(&param,file,fixed_name);
   Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
	      char * name, int rep_quick)
			    (param->testflag & T_BACKUP_DATA ?
			     MYF(MY_REDEL_MAKE_BACKUP): MYF(0))) ||
int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name)
			MYF(0)) ||
		      const char * name, int rep_quick)
			    (param->testflag & T_BACKUP_DATA ?
			     MYF(MY_REDEL_MAKE_BACKUP): MYF(0))) ||
			const char * name, int rep_quick)
			    (param->testflag & T_BACKUP_DATA ?
			     MYF(MY_REDEL_MAKE_BACKUP): MYF(0))) ||
/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
            error=mi_repair_by_sort(param,info,filename,rep_quick);
            error=mi_repair_parallel(param,info,filename,rep_quick);
	  error=mi_repair(param, info,filename,rep_quick);
	    error=mi_repair_by_sort(param,info,filename,1);
	error=mi_sort_index(param,info,filename);

Fixed code:
/* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
#define MY_REDEL_NO_COPY_STAT 512 /* my_redel() doesn't call my_copystat() */
   Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
	      char * name, int rep_quick, my_bool no_copy_stat);
int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name,
                  my_bool no_copy_stat);
		      const char * name, int rep_quick, my_bool no_copy_stat);
                       const char * name, int rep_quick, my_bool no_copy_stat);
/* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

          if MY_REDEL_NO_COPY_STAT is given, stats are not copied
          from org_name to tmp_name.
  if (!(MyFlags & MY_REDEL_NO_COPY_STAT))
  {
    if (my_copystat(org_name,tmp_name,MyFlags) < 0)
      goto end;
  }
   Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
        /*
          The new file is created with the right stats, so we can skip
          copying file stats from old to new.
        */
                                   param.testflag & T_QUICK, TRUE);
        /*
          The new file is created with the right stats, so we can skip
          copying file stats from old to new.
        */
                                  param.testflag & T_QUICK, TRUE);
      /*
        The new file is created with the right stats, so we can skip
        copying file stats from old to new.
      */
			param.testflag & T_QUICK, TRUE);
      /*
        The new file is created with the right stats, so we can skip
        copying file stats from old to new.
      */
      error=mi_sort_index(&param,file,fixed_name, TRUE);
   Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
	      char * name, int rep_quick, my_bool no_copy_stat)
      myf flags= 0;
      if (param->testflag & T_BACKUP_DATA)
        flags |= MY_REDEL_MAKE_BACKUP;
      if (no_copy_stat)
        flags |= MY_REDEL_NO_COPY_STAT;
                            flags) ||
int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name,
                  my_bool no_copy_stat)
			no_copy_stat ? MYF(MY_REDEL_NO_COPY_STAT) : MYF(0)) ||
    no_copy_stat        Don't copy file stats from old to new file,
                        assume that new file was created with correct stats
		      const char * name, int rep_quick, my_bool no_copy_stat)
      myf flags= 0;
      if (param->testflag & T_BACKUP_DATA)
        flags |= MY_REDEL_MAKE_BACKUP;
      if (no_copy_stat)
        flags |= MY_REDEL_NO_COPY_STAT;
                            flags) ||
    no_copy_stat        Don't copy file stats from old to new file,
                        assume that new file was created with correct stats
                       const char * name, int rep_quick, my_bool no_copy_stat)
      myf flags= 0;
      if (param->testflag & T_BACKUP_DATA)
        flags |= MY_REDEL_MAKE_BACKUP;
      if (no_copy_stat)
        flags |= MY_REDEL_NO_COPY_STAT;
			    flags) ||
/* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
          /*
            The new file might not be created with the right stats depending
            on how myisamchk is run, so we must copy file stats from old to new.
          */
            error= mi_repair_by_sort(param, info, filename, rep_quick, FALSE);
            error= mi_repair_parallel(param, info, filename, rep_quick, FALSE);
	  error= mi_repair(param, info, filename, rep_quick, FALSE);
	    error= mi_repair_by_sort(param, info, filename, 1, FALSE);
	error= mi_sort_index(param, info, filename, FALSE);