Commit 033b1191 authored by Jon Olav Hauglid's avatar Jon Olav Hauglid

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.
parent 8b1f4d84
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. /* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -83,6 +83,7 @@ typedef struct my_aio_result { ...@@ -83,6 +83,7 @@ typedef struct my_aio_result {
#define MY_RESOLVE_LINK 128 /* my_realpath(); Only resolve links */ #define MY_RESOLVE_LINK 128 /* my_realpath(); Only resolve links */
#define MY_HOLD_ORIGINAL_MODES 128 /* my_copy() holds to file modes */ #define MY_HOLD_ORIGINAL_MODES 128 /* my_copy() holds to file modes */
#define MY_REDEL_MAKE_BACKUP 256 #define MY_REDEL_MAKE_BACKUP 256
#define MY_REDEL_NO_COPY_STAT 512 /* my_redel() doesn't call my_copystat() */
#define MY_SEEK_NOT_DONE 32 /* my_lock may have to do a seek */ #define MY_SEEK_NOT_DONE 32 /* my_lock may have to do a seek */
#define MY_DONT_WAIT 64 /* my_lock() don't wait if can't lock */ #define MY_DONT_WAIT 64 /* my_lock() don't wait if can't lock */
#define MY_ZEROFILL 32 /* my_malloc(), fill array with zero */ #define MY_ZEROFILL 32 /* my_malloc(), fill array with zero */
......
/* /*
Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -426,12 +426,13 @@ int chk_size(MI_CHECK *param, MI_INFO *info); ...@@ -426,12 +426,13 @@ int chk_size(MI_CHECK *param, MI_INFO *info);
int chk_key(MI_CHECK *param, MI_INFO *info); int chk_key(MI_CHECK *param, MI_INFO *info);
int chk_data_link(MI_CHECK *param, MI_INFO *info,int extend); int chk_data_link(MI_CHECK *param, MI_INFO *info,int extend);
int mi_repair(MI_CHECK *param, register MI_INFO *info, int mi_repair(MI_CHECK *param, register MI_INFO *info,
char * name, int rep_quick); char * name, int rep_quick, my_bool no_copy_stat);
int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name); int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name,
my_bool no_copy_stat);
int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info, int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info,
const char * name, int rep_quick); const char * name, int rep_quick, my_bool no_copy_stat);
int mi_repair_parallel(MI_CHECK *param, register MI_INFO *info, int mi_repair_parallel(MI_CHECK *param, register MI_INFO *info,
const char * name, int rep_quick); const char * name, int rep_quick, my_bool no_copy_stat);
int change_to_newfile(const char * filename, const char * old_ext, int change_to_newfile(const char * filename, const char * old_ext,
const char * new_ext, myf myflags); const char * new_ext, myf myflags);
int lock_file(MI_CHECK *param, File file, my_off_t start, int lock_type, int lock_file(MI_CHECK *param, File file, my_off_t start, int lock_type,
......
/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. /* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -35,6 +35,9 @@ struct utimbuf { ...@@ -35,6 +35,9 @@ struct utimbuf {
if MY_REDEL_MAKE_COPY is given, then the orginal file if MY_REDEL_MAKE_COPY is given, then the orginal file
is renamed to org_name-'current_time'.BAK is renamed to org_name-'current_time'.BAK
if MY_REDEL_NO_COPY_STAT is given, stats are not copied
from org_name to tmp_name.
*/ */
#define REDEL_EXT ".BAK" #define REDEL_EXT ".BAK"
...@@ -46,8 +49,11 @@ int my_redel(const char *org_name, const char *tmp_name, myf MyFlags) ...@@ -46,8 +49,11 @@ int my_redel(const char *org_name, const char *tmp_name, myf MyFlags)
DBUG_PRINT("my",("org_name: '%s' tmp_name: '%s' MyFlags: %d", DBUG_PRINT("my",("org_name: '%s' tmp_name: '%s' MyFlags: %d",
org_name,tmp_name,MyFlags)); org_name,tmp_name,MyFlags));
if (!(MyFlags & MY_REDEL_NO_COPY_STAT))
{
if (my_copystat(org_name,tmp_name,MyFlags) < 0) if (my_copystat(org_name,tmp_name,MyFlags) < 0)
goto end; goto end;
}
if (MyFlags & MY_REDEL_MAKE_BACKUP) if (MyFlags & MY_REDEL_MAKE_BACKUP)
{ {
char name_buff[FN_REFLEN+20]; char name_buff[FN_REFLEN+20];
......
/* /*
Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -1091,24 +1091,36 @@ int ha_myisam::repair(THD *thd, MI_CHECK &param, bool do_optimize) ...@@ -1091,24 +1091,36 @@ int ha_myisam::repair(THD *thd, MI_CHECK &param, bool do_optimize)
/* TODO: respect myisam_repair_threads variable */ /* TODO: respect myisam_repair_threads variable */
my_snprintf(buf, 40, "Repair with %d threads", my_count_bits(key_map)); my_snprintf(buf, 40, "Repair with %d threads", my_count_bits(key_map));
thd_proc_info(thd, buf); thd_proc_info(thd, buf);
/*
The new file is created with the right stats, so we can skip
copying file stats from old to new.
*/
error = mi_repair_parallel(&param, file, fixed_name, error = mi_repair_parallel(&param, file, fixed_name,
param.testflag & T_QUICK); param.testflag & T_QUICK, TRUE);
thd_proc_info(thd, "Repair done"); // to reset proc_info, as thd_proc_info(thd, "Repair done"); // to reset proc_info, as
// it was pointing to local buffer // it was pointing to local buffer
} }
else else
{ {
thd_proc_info(thd, "Repair by sorting"); thd_proc_info(thd, "Repair by sorting");
/*
The new file is created with the right stats, so we can skip
copying file stats from old to new.
*/
error = mi_repair_by_sort(&param, file, fixed_name, error = mi_repair_by_sort(&param, file, fixed_name,
param.testflag & T_QUICK); param.testflag & T_QUICK, TRUE);
} }
} }
else else
{ {
thd_proc_info(thd, "Repair with keycache"); thd_proc_info(thd, "Repair with keycache");
param.testflag &= ~T_REP_BY_SORT; param.testflag &= ~T_REP_BY_SORT;
/*
The new file is created with the right stats, so we can skip
copying file stats from old to new.
*/
error= mi_repair(&param, file, fixed_name, error= mi_repair(&param, file, fixed_name,
param.testflag & T_QUICK); param.testflag & T_QUICK, TRUE);
} }
#ifdef HAVE_MMAP #ifdef HAVE_MMAP
if (remap) if (remap)
...@@ -1124,7 +1136,11 @@ int ha_myisam::repair(THD *thd, MI_CHECK &param, bool do_optimize) ...@@ -1124,7 +1136,11 @@ int ha_myisam::repair(THD *thd, MI_CHECK &param, bool do_optimize)
{ {
optimize_done=1; optimize_done=1;
thd_proc_info(thd, "Sorting index"); thd_proc_info(thd, "Sorting index");
error=mi_sort_index(&param,file,fixed_name); /*
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);
} }
if (!statistics_done && (local_testflag & T_STATISTICS)) if (!statistics_done && (local_testflag & T_STATISTICS))
{ {
......
/* /*
Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved. Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -1512,7 +1512,7 @@ static int mi_drop_all_indexes(MI_CHECK *param, MI_INFO *info, my_bool force) ...@@ -1512,7 +1512,7 @@ static int mi_drop_all_indexes(MI_CHECK *param, MI_INFO *info, my_bool force)
/* Save new datafile-name in temp_filename */ /* Save new datafile-name in temp_filename */
int mi_repair(MI_CHECK *param, register MI_INFO *info, int mi_repair(MI_CHECK *param, register MI_INFO *info,
char * name, int rep_quick) char * name, int rep_quick, my_bool no_copy_stat)
{ {
int error,got_error; int error,got_error;
ha_rows start_records,new_header_length; ha_rows start_records,new_header_length;
...@@ -1726,6 +1726,11 @@ int mi_repair(MI_CHECK *param, register MI_INFO *info, ...@@ -1726,6 +1726,11 @@ int mi_repair(MI_CHECK *param, register MI_INFO *info,
/* Replace the actual file with the temporary file */ /* Replace the actual file with the temporary file */
if (new_file >= 0) if (new_file >= 0)
{ {
myf flags= 0;
if (param->testflag & T_BACKUP_DATA)
flags |= MY_REDEL_MAKE_BACKUP;
if (no_copy_stat)
flags |= MY_REDEL_NO_COPY_STAT;
mysql_file_close(new_file, MYF(0)); mysql_file_close(new_file, MYF(0));
info->dfile=new_file= -1; info->dfile=new_file= -1;
/* /*
...@@ -1744,8 +1749,7 @@ int mi_repair(MI_CHECK *param, register MI_INFO *info, ...@@ -1744,8 +1749,7 @@ int mi_repair(MI_CHECK *param, register MI_INFO *info,
info->s->file_map= NULL; info->s->file_map= NULL;
} }
if (change_to_newfile(share->data_file_name, MI_NAME_DEXT, DATA_TMP_EXT, if (change_to_newfile(share->data_file_name, MI_NAME_DEXT, DATA_TMP_EXT,
(param->testflag & T_BACKUP_DATA ? flags) ||
MYF(MY_REDEL_MAKE_BACKUP): MYF(0))) ||
mi_open_datafile(info,share,name,-1)) mi_open_datafile(info,share,name,-1))
got_error=1; got_error=1;
...@@ -1933,7 +1937,8 @@ int flush_blocks(MI_CHECK *param, KEY_CACHE *key_cache, File file) ...@@ -1933,7 +1937,8 @@ int flush_blocks(MI_CHECK *param, KEY_CACHE *key_cache, File file)
/* Sort index for more efficent reads */ /* Sort index for more efficent reads */
int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name) int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name,
my_bool no_copy_stat)
{ {
reg2 uint key; reg2 uint key;
reg1 MI_KEYDEF *keyinfo; reg1 MI_KEYDEF *keyinfo;
...@@ -2004,7 +2009,7 @@ int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name) ...@@ -2004,7 +2009,7 @@ int mi_sort_index(MI_CHECK *param, register MI_INFO *info, char * name)
share->kfile = -1; share->kfile = -1;
(void) mysql_file_close(new_file, MYF(MY_WME)); (void) mysql_file_close(new_file, MYF(MY_WME));
if (change_to_newfile(share->index_file_name, MI_NAME_IEXT, INDEX_TMP_EXT, if (change_to_newfile(share->index_file_name, MI_NAME_IEXT, INDEX_TMP_EXT,
MYF(0)) || no_copy_stat ? MYF(MY_REDEL_NO_COPY_STAT) : MYF(0)) ||
mi_open_keyfile(share)) mi_open_keyfile(share))
goto err2; goto err2;
info->lock_type= F_UNLCK; /* Force mi_readinfo to lock */ info->lock_type= F_UNLCK; /* Force mi_readinfo to lock */
...@@ -2209,6 +2214,8 @@ int filecopy(MI_CHECK *param, File to,File from,my_off_t start, ...@@ -2209,6 +2214,8 @@ int filecopy(MI_CHECK *param, File to,File from,my_off_t start,
info MyISAM handler to repair info MyISAM handler to repair
name Name of table (for warnings) name Name of table (for warnings)
rep_quick set to <> 0 if we should not change data file rep_quick set to <> 0 if we should not change data file
no_copy_stat Don't copy file stats from old to new file,
assume that new file was created with correct stats
RESULT RESULT
0 ok 0 ok
...@@ -2216,7 +2223,7 @@ int filecopy(MI_CHECK *param, File to,File from,my_off_t start, ...@@ -2216,7 +2223,7 @@ int filecopy(MI_CHECK *param, File to,File from,my_off_t start,
*/ */
int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info, int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info,
const char * name, int rep_quick) const char * name, int rep_quick, my_bool no_copy_stat)
{ {
int got_error; int got_error;
uint i; uint i;
...@@ -2543,11 +2550,15 @@ int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info, ...@@ -2543,11 +2550,15 @@ int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info,
/* Replace the actual file with the temporary file */ /* Replace the actual file with the temporary file */
if (new_file >= 0) if (new_file >= 0)
{ {
myf flags= 0;
if (param->testflag & T_BACKUP_DATA)
flags |= MY_REDEL_MAKE_BACKUP;
if (no_copy_stat)
flags |= MY_REDEL_NO_COPY_STAT;
mysql_file_close(new_file, MYF(0)); mysql_file_close(new_file, MYF(0));
info->dfile=new_file= -1; info->dfile=new_file= -1;
if (change_to_newfile(share->data_file_name,MI_NAME_DEXT, DATA_TMP_EXT, if (change_to_newfile(share->data_file_name,MI_NAME_DEXT, DATA_TMP_EXT,
(param->testflag & T_BACKUP_DATA ? flags) ||
MYF(MY_REDEL_MAKE_BACKUP): MYF(0))) ||
mi_open_datafile(info,share,name,-1)) mi_open_datafile(info,share,name,-1))
got_error=1; got_error=1;
} }
...@@ -2595,6 +2606,8 @@ int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info, ...@@ -2595,6 +2606,8 @@ int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info,
info MyISAM handler to repair info MyISAM handler to repair
name Name of table (for warnings) name Name of table (for warnings)
rep_quick set to <> 0 if we should not change data file rep_quick set to <> 0 if we should not change data file
no_copy_stat Don't copy file stats from old to new file,
assume that new file was created with correct stats
DESCRIPTION DESCRIPTION
Same as mi_repair_by_sort but do it multithreaded Same as mi_repair_by_sort but do it multithreaded
...@@ -2629,7 +2642,7 @@ int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info, ...@@ -2629,7 +2642,7 @@ int mi_repair_by_sort(MI_CHECK *param, register MI_INFO *info,
*/ */
int mi_repair_parallel(MI_CHECK *param, register MI_INFO *info, int mi_repair_parallel(MI_CHECK *param, register MI_INFO *info,
const char * name, int rep_quick) const char * name, int rep_quick, my_bool no_copy_stat)
{ {
int got_error; int got_error;
uint i,key, total_key_length, istep; uint i,key, total_key_length, istep;
...@@ -3076,11 +3089,15 @@ int mi_repair_parallel(MI_CHECK *param, register MI_INFO *info, ...@@ -3076,11 +3089,15 @@ int mi_repair_parallel(MI_CHECK *param, register MI_INFO *info,
/* Replace the actual file with the temporary file */ /* Replace the actual file with the temporary file */
if (new_file >= 0) if (new_file >= 0)
{ {
myf flags= 0;
if (param->testflag & T_BACKUP_DATA)
flags |= MY_REDEL_MAKE_BACKUP;
if (no_copy_stat)
flags |= MY_REDEL_NO_COPY_STAT;
mysql_file_close(new_file, MYF(0)); mysql_file_close(new_file, MYF(0));
info->dfile=new_file= -1; info->dfile=new_file= -1;
if (change_to_newfile(share->data_file_name, MI_NAME_DEXT, DATA_TMP_EXT, if (change_to_newfile(share->data_file_name, MI_NAME_DEXT, DATA_TMP_EXT,
(param->testflag & T_BACKUP_DATA ? flags) ||
MYF(MY_REDEL_MAKE_BACKUP): MYF(0))) ||
mi_open_datafile(info,share,name,-1)) mi_open_datafile(info,share,name,-1))
got_error=1; got_error=1;
} }
......
/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. /* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
...@@ -993,14 +993,18 @@ static int myisamchk(MI_CHECK *param, char * filename) ...@@ -993,14 +993,18 @@ static int myisamchk(MI_CHECK *param, char * filename)
info->s->state.key_map, info->s->state.key_map,
param->force_sort)) param->force_sort))
{ {
/*
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.
*/
if (param->testflag & T_REP_BY_SORT) if (param->testflag & T_REP_BY_SORT)
error=mi_repair_by_sort(param,info,filename,rep_quick); error= mi_repair_by_sort(param, info, filename, rep_quick, FALSE);
else else
error=mi_repair_parallel(param,info,filename,rep_quick); error= mi_repair_parallel(param, info, filename, rep_quick, FALSE);
state_updated=1; state_updated=1;
} }
else if (param->testflag & T_REP_ANY) else if (param->testflag & T_REP_ANY)
error=mi_repair(param, info,filename,rep_quick); error= mi_repair(param, info, filename, rep_quick, FALSE);
} }
if (!error && param->testflag & T_SORT_RECORDS) if (!error && param->testflag & T_SORT_RECORDS)
{ {
...@@ -1040,12 +1044,12 @@ static int myisamchk(MI_CHECK *param, char * filename) ...@@ -1040,12 +1044,12 @@ static int myisamchk(MI_CHECK *param, char * filename)
{ {
if (param->verbose) if (param->verbose)
puts("Table had a compressed index; We must now recreate the index"); puts("Table had a compressed index; We must now recreate the index");
error=mi_repair_by_sort(param,info,filename,1); error= mi_repair_by_sort(param, info, filename, 1, FALSE);
} }
} }
} }
if (!error && param->testflag & T_SORT_INDEX) if (!error && param->testflag & T_SORT_INDEX)
error=mi_sort_index(param,info,filename); error= mi_sort_index(param, info, filename, FALSE);
if (!error) if (!error)
share->state.changed&= ~(STATE_CHANGED | STATE_CRASHED | share->state.changed&= ~(STATE_CHANGED | STATE_CRASHED |
STATE_CRASHED_ON_REPAIR); STATE_CRASHED_ON_REPAIR);
......
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