Bug #4696 segfault in cmd-line-utils/libedit/history.c:history_save() (bundled libedit)
Submitted: 22 Jul 2004 13:33 Modified: 21 Aug 2004 23:44
Reporter: Sergey Kostyliov Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: Command-line Clients Severity:S3 (Non-critical)
Version:4.1.4 (bk snapshot from 20040718) OS:Linux (Linux (Gentoo v2004.1))
Assigned to: Sergei Golubchik CPU Architecture:Any

[22 Jul 2004 13:33] Sergey Kostyliov
Description:
cmd-line-utils/libedit/history.c:history_save() 
 
history_save(History *h, const char *fname) 
{ 
        FILE *fp; 
        HistEvent ev; 
        int i = 0, retval; 
        size_t len, max_size; 
        char *ptr; 
... 
        ptr = h_malloc(max_size = 1024); 
        for (retval = HLAST(h, &ev); 
            retval != -1; 
            retval = HPREV(h, &ev), i++) { 
                len = strlen(ev.str) * 4; 
                if (len >= max_size) { 
                        max_size = (len + 1023) & 1023; 
                        ptr = h_realloc(ptr, max_size); 
                } 
                (void) strvis(ptr, ev.str, VIS_WHITE); 
                (void) fprintf(fp, "%s\n", ev.str); 
        } 
        h_free(ptr); 
... 
} 
 
When strlen(ev.str) is large enough it will be possible that ptr 
(after h_realloc(ptr, max_size)) is lesser than ev.str. 
 

How to repeat:
rathamahata@dev mysql-4.1.4-20040718-gcc-3.4.1 $ 
LD_LIBRARY_PATH=./libmysql/.libs/:$LD_LIBRARY_PATH ./client/.libs/mysql 
--socket=/var/lib/mysql/4/mysql.sock 
Welcome to the MySQL monitor.  Commands end with ; or \g. 
Your MySQL connection id is 243 to server version: 4.1.3-beta-log 
 
Type 'help;' or '\h' for help. Type '\c' to clear the buffer. 
 
mysql> SELECT 
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
| 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa                                              
| 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
| 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 
| 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
1 row in set (0.00 sec) 
 
mysql> \q 
Segmentation fault 
rathamahata@dev mysql-4.1.4-20040718-gcc-3.4.1 $ 
 
 
 
rathamahata@dev mysql-4.1.4-20040718-gcc-3.4.1 $ 
LD_LIBRARY_PATH=./libmysql/.libs/:$LD_LIBRARY_PATH  gdb ./client/.libs/mysql 
GNU gdb 6.0 
Copyright 2003 Free Software Foundation, Inc. 
GDB is free software, covered by the GNU General Public License, and you are 
welcome to change it and/or distribute copies of it under certain conditions. 
Type "show copying" to see the conditions. 
There is absolutely no warranty for GDB.  Type "show warranty" for details. 
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library 
"/lib/libthread_db.so.1". 
 
(gdb) break history_save 
Breakpoint 1 at 0x8064c63: file history.c, line 637. 
(gdb) run --socket=/var/lib/mysql/4/mysql.sock 
Starting program: /var/tmp/mysql-4.1.4-20040718-gcc-3.4.1/client/.libs/mysql 
--socket=/var/lib/mysql/4/mysql.sock 
warning: Unable to find dynamic linker breakpoint function. 
GDB will be unable to debug shared library initializers 
and track explicitly loaded dynamic code. 
Welcome to the MySQL monitor.  Commands end with ; or \g. 
Your MySQL connection id is 245 to server version: 4.1.3-beta-log 
 
Type 'help;' or '\h' for help. Type '\c' to clear the buffer. 
 
mysql> SELECT 
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
| 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa                                              
| 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
| 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 
| 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
1 row in set (0.00 sec) 
 
mysql> \q 
 
Breakpoint 1, history_save (h=0x808a2a8, 
    fname=0x807a520 "/home/rathamahata/.mysql_history") at history.c:637 
637             int i = 0, retval; 
Current language:  auto; currently c 
(gdb) n 
641             if ((fp = fopen(fname, "w")) == NULL) 
(gdb) n 
644             (void) fchmod(fileno(fp), S_IRUSR|S_IWUSR); 
(gdb) n 
645             (void) fputs(hist_cookie, fp); 
(gdb) n 
646             ptr = h_malloc(max_size = 1024); 
(gdb) n 
647             for (retval = HLAST(h, &ev); 
(gdb) n 
650                     len = strlen(ev.str) * 4; 
(gdb) n 
651                     if (len >= max_size) { 
(gdb) print ev.str 
$1 = 0x808ab18 "SELECT \"", 'a' <repeats 192 times>... 
(gdb) print len/4 
$3 = 310  <<================  
(gdb) n 
652                             max_size = (len + 1023) & 1023; 
(gdb) n 
653                             ptr = h_realloc(ptr, max_size); 
(gdb) print max_size 
$4 = 215  <<================ 
(gdb) n 
655                     (void) strvis(ptr, ev.str, VIS_WHITE); 
(gdb) n 
656                     (void) fprintf(fp, "%s\n", ev.str); 
(gdb) n 
647             for (retval = HLAST(h, &ev); 
(gdb) n 
650                     len = strlen(ev.str) * 4; 
(gdb) n 
651                     if (len >= max_size) { 
(gdb) n 
655                     (void) strvis(ptr, ev.str, VIS_WHITE); 
(gdb) n 
656                     (void) fprintf(fp, "%s\n", ev.str); 
(gdb) n 
647             for (retval = HLAST(h, &ev); 
(gdb) n 
658             h_free(ptr); 
(gdb) n 
 
Program received signal SIGSEGV, Segmentation fault. 
0x401a3d12 in mallopt () from /lib/libc.so.6
[22 Jul 2004 14:03] Sergey Kostyliov
It's looks like that the current code in {net,open}BSD is also affected by this 
problem :(
[23 Jul 2004 2:38] Matthew Lord
I get a similar segfault in RH 9 Linux booty 2.4.21 #12 SMP Thu Aug 14 00:49:40 EDT 2003 i686 
i686 i386 GNU/Linux:

(gdb) Program received signal SIGSEGV, Segmentation fault.
Undefined command: "Program".  Try "help".
(gdb) 0x4027617d in _int_free () from /lib/libc.so.6
Undefined command: "0x4027617d".  Try "help".
(gdb) (gdb) bt
Undefined command: "".  Try "help".
(gdb) #0  0x4027617d in _int_free () from /lib/libc.so.6
(gdb) #1  0x40274fbc in free () from /lib/libc.so.6
(gdb) #2  0x08067136 in history_save ()
(gdb) #3  0x0806759e in history ()
(gdb) #4  0x0805f292 in write_history ()
(gdb) #5  0x080556de in mysql_end(int) ()
(gdb) #6  0x080554a1 in main ()
(gdb) #7  0x40217917 in __libc_start_main () from /lib/libc.so.6

I could not repeat this on OS X 10.3.4 
Darwin silverbullet 7.4.0 Darwin Kernel Version 7.4.0: Wed May 12 16:58:24 PDT 2004; root:xnu/
xnu-517.7.7.obj~7/RELEASE_PPC  Power Macintosh powerpc

I could not repeat this on Solaris 9
SunOS sunfire100b 5.9 Generic_112233-08 sun4u sparc SUNW,UltraAX-i2

Best Regards
[23 Jul 2004 14:17] Sergey Kostyliov
possible fix

Attachment: mysql-4.1.4-bug4696.patch (text/x-diff), 826 bytes.

[23 Jul 2004 14:18] Sergey Kostyliov
I think in case when mysql compiled using bundled libedit random memory overwrite  
bug is still possible even if there is no visible symptoms. I believe it's all depends on  
malloc()/realloc()/free() implementation on different systems. AFAICS this also applies  
to bsd libedit, at least netbsd,openbsd and freebsd (libedit from ports) are  
affected in theory (sorry, couldn't test this for myself here).  
  
Since char *ptr isn't really used anywhere  in  
cmd-line-utils/libedit/history.c:history_save() the simple fix is just to remove it.  
Possible patch is attached (see mysql-4.1.4-bug4696.patch).
[20 Aug 2004 0:29] Sergei Golubchik
this is an obvious typo:

			max_size = (len + 1023) & 1023;

of course it must be

			max_size = (len + 1023) & ~1023;

the initial value of max_size is 1024, and then it should be len rounded to the next n*1024 up.

fixed in 4.1.4
thanks for spotting this
[21 Aug 2004 18:45] Sergey Kostyliov
Otto Moerbeek <otto at drijf.net> 
has just pointed out that the: 
        max_size = (len + 1023) & ~1023; 
patch is not enough (see 
http://www.sigmasoft.com/cgi-bin/wilma_hiliter/openbsd-bugs/200408/msg00092.html) 
 
"... If  
len is a multiple of 1024,  
 
max_size = (len + 1023) & ~1023; 
 
wil not increase it. Should probably be 
 
max_size = (len + 1024) & ~1023;" 
 
It looks like his statement is correct and either his patch or something like: 
        http://www.sigmasoft.com/cgi-bin/wilma_hiliter/openbsd-bugs/200408/msg00096.html 
(which is a bit more intrusive but seems more self documented to me) is needed.
[21 Aug 2004 23:44] Sergei Golubchik
you're right. I'll account for '\0'
(and you I think your approach with explicit +1 is better :)