CVE-2016-1251: Use After Free (CWE-416)
value: There is a vulnerability of type use-after-free affecting DBD::mysql (aka DBD-mysql or the Database Interface (DBI) MySQL driver for Perl) 3.x and 4.x before 4.041 when used with mysql_server_prepare=1.
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
CVSS v3 base score: 8.1
Published: 2016-11-29T20:59Z
Repository: https://github.com/perl5-dbi/DBD-mysql
Language: Other
Files changed: dbdimp.c, t/40server_prepare_crash.t
Commit date: 2016-11-28 21:16:27 +0100
Commit message: Fix use-after-free for repeated fetchrow_arrayref calls when mysql_server_prepare=1

Function dbd_st_fetch() via Renew() can reallocate output buffer for
mysql_stmt_fetch() call. But it does not update pointer to that buffer in
imp_sth->stmt structure initialized by mysql_stmt_bind_result() function.
That leads to use-after-free in any mysql function which access
imp_sth->stmt structure (e.g. mysql_stmt_fetch()).

This patch fix this problem and properly updates pointer in imp_sth->stmt
structure after Renew() call.

Test 40server_prepare_crash.t is extended to check for that use-after-free
crash.

Diff: diff --git a/dbdimp.c b/dbdimp.c
index f8055bc6..9b8b3134 100644
--- a/dbdimp.c
+++ b/dbdimp.c
@@ -4048,10 +4048,12 @@ process:
               i, fbh->length, fbh->error);
 
           Renew(fbh->data, fbh->length, char);
           buffer->buffer_length= fbh->length;
           buffer->buffer= (char *) fbh->data;
+          imp_sth->stmt->bind[i].buffer_length = fbh->length;
+          imp_sth->stmt->bind[i].buffer = (char *)fbh->data;
 
           if (DBIc_TRACE_LEVEL(imp_xxh) >= 2) {
             int j;
             int m = MIN(*buffer->length, buffer->buffer_length);
             char *ptr = (char*)buffer->buffer;
diff --git a/t/40server_prepare_crash.t b/t/40server_prepare_crash.t
index 6db3207d..df6e2b3d 100644
--- a/t/40server_prepare_crash.t
+++ b/t/40server_prepare_crash.t
@@ -8,15 +8,26 @@ use vars qw($test_dsn $test_user $test_password);
 require "t/lib.pl";
 
 my $dbh = eval { DBI->connect($test_dsn, $test_user, $test_password, { PrintError => 1, RaiseError => 1, AutoCommit => 0, mysql_server_prepare => 1, mysql_server_prepare_disable_fallback => 1 }) };
 plan skip_all => "no database connection" if $@ or not $dbh;
 
-plan tests => 17;
+plan tests => 39;
 
-ok $dbh->do("CREATE TEMPORARY TABLE t (i INTEGER NOT NULL, n TEXT)");
+my $sth;
 
-ok my $sth = $dbh->prepare("SELECT * FROM t WHERE i=? AND n=?");
+ok $dbh->do("CREATE TEMPORARY TABLE t (i INTEGER NOT NULL, n LONGBLOB)");
+
+ok $sth = $dbh->prepare("INSERT INTO t(i, n) VALUES(?, ?)");
+ok $sth->execute(1, "x" x 10);
+ok $sth->execute(2, "x" x 100);
+ok $sth->execute(3, "x" x 1000);
+ok $sth->execute(4, "x" x 10000);
+ok $sth->execute(5, "x" x 100000);
+ok $sth->execute(6, "x" x 1000000);
+ok $sth->finish();
+
+ok $sth = $dbh->prepare("SELECT * FROM t WHERE i=? AND n=?");
 
 ok $sth->bind_param(2, "x" x 1000000);
 ok $sth->bind_param(1, "abcx", 12);
 ok $sth->execute();
 
@@ -32,8 +43,36 @@ ok $sth->finish();
 
 ok $sth = $dbh->prepare("SELECT 1 FROM t WHERE i = ?" . (" OR i = ?" x 10000));
 ok $sth->execute((1) x (10001));
 ok $sth->finish();
 
+my $test;
+ok $sth = $dbh->prepare("SELECT i,n FROM t WHERE i = ?");
+
+ok $sth->execute(1);
+ok $sth->fetchrow_arrayref();
+
+ok $sth->execute(2);
+$test = map { $_ } 'a';
+ok $sth->fetchrow_arrayref();
+
+ok $sth->execute(3);
+$test = map { $_ } 'b' x 10000000; # try to reuse released memory
+ok $sth->fetchrow_arrayref();
+
+ok $sth->execute(4);
+$test = map { $_ } 'cd' x 10000000; # try to reuse of released memory
+ok $sth->fetchrow_arrayref();
+
+ok $sth->execute(5);
+$test = map { $_ } 'efg' x 10000000; # try to reuse of released memory
+ok $sth->fetchrow_arrayref();
+
+ok $sth->execute(6);
+$test = map { $_ } 'hijk' x 10000000; # try to reuse of released memory
+ok $sth->fetchrow_arrayref();
+
+ok $sth->finish();
+
 ok $dbh->do("SELECT 1 FROM t WHERE i = ?" . (" OR i = ?" x 10000), {}, (1) x (10001));
 
 ok $dbh->disconnect();

Vulnerable code: plan tests => 17;
ok $dbh->do("CREATE TEMPORARY TABLE t (i INTEGER NOT NULL, n TEXT)");
ok my $sth = $dbh->prepare("SELECT * FROM t WHERE i=? AND n=?");
Fixed code:           imp_sth->stmt->bind[i].buffer_length = fbh->length;
          imp_sth->stmt->bind[i].buffer = (char *)fbh->data;
plan tests => 39;
my $sth;
ok $dbh->do("CREATE TEMPORARY TABLE t (i INTEGER NOT NULL, n LONGBLOB)");

ok $sth = $dbh->prepare("INSERT INTO t(i, n) VALUES(?, ?)");
ok $sth->execute(1, "x" x 10);
ok $sth->execute(2, "x" x 100);
ok $sth->execute(3, "x" x 1000);
ok $sth->execute(4, "x" x 10000);
ok $sth->execute(5, "x" x 100000);
ok $sth->execute(6, "x" x 1000000);
ok $sth->finish();

ok $sth = $dbh->prepare("SELECT * FROM t WHERE i=? AND n=?");
my $test;
ok $sth = $dbh->prepare("SELECT i,n FROM t WHERE i = ?");

ok $sth->execute(1);
ok $sth->fetchrow_arrayref();

ok $sth->execute(2);
$test = map { $_ } 'a';
ok $sth->fetchrow_arrayref();

ok $sth->execute(3);
$test = map { $_ } 'b' x 10000000; # try to reuse released memory
ok $sth->fetchrow_arrayref();

ok $sth->execute(4);
$test = map { $_ } 'cd' x 10000000; # try to reuse of released memory
ok $sth->fetchrow_arrayref();

ok $sth->execute(5);
$test = map { $_ } 'efg' x 10000000; # try to reuse of released memory
ok $sth->fetchrow_arrayref();

ok $sth->execute(6);
$test = map { $_ } 'hijk' x 10000000; # try to reuse of released memory
ok $sth->fetchrow_arrayref();

ok $sth->finish();