Package: mysql Version: 3.23.49-8.9 Severity: critical Tags: security patch woody
Hello [in copy to [EMAIL PROTECTED] After the packages in unstable and testing were fixed by uploading 4.0.24-1 and 4.1.10a-1 I took a look at the Woody packages and found them vulnerable, too. Sergei Golubchik <[EMAIL PROTECTED]> provided a reference to the 4.0 patch http://mysql.bkbits.net:8080/mysql-4.0/[EMAIL PROTECTED] which, with some minor modifications, was applicable to the 3.23 source tree. I afterwards verified the three prove of concept examples given by Stefano and they did at least not work any longer as they did with the unpatched version. http://archives.neohapsis.com/archives/vulnwatch/2005-q1/0082.html http://archives.neohapsis.com/archives/vulnwatch/2005-q1/0083.html http://archives.neohapsis.com/archives/vulnwatch/2005-q1/0084.html You can find a proposed upload for stable-security on http://www.lathspell.de/linux/debian/mysql/woody/ To verify the patches the directory single-patches/ contains the original patch splitted up to 11 individual files all on their original version and after I adjusted the patch. The concatenation of the p*_new.diff snippets toghether with some comments and a diff to generate the new changelog entry is in the directory what-i-applied/. The complete and adjusted patch was copied to debian/patches/SECURITY__CAN-2005-0709....diff as reference after I applied it. For the patches itself I did some minor checks i.e. looked if there are occurances of O_TRUNC flags or my_create() functions that were not replaced. But I would sleep better if somebody else would look over it again as I'm getting tired :) hope that helps, -christian- P.S.: Security Team, Woody's mysql has also another, although very minor security problem for which Sean Finney recently backported a patch, you might want to take a look at bug #296674 [CAN-2004-0957] -- System Information: Debian Release: 3.1 Architecture: amd64 (x86_64) Kernel: Linux 2.6.10-9-amd64-k8 Locale: LANG=de_DE, LC_CTYPE=de_DE (charmap=ISO-8859-1) (ignored: LC_ALL set to de_DE)
# # * Stefano Di Paola found the following vulnerabilities: # - Remote authenticated users with INSERT and DELETE privileges could # execute arbitrary code by using CREATE FUNCTION to access libc calls, # as demonstrated byusing strcat, on_exit, and exit. [CAN-2005-0709] # - Remote authenticated users with INSERT and DELETE privileges could # bypass library path restrictions and execute arbitrary libraries by # using INSERT INTO to modify the mysql.func table, which is processed # by the udf_init function. [CAN-2005-0710] # - Predictable file names were used when creating temporary tables, which # allowed local users with CREATE TEMPORARY TABLE privileges to overwrite # arbitrary files via a symlink attack. [CAN-2005-0711] # # The patch is a backported version of MySQLs original patch for 4.0 which # was available at: # http://mysql.bkbits.net:8080/mysql-4.0/[EMAIL PROTECTED] # # The following is a quotation of the upstream patch comments: # # > This is a BitKeeper generated diff -Nru style patch. # > # > ChangeSet # > 2005/03/03 19:51:29+01:00 [EMAIL PROTECTED] # > Fixes for bugs reported by Stefano Di Paola ([EMAIL PROTECTED]) # > # > include/my_global.h # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +3 -0 # > O_NOFOLLOW # > # > isam/create.c # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +3 -2 # > create table files with O_EXCL|O_NOFOLLOW # > # > merge/mrg_create.c # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +1 -1 # > create table files with O_EXCL|O_NOFOLLOW # > # > myisam/mi_create.c # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +8 -8 # > create files of temporary tables with O_EXCL|O_NOFOLLOW # > # > myisammrg/myrg_create.c # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +1 -1 # > create table files with O_EXCL|O_NOFOLLOW # > # > mysys/mf_tempfile.c # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +4 -4 # > create temporary files with O_EXCL|O_NOFOLLOW # > # > sql/ha_myisam.cc # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +11 -7 # > let mi_create know if the table is TEMPORARY # > # > sql/mysql_priv.h # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +1 -1 # > --allow_suspicious_udfs # > # > sql/mysqld.cc # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +9 -2 # > --allow_suspicious_udfs # > # > sql/share/english/errmsg.txt # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +1 -1 # > typo # > # > sql/sql_udf.cc # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +67 -31 # > --allow_suspicious_udfs # > don't allow xxx() udf without any of xxx_init/deinit/add/reset # > check paths when loading from mysql.func # > # > sql/table.cc # > 2005/03/03 19:51:26+01:00 [EMAIL PROTECTED] +5 -1 # > create frm of temporary table with O_EXCL|O_NOFOLLOW # --- a/include/my_global.h 2005-03-17 23:49:22.272318000 +0100 +++ b/include/my_global.h 2005-03-17 23:49:49.135092659 +0100 @@ -395,6 +395,9 @@ #ifndef O_SHORT_LIVED #define O_SHORT_LIVED 0 #endif +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif /* #define USE_RECORD_LOCK */ --- a/isam/create.c 2002-02-14 18:30:15.000000000 +0100 +++ b/isam/create.c 2005-03-17 23:49:51.912655694 +0100 @@ -58,13 +58,14 @@ base_pos=512; /* Enough for N_STATE_INFO */ bzero((byte*) &share,sizeof(share)); if ((file = my_create(fn_format(buff,name,"",N_NAME_IEXT,4),0, - O_RDWR | O_TRUNC,MYF(MY_WME))) < 0) + O_RDWR | O_EXCL | O_NOFOLLOW,MYF(MY_WME))) < 0) goto err; errpos=1; VOID(fn_format(buff,name,"",N_NAME_DEXT,2+4)); if (!(flags & HA_DONT_TOUCH_DATA)) { - if ((dfile = my_create(buff,0,O_RDWR | O_TRUNC,MYF(MY_WME))) < 0) + if ((dfile = my_create(buff,0,O_RDWR | O_EXCL | O_NOFOLLOW, + MYF(MY_WME))) < 0) goto err; errpos=2; } --- a/merge/create.c 2002-02-14 18:30:15.000000000 +0100 +++ b/merge/create.c 2005-03-17 23:49:54.377267964 +0100 @@ -33,7 +33,7 @@ errpos=0; if ((file = my_create(fn_format(buff,name,"",MRG_NAME_EXT,4),0, - O_RDWR | O_TRUNC,MYF(MY_WME))) < 0) + O_RDWR | O_EXCL | O_NOFOLLOW,MYF(MY_WME))) < 0) goto err; errpos=1; if (table_names) --- a/myisam/mi_create.c 2002-02-14 18:30:17.000000000 +0100 +++ b/myisam/mi_create.c 2005-03-18 00:01:52.892241358 +0100 @@ -37,7 +37,7 @@ { register uint i,j; File dfile,file; - int errpos,save_errno; + int errpos,save_errno, create_mode= O_RDWR | O_TRUNC; uint fields,length,max_key_length,packed,pointer, key_length,info_length,key_segs,options,min_key_length_skipp, base_pos,varchar_count,long_varchar_count,varchar_length, @@ -170,7 +170,10 @@ min_pack_length+=varchar_length+2*varchar_count; } if (flags & HA_CREATE_TMP_TABLE) + { options|= HA_OPTION_TMP_TABLE; + create_mode|= O_EXCL | O_NOFOLLOW; + } if (flags & HA_CREATE_CHECKSUM || (options & HA_OPTION_CHECKSUM)) { options|= HA_OPTION_CHECKSUM; @@ -468,8 +471,8 @@ if (! (flags & HA_DONT_TOUCH_DATA)) share.state.create_time= (long) time((time_t*) 0); - if ((file = my_create(fn_format(buff,name,"",MI_NAME_IEXT,4),0, - O_RDWR | O_TRUNC,MYF(MY_WME))) < 0) + if ((file = my_create(fn_format(buff,name,"",MI_NAME_IEXT,4),0, create_mode, + MYF(MY_WME))) < 0) goto err; errpos=1; VOID(fn_format(buff,name,"",MI_NAME_DEXT,2+4)); @@ -478,7 +481,7 @@ #ifdef USE_RAID if (share.base.raid_type) { - if ((dfile=my_raid_create(buff,0,O_RDWR | O_TRUNC, + if ((dfile=my_raid_create(buff, 0, create_mode, share.base.raid_type, share.base.raid_chunks, share.base.raid_chunksize, @@ -487,7 +490,7 @@ } else #endif - if ((dfile = my_create(buff,0,O_RDWR | O_TRUNC,MYF(MY_WME))) < 0) + if ((dfile = my_create(buff, 0, create_mode, MYF(MY_WME))) < 0) goto err; errpos=3; --- a/myisammrg/myrg_create.c 2002-02-14 18:30:18.000000000 +0100 +++ b/myisammrg/myrg_create.c 2005-03-18 00:03:27.279412105 +0100 @@ -33,7 +33,7 @@ errpos=0; if ((file = my_create(fn_format(buff,name,"",MYRG_NAME_EXT,4),0, - O_RDWR | O_TRUNC,MYF(MY_WME))) < 0) + O_RDWR | O_EXCL | O_NOFOLLOW,MYF(MY_WME))) < 0) goto err; errpos=1; if (table_names) --- a/mysys/mf_tempfile.c 2002-02-14 18:30:17.000000000 +0100 +++ b/mysys/mf_tempfile.c 2005-03-18 00:03:48.154143325 +0100 @@ -71,7 +71,7 @@ { strmake(to,res,FN_REFLEN-1); (*free)(res); - file=my_create(to,0, mode, MyFlags); + file=my_create(to,0, mode | O_EXCL | O_NOFOLLOW, MyFlags); } environ=old_env; } @@ -82,7 +82,7 @@ { strmake(to,res,FN_REFLEN-1); (*free)(res); - file=my_create(to, 0, mode, MyFlags); + file=my_create(to, 0, mode | O_EXCL | O_NOFOLLOW, MyFlags); } #elif defined(HAVE_MKSTEMP) { @@ -143,7 +143,7 @@ strmake(to,res,FN_REFLEN-1); (*free)(res); file=my_create(to,0, - (int) (O_RDWR | O_BINARY | O_TRUNC | + (int) (O_RDWR | O_BINARY | O_TRUNC | O_EXCL | O_NOFOLLOW | O_TEMPORARY | O_SHORT_LIVED), MYF(MY_WME)); @@ -186,7 +186,7 @@ } (void) strmov(end_pos,TMP_EXT); file=my_create(to,0, - (int) (O_RDWR | O_BINARY | O_TRUNC | + (int) (O_RDWR | O_BINARY | O_TRUNC | O_EXCL | O_NOFOLLOW | O_TEMPORARY | O_SHORT_LIVED), MYF(MY_WME)); } --- a/sql/ha_myisam.cc 2002-02-14 18:30:24.000000000 +0100 +++ b/sql/ha_myisam.cc 2005-03-18 00:10:03.435368039 +0100 @@ -931,7 +931,7 @@ HA_CREATE_INFO *info) { int error; - uint i,j,recpos,minpos,fieldpos,temp_length,length; + uint i,j,recpos,minpos,fieldpos,temp_length,length, create_flags; bool found_auto_increment=0; enum ha_base_keytype type; char buff[FN_REFLEN]; @@ -1096,16 +1096,20 @@ create_info.raid_chunks=info->raid_chunks ? info->raid_chunks : RAID_DEFAULT_CHUNKS; create_info.raid_chunksize=info->raid_chunksize ? info->raid_chunksize : RAID_DEFAULT_CHUNKSIZE; + if (info->options & HA_LEX_CREATE_TMP_TABLE) + create_flags|= HA_CREATE_TMP_TABLE; + if (options & HA_OPTION_PACK_RECORD) + create_flags|= HA_PACK_RECORD; + if (options & HA_OPTION_CHECKSUM) + create_flags|= HA_CREATE_CHECKSUM; + if (options & HA_OPTION_DELAY_KEY_WRITE) + create_flags|= HA_CREATE_DELAY_KEY_WRITE; + error=mi_create(fn_format(buff,name,"","",2+4+16), form->keys,keydef, (uint) (recinfo_pos-recinfo), recinfo, 0, (MI_UNIQUEDEF*) 0, - &create_info, - (((options & HA_OPTION_PACK_RECORD) ? HA_PACK_RECORD : 0) | - ((options & HA_OPTION_CHECKSUM) ? HA_CREATE_CHECKSUM : 0) | - ((options & HA_OPTION_DELAY_KEY_WRITE) ? - HA_CREATE_DELAY_KEY_WRITE : 0))); - + &create_info, create_flags); my_free((gptr) recinfo,MYF(0)); DBUG_RETURN(error); --- a/sql/mysql_priv.h 2002-02-14 18:30:26.000000000 +0100 +++ b/sql/mysql_priv.h 2005-03-18 00:15:03.379379115 +0100 @@ -525,7 +525,8 @@ COND_slave_stopped, COND_slave_start; extern pthread_attr_t connection_attrib; extern bool opt_endinfo, using_udf_functions, locked_in_memory, - opt_using_transactions, use_temp_pool, opt_local_infile; + opt_using_transactions, use_temp_pool, opt_local_infile, + opt_allow_suspicious_udfs; extern char f_fyllchar; extern ulong ha_read_count, ha_write_count, ha_delete_count, ha_update_count, ha_read_key_count, ha_read_next_count, ha_read_prev_count, --- a/sql/mysqld.cc 2002-02-14 18:30:15.000000000 +0100 +++ b/sql/mysqld.cc 2005-03-18 00:25:13.190686788 +0100 @@ -221,7 +221,7 @@ opt_myisam_log=0, opt_large_files=sizeof(my_off_t) > 4; bool opt_sql_bin_update = 0, opt_log_slave_updates = 0, opt_safe_show_db=0, - opt_safe_user_create=0; + opt_safe_user_create=0, opt_allow_suspicious_udfs; FILE *bootstrap_file=0; int segfaulted = 0; // ensure we do not enter SIGSEGV handler twice extern MASTER_INFO glob_mi; @@ -2614,11 +2614,13 @@ OPT_SKIP_STACK_TRACE, OPT_SKIP_SYMLINKS, OPT_MAX_BINLOG_DUMP_EVENTS, OPT_SPORADIC_BINLOG_DUMP_FAIL, OPT_SAFE_USER_CREATE, OPT_SQL_MODE, - OPT_SLAVE_SKIP_ERRORS, OPT_LOCAL_INFILE + OPT_SLAVE_SKIP_ERRORS, OPT_LOCAL_INFILE, + OPT_ALLOW_SUSPICIOUS_UDFS }; static struct option long_options[] = { {"ansi", no_argument, 0, 'a'}, + {"allow-suspicious-udfs", no_argument, 0, (int) OPT_ALLOW_SUSPICIOUS_UDFS}, {"basedir", required_argument, 0, 'b'}, #ifdef HAVE_BERKELEY_DB {"bdb-home", required_argument, 0, (int) OPT_BDB_HOME}, @@ -3195,6 +3197,11 @@ printf("Usage: %s [OPTIONS]\n", my_progname); puts("\n\ --ansi Use ANSI SQL syntax instead of MySQL syntax\n\ + --allow-suspicious-udfs\n\ + Allows to use UDF's consisting of only one symbol\n\ + xxx() without corresponing xxx_init() or xxx_deinit().\n\ + That also means that one can load any function from\n\ + any library, for example exit() from libc.so\n\ -b, --basedir=path Path to installation directory. All paths are\n\ usually resolved relative to this\n\ --big-tables Allow big result sets by saving all temporary sets\n\ --- a/sql/share/english/errmsg.txt 2002-02-14 18:51:40.000000000 +0100 +++ b/sql/share/english/errmsg.txt 2005-03-18 00:26:13.891147821 +0100 @@ -128,7 +128,7 @@ "No paths allowed for shared library", "Function '%-.64s' already exist", "Can't open shared library '%-.64s' (errno: %d %-.64s)", -"Can't find function '%-.64s' in library'", +"Can't find function '%-.64s' in library", "Function '%-.64s' is not defined", "Host '%-.64s' is blocked because of many connection errors. Unblock with 'mysqladmin flush-hosts'", "Host '%-.64s' is not allowed to connect to this MySQL server", --- a/sql/sql_udf.cc 2002-02-14 18:30:22.000000000 +0100 +++ b/sql/sql_udf.cc 2005-03-18 00:30:05.890689505 +0100 @@ -75,29 +75,49 @@ static pthread_mutex_t THR_LOCK_udf; -static udf_func *add_udf(char *name, Item_result ret, char *dl, - Item_udftype typ); +static udf_func *add_udf(char *name, Item_result ret, + char *dl, Item_udftype typ); static void del_udf(udf_func *udf); static void *find_udf_dl(const char *dl); - -static void init_syms(udf_func *tmp) +static char *init_syms(udf_func *tmp, char *nm) { - char nm[MAX_FIELD_NAME+16],*end; + char *end; + + if (!((tmp->func= dlsym(tmp->dlhandle, tmp->name)))) + return tmp->name; - tmp->func = dlsym(tmp->dlhandle, tmp->name); end=strmov(nm,tmp->name); - (void) strmov(end,"_init"); - tmp->func_init = dlsym(tmp->dlhandle, nm); - (void) strmov(end,"_deinit"); - tmp->func_deinit = dlsym(tmp->dlhandle, nm); + if (tmp->type == UDFTYPE_AGGREGATE) { - (void)strmov( end, "_reset" ); - tmp->func_reset = dlsym( tmp->dlhandle, nm ); - (void)strmov( end, "_add" ); - tmp->func_add = dlsym( tmp->dlhandle, nm ); + (void)strmov(end, "_reset"); + if (!((tmp->func_reset= dlsym(tmp->dlhandle, nm)))) + return nm; + (void)strmov(end, "_add"); + if (!((tmp->func_add= dlsym(tmp->dlhandle, nm)))) + return nm; + } + + (void) strmov(end,"_deinit"); + tmp->func_deinit= dlsym(tmp->dlhandle, nm); + + (void) strmov(end,"_init"); + tmp->func_init= dlsym(tmp->dlhandle, nm); + + /* + to prefent loading "udf" from, e.g. libc.so + let's ensure that at least one auxiliary symbol is defined + */ + if (!tmp->func_init && !tmp->func_deinit && tmp->type != UDFTYPE_AGGREGATE) + { + if (opt_allow_suspicious_udfs) + sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), nm); + else + return nm; } + + return 0; } static byte* get_hash_key(const byte *buff,uint *length, @@ -109,7 +129,7 @@ } /* -** Read all predeclared functions from [EMAIL PROTECTED] and accept all that +** Read all predeclared functions from mysql.func and accept all that ** can be used. */ @@ -151,7 +171,7 @@ if (open_tables(new_thd, &tables)) { DBUG_PRINT("error",("Can't open udf table")); - sql_print_error("Can't open mysql/func table"); + sql_print_error("Can't open mysql.func table. Please run the mysql_install_db script to create it."); close_thread_tables(new_thd); delete new_thd; DBUG_VOID_RETURN; @@ -169,10 +189,22 @@ if (table->fields >= 4) // New func table udftype=(Item_udftype) table->field[3]->val_int(); + /* + Ensure that the .dll doesn't have a path + This is done to ensure that only approved dll from the system + directories are used (to make this even remotely secure). + */ + if (strchr(dl_name, '/') || strlen(name) > NAME_LEN) + { + sql_print_error("Invalid row in mysql.func table for function '%.64s'", + name); + continue; + } + if (!(tmp = add_udf(name,(Item_result) table->field[1]->val_int(), dl_name, udftype))) { - sql_print_error("Can't alloc memory for udf function: name"); + sql_print_error("Can't alloc memory for udf function: '%.64s'", name); continue; } @@ -189,13 +221,15 @@ new_dl=1; } tmp->dlhandle = dl; - init_syms(tmp); - if (!tmp->func) { - sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), name); - del_udf(tmp); - if (new_dl) - dlclose(dl); + char buf[MAX_FIELD_NAME+16], *missing; + if ((missing= init_syms(tmp, buf))) + { + sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), missing); + del_udf(tmp); + if (new_dl) + dlclose(dl); + } } } if (error > 0) @@ -380,13 +414,15 @@ new_dl=1; } udf->dlhandle=dl; - init_syms(udf); - - if (udf->func == NULL) { - net_printf(&thd->net, ER_CANT_FIND_DL_ENTRY, udf->name); - goto err; + char buf[MAX_FIELD_NAME+16], *missing; + if ((missing= init_syms(udf, buf))) + { + net_printf(&thd->net, ER_CANT_FIND_DL_ENTRY, missing); + goto err; + } } + udf->name=strdup_root(&mem,udf->name); udf->dl=strdup_root(&mem,udf->dl); if (!udf->name || !udf->dl || @@ -402,7 +438,7 @@ u_d->func_reset=udf->func_reset; u_d->func_add=udf->func_add; - /* create entry in mysql/func table */ + /* create entry in mysql.func table */ bzero((char*) &tables,sizeof(tables)); tables.db= (char*) "mysql"; @@ -422,7 +458,7 @@ close_thread_tables(thd); if (error) { - net_printf(&thd->net, ER_ERROR_ON_WRITE, "[EMAIL PROTECTED]",error); + net_printf(&thd->net, ER_ERROR_ON_WRITE, "mysql.func",error); del_udf(u_d); goto err; } --- a/sql/table.cc 2002-02-14 18:30:24.000000000 +0100 +++ b/sql/table.cc 2005-03-18 00:38:47.911609320 +0100 @@ -945,6 +945,10 @@ uint key_length; ulong length; char fill[IO_SIZE]; + int create_flags= O_RDWR | O_TRUNC; + + if (create_info->options & HA_LEX_CREATE_TMP_TABLE) + create_flags|= O_EXCL | O_NOFOLLOW; #if SIZEOF_OFF_T > 4 /* Fix this in MySQL 4.0; The current limit is 4G rows (QQ) */ @@ -954,7 +958,7 @@ create_info->min_rows= ~(ulong) 0; #endif - if ((file=my_create(name,CREATE_MODE,O_RDWR | O_TRUNC,MYF(MY_WME))) >= 0) + if ((file= my_create(name, CREATE_MODE, create_flags, MYF(MY_WME))) >= 0) { bzero((char*) fileinfo,64); fileinfo[0]=(uchar) 254; fileinfo[1]= 1; fileinfo[2]= FRM_VER+1; // Header