Skip Menu |

This queue is for tickets about the DBD-InterBase CPAN distribution.

Report information
The Basics
Id: 49896
Status: open
Priority: 0/
Queue: DBD-InterBase

People
Owner: Nobody in particular
Requestors: ickphum [...] gmail.com
Cc:
AdminCc:

Bug Information
Severity: Normal
Broken in: 0.48
Fixed in: (no value)



Subject: Varchar fields accept data one char over field length (but memory is corrupted)
DBD-InterBase-0.48 This is perl, v5.8.8 built for i586-linux-thread-multi Linux lxdev 2.6.16.60-0.21-xenpae #1 SMP Tue May 6 12:41:02 UTC 2008 i686 i686 i386 GNU/Linux We've found a problem with data that exceeds the limit for varchar fields. CREATE TABLE GGG( C1 Varchar(10) ); GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE ON GGG TO "PUBLIC"; my $sth = $dbh->prepare("insert into ggg values(?)") or die "no prepare"; my $rc = $sth->execute('12345678901'); Going one over the limit as above silently truncates to 10 chars, and also causes memory corruption; this manifested in our wxWidgets app as a free() error on application exit. my $sth = $dbh->prepare("insert into ggg values(?)") or die "no prepare"; my $rc = $sth->execute('123456789012'); DBD::InterBase::st execute failed: You are trying to put 12 characters into a 11 character field at ./test.pl line 141. Going 2 over the limit as above gives an error (which is good) but the error gets the size of the field wrong (which is bad, particularly as if you trust the size shown in the error and don't bother checking the schema, you are led directly into the memory corruption problem mentioned above).
On Mon Sep 21 22:13:30 2009, ickphum wrote: Show quoted text
> We've found a problem with data that exceeds the limit for varchar fields.
The attached patch (incl. test case) resolves the issue for me, does it also work for you? Also available here: http://github.com/pilcrow/perl-dbd-interbase/tree/rt49896
Subject: rt-49896.diff
diff --git a/dbdimp.c b/dbdimp.c index bafb9a2..8e3fb50 100644 --- a/dbdimp.c +++ b/dbdimp.c @@ -2179,53 +2179,36 @@ static int ib_fill_isqlda(SV *sth, imp_sth_t *imp_sth, SV *param, SV *value, case SQL_VARYING: DBI_TRACE_imp_xxh(imp_sth, 1, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: SQL_VARYING\n")); { - char buf[25]; /* long long can have max 20 chars. */ - char *tmp = NULL; + char *string; + if (ivar->sqldata == (char *) NULL) { if ((ivar->sqldata = (char *)safemalloc( - sizeof(char) * (ivar->sqllen + 1) + sizeof(short))) == NULL) + sizeof(char) * ivar->sqllen + sizeof(short))) == NULL) { do_error(sth, 2, "Cannot allocate buffer for VARCHAR input parameter \n"); retval = FALSE; break; } } - if (SvIOK(value)) { - tmp = buf; - len = sprintf(tmp, "%d", (int)SvIV(value)); - } - else if (SvNOK(value)) { - tmp = buf; - len = sprintf(tmp, "%f", SvNV(value)); - } - else if (SvPOK(value) || (SvTYPE(value) == SVt_PVMG)) { - len = SvCUR(value); - tmp = SvPV_nolen(value); - } - else { - /* error */ - do_error(sth, 2, "Cannot cast to VARCHAR input parameter\n"); - retval = FALSE; - break; - } + + string = SvPV(value, len); /* The first word of VARCHAR sqldata is the length */ *((short *) ivar->sqldata) = len; - /* is the scalar longer than the database field? */ - if (len > (sizeof(char) * (ivar->sqllen+1))) + /* is the scalar longer than the database field? */ + if (len > (sizeof(char) * ivar->sqllen)) { char err[80]; sprintf(err, "You are trying to put %d characters into a %d character field", - len, (sizeof(char) * (ivar->sqllen + 1))); + len, (sizeof(char) * ivar->sqllen)); do_error(sth, 2, err); retval = FALSE; } else { - memcpy(ivar->sqldata + sizeof(short), tmp, len); - ivar->sqldata[len + sizeof(short)] = '\0'; + memcpy(ivar->sqldata + sizeof(short), string, len); } break; @@ -2234,8 +2217,7 @@ static int ib_fill_isqlda(SV *sth, imp_sth_t *imp_sth, SV *param, SV *value, case SQL_TEXT: DBI_TRACE_imp_xxh(imp_sth, 1, (DBIc_LOGPIO(imp_sth), "ib_fill_isqlda: SQL_TEXT\n")); { - char buf[25]; /* long long can have max 20 chars. */ - char *tmp; + char *string; if (ivar->sqldata == (char *) NULL) { @@ -2247,39 +2229,23 @@ static int ib_fill_isqlda(SV *sth, imp_sth_t *imp_sth, SV *param, SV *value, break; } } - if (SvIOK(value)) { - tmp = buf; - len = sprintf(tmp, "%d", (int)SvIV(value)); - } - else if (SvNOK(value)) { - tmp = buf; - len = sprintf(tmp, "%f", SvNV(value)); - } - else if (SvPOK(value) || (SvTYPE(value) == SVt_PVMG)) { - len = SvCUR(value); - tmp = SvPV_nolen(value); - } - else { - /* error */ - do_error(sth, 2, "Cannot cast to TEXT input parameter\n"); - retval = FALSE; - break; - } + + string = SvPV(value, len); /* is the scalar longer than the database field? */ - if (len > (sizeof(char) * (ivar->sqllen+1))) + if (len > (sizeof(char) * ivar->sqllen)) { /* error? or truncate? */ char err[80]; sprintf(err, "You are trying to put %d characters into a %d character field", - len, (sizeof(char) * (ivar->sqllen+1))); + len, (sizeof(char) * ivar->sqllen)); do_error(sth, 2, err); retval = FALSE; } else { memset(ivar->sqldata, ' ', ivar->sqllen); - memcpy(ivar->sqldata, tmp, len); + memcpy(ivar->sqldata, string, len); } break; diff --git a/t/rt49896.t b/t/rt49896.t new file mode 100644 index 0000000..d5e99b5 --- /dev/null +++ b/t/rt49896.t @@ -0,0 +1,73 @@ +#!/usr/local/bin/perl -w +# +# Test cases for DBD-InterBase rt.cpan.org #49896 +# "Varchar fields accept data one char over field length (but memory +# is corrupted)" +# + +use strict; +use DBI; +use Test::More tests => 8; +use vars qw( $dbh $table ); + +END { + if (defined($dbh) and $table) { + eval { $dbh->do("DROP TABLE $table"); }; + $dbh->disconnect; + } +} + +# Make -w happy +$::test_dsn = ''; +$::test_user = ''; +$::test_password = ''; + +my $file; +do { + if (-f ($file = "t/InterBase.dbtest") || + -f ($file = "InterBase.dbtest")) + { + eval { require $file }; + if ($@) { + diag("Cannot execute $file: $@\n"); + exit 0; + } + } +}; + +sub find_new_table { + my $dbh = shift; + my $try_name = 'GGG'; + my %tables = map { uc($_) => 1 } $dbh->tables; + while (exists $tables{$try_name}) { + ++$try_name; + } + $try_name; +} + +# ------- TESTS ------------------------------------------------------------- # + +$dbh = DBI->connect($::test_dsn, $::test_user, $::test_password); +ok($dbh); + +$table = find_new_table($dbh); +ok($table); + +ok($dbh->do("CREATE TABLE $table( c1 varchar(3) )", + "CREATE TABLE $table(...)")); + +ok($dbh->do("INSERT INTO GGG(c1) VALUES(?)", undef, 'aa'), + "INSERT string (length < column size) succeeds"); + +ok($dbh->do("INSERT INTO GGG(c1) VALUES(?)", undef, 'aaa'), + "INSERT string (length == column size) succeeds"); + +$dbh->{PrintError} = 0; + +ok(! defined $dbh->do("INSERT INTO GGG(c1) VALUES(?)", undef, 'aaa!'), + "INSERT string (length == column size + 1) fails"); + +ok(! defined $dbh->do("INSERT INTO GGG(c1) VALUES(?)", undef, 'aaa!!'), + "INSERT string (length == column size + 2) fails"); + +ok($dbh->do("DROP TABLE $table"), "DROP TABLE $table");