Bug #63363 mysql_prune_stmt_list causes data corruption
Submitted: 21 Nov 2011 17:09 Modified: 16 Feb 2017 11:03
Reporter: Matthieu Lochegnies Email Updates:
Status: Duplicate Impact on me:
None 
Category:MySQL Server: C API (client library) Severity:S3 (Non-critical)
Version:5.5, 5.1, 5.6.4 OS:Any
Assigned to: CPU Architecture:Any
Tags: Contribution
Triage: Needs Triage: D1 (Critical)

[21 Nov 2011 17:09] Matthieu Lochegnies
Description:
In an application using prepared statements, it may happen that the application gets disconnected (after its idle timeout) just before it tries to prepare a new statement.
In this case, 'mysql_prune_stmt_list' tries to detach all previously prepared statements, only keeping those at the 'MYSQL_STMT_INIT_DONE' state.
When the 'element' is moved to the 'pruned_list', its 'prev' and 'next' pointers are udpated, invalidating the current statement walk.

How to repeat:
1. Set a short session timeout ("SET session wait_timeout=10" for example)
2. Prepare a statement (with mysql_stmt_init and mysql_stmt_prepare)
3. Wait for the timeout to happen
4. Prepare a new statement - it fails with CR_SERVER_GONE
5. Close the new statement (mysql_stmt_close)
6. Close the first statement

In my production environment, it has caused several segmentation faults.
With the test case, valgrind complains.

Suggested fix:
Apply the following patch on client.c:
--- mysql-5.5.18.orig/sql-common/client.c       2011-11-21 18:06:36.895327989 +0100
+++ mysql-5.5.18/sql-common/client.c    2011-11-21 18:07:44.511213214 +0100
@@ -3668,8 +3668,9 @@
   LIST *element= mysql->stmts;
   LIST *pruned_list= 0;
 
-  for (; element; element= element->next)
+  while (element)
   {
+    LIST *next= element->next;
     MYSQL_STMT *stmt= (MYSQL_STMT *) element->data;
     if (stmt->state != MYSQL_STMT_INIT_DONE)
     {
@@ -3680,8 +3681,11 @@
     }
     else
     {
+      element->prev= 0;
+      element->next= 0;
       pruned_list= list_add(pruned_list, element);
     }
+    element= next;
   }
 
   mysql->stmts= pruned_list;
[21 Nov 2011 17:12] Matthieu Lochegnies
Sample code to produce the default: run with valgrind

Attachment: mysql_bad_prune.c (text/x-c), 1.11 KiB.

[21 Nov 2011 17:13] Matthieu Lochegnies
Proposed fix

Attachment: mysql_bad_prune.patch (application/octet-stream, text), 730 bytes.

[22 Nov 2011 8:55] Matthieu Lochegnies
Further explanation:
Before the second mysql_stmt_init, we have in mysql->stmts the list:
stmt2(root)<->stmt1

After mysql_prune_stmt_list, we have in mysql->stmts:
stmt2(root)

But stmt1 has not been modified; its previous node is still stmt2.

When stmt2 is closed, its structure is freed; when stmt1 is closed, its list pointers are updated, and write in the freed area on stmt2.

If this area has been reallocated meanwhile (in a multi-threaded app), it can cause data corruption.
[22 Nov 2011 8:58] Matthieu Lochegnies
Better fix

Attachment: mysql_bad_prune.patch (application/octet-stream, text), 942 bytes.

[22 Nov 2011 9:11] Matthieu Lochegnies
Seems like the right category is "C API", rather than "Connector/C".
[19 Dec 2011 18:53] Sveta Smirnova
Thank you for the report.

Memory leak verified as described.

Output of valgrind in my environment for 5.6.4:

[sveta@delly bug63363]$ valgrind --leak-check=full ./mysql_bad_prune 
==5368== Memcheck, a memory error detector
==5368== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==5368== Using Valgrind-3.6.0 and LibVEX; rerun with -h for copyright info
==5368== Command: ./mysql_bad_prune
==5368== 
cannot prepare statement2: Lost connection to MySQL server during query
==5368== Invalid write of size 8
==5368==    at 0x4C83A12: list_delete (list.c:48)
==5368==    by 0x4C49146: mysql_stmt_close (libmysql.c:4647)
==5368==    by 0x400B28: main (in /home/sveta/src/bugs/bug63363/mysql_bad_prune)
==5368==  Address 0x5223b40 is 64 bytes inside a block of size 816 free'd
==5368==    at 0x4A0595D: free (vg_replace_malloc.c:366)
==5368==    by 0x4C89556: my_free (my_malloc.c:130)
==5368==    by 0x4C492CF: mysql_stmt_close (libmysql.c:4679)
==5368==    by 0x400B1C: main (in /home/sveta/src/bugs/bug63363/mysql_bad_prune)
==5368== 
==5368== 
==5368== HEAP SUMMARY:
==5368==     in use at exit: 63,274 bytes in 26 blocks
==5368==   total heap usage: 91 allocs, 65 frees, 134,677 bytes allocated
==5368== 
==5368== 1,362 (1,272 direct, 90 indirect) bytes in 1 blocks are definitely lost in loss record 11 of 15
==5368==    at 0x4A05FDE: malloc (vg_replace_malloc.c:236)
==5368==    by 0x4C891E1: my_malloc (my_malloc.c:38)
==5368==    by 0x4C4C338: mysql_init (client.c:1617)
==5368==    by 0x400975: main (in /home/sveta/src/bugs/bug63363/mysql_bad_prune)
==5368== 
==5368== LEAK SUMMARY:
==5368==    definitely lost: 1,272 bytes in 1 blocks
==5368==    indirectly lost: 90 bytes in 5 blocks
==5368==      possibly lost: 0 bytes in 0 blocks
==5368==    still reachable: 61,912 bytes in 20 blocks
==5368==         suppressed: 0 bytes in 0 blocks
==5368== Reachable blocks (those to which a pointer was found) are not shown.
==5368== To see them, rerun with: --leak-check=full --show-reachable=yes
==5368== 
==5368== For counts of detected and suppressed errors, rerun with: -v
==5368== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 6 from 6)
[5 Sep 2013 12:18] Cameron Smith
I tripped over this bug after digging into an issue with mysql_stmt_close causing a crash when closing prepared statements after losing connection with the database.  It's clear that this function (mysql_prune_stmt) is broken and doesn't really fix the original bug #43560 from 2009.  The last patch attached to the bug seems to work.  Why hasn't it been accepted yet?
[12 Dec 2013 9:24] jack chen
This bug still exist in Connector/C 6.1.2.
[16 Feb 2017 11:03] Nisha Gopalakrishnan
A duplicate of Bug#70429