CVE-2016-1251 (published 2016-11-29T20:59Z)

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.

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 8.1, HIGH (CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-416
cwe_name: Use After Free
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.
extended_description: 
             
             
             
			 
             
             
         
         

Fix information:
repo_url: https://github.com/perl5-dbi/DBD-mysql
commit_details: repo_url: https://github.com/perl5-dbi/DBD-mysql
author: Pali
author_date: 2016-11-18 19:01:48+01:00
msg: 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.
repository: repo_url: https://github.com/perl5-dbi/DBD-mysql
repo_name: perl5-dbi/DBD-mysql
description: MySQL driver for the Perl5 Database Interface (DBI)
repo_language: Perl
owner: perl5-dbi
file_changes: filename: dbdimp.c
change_type: ModificationType.MODIFY
diff: @@ -4050,6 +4050,8 @@ process:
           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;

programming_language: C
filename: 40server_prepare_crash.t
change_type: ModificationType.MODIFY
diff: @@ -10,11 +10,22 @@ 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);
@@ -34,6 +45,34 @@ 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();

programming_language: Perl