On Wed, Aug 21, 2002 at 11:26:17AM +1000, Bradley Baetz wrote:
> On Tue, 20 Aug 2002, Tim Bunce wrote:
>
> > On Tue, Aug 20, 2002 at 07:18:23PM +1000, Bradley Baetz wrote:
>
> > Ah. I think ExampleP predates _get_fbav. I'll mandate that all
> > drivers must use _get_fbav (either in C or perl) to get the row
> > buffer. So the right approach here is to use $sth->_get_fbav()
> > and then deal with whatever knock-on effects appear.
>
> I can't work out how to use this from the perl code to get the correct
> results. Whatever I try, I can't seem to either get the tatint tests to
> pass, or not regress other tests I'm sure I'm missing something obvious...
>
> However, _get_fbav has the comment that the TAINT; "affects sv_setsv()'s
> called within same perl statement".
>
> In the perl code, I'm not in the same statement, and there aren't any
> sv_setsv's (since the entire hash isn't tainted, only (possibly) values
> within it) - is that the problem?
Probably (well spotted). There's a $sth->_set_fbav( \@fieldvalues) method
you could try that copies the supplied values into the row array.
> I can't find docs on most of the XS taint stuff you do, so I'm not sure if
> I'm reading this correctly.
Yeah, taint internals aren't well documented. I had to work it out
from the perl source code.
> > > If I reenable that part of the code, then the taint tests pass, but
> > > selectall_hashref fails at the ->bind_col in DBI.pm because $index is
> > > tainted; this is presumably why that was disabled.
> >
> > Um, not sure what that's about off-hand. Can you repost once you've
> > switched ExampleP over to using $sth->_get_fbav()?
>
> If I could do that, then I can leave that code disabled, so its OK.
>
> Anyway, I've attached a patch (w/o docs, for the moment) which fails the
> tests for when the output stuff is meant to be tainted. I also decided to
> name the attributes TaintIn and TaintOut rather than TaintInput and
> TaintOutput, for no real reason except personal preference.
Okay.
> Let me know what you think.
Great work so far, thanks.
Try using _set_fbav(). If that doesn't work then try reenabling the TaintOut
logic in the dispatch code, but only if the methodname starts with 'fetch'.
Tim.
> Bradley
Content-Description: patch
> diff -ur DBI-1.30/DBI.xs DBI/DBI.xs
> --- DBI-1.30/DBI.xs Fri Jul 19 00:23:51 2002
> +++ DBI/DBI.xs Tue Aug 20 16:20:05 2002
> @@ -932,8 +932,9 @@
> if (DBIc_is(imp_xxh, DBIcf_BegunWork)) sv_catpv(flags,"BegunWork ");
> if (DBIc_is(imp_xxh, DBIcf_LongTruncOk)) sv_catpv(flags,"LongTruncOk ");
> if (DBIc_is(imp_xxh, DBIcf_MultiThread)) sv_catpv(flags,"MultiThread ");
> - if (DBIc_is(imp_xxh, DBIcf_Taint)) sv_catpv(flags,"Taint ");
> + if (DBIc_is(imp_xxh, DBIcf_TaintOut)) sv_catpv(flags,"TaintOut ");
> if (DBIc_is(imp_xxh, DBIcf_Profile)) sv_catpv(flags,"Profile ");
> + if (DBIc_is(imp_xxh, DBIcf_TaintIn)) sv_catpv(flags,"TaintIn ");
> PerlIO_printf(DBILOGFP,"%s FLAGS 0x%lx: %s\n", pad, (long)DBIc_FLAGS(imp_xxh),
>SvPV(flags,lna));
> PerlIO_printf(DBILOGFP,"%s PARENT %s\n", pad,
>neatsvpv((SV*)DBIc_PARENT_H(imp_xxh),0));
> PerlIO_printf(DBILOGFP,"%s KIDS %ld (%ld Active)\n", pad,
> @@ -1109,7 +1110,7 @@
> if ( (av = DBIc_FIELDS_AV(imp_sth)) == Nullav)
> av = dbih_setup_fbav(imp_sth);
>
> - if (DBIc_is(imp_sth, DBIcf_Taint)) {
> + if (DBIc_is(imp_sth, DBIcf_TaintOut)) {
> dTHR;
> TAINT; /* affects sv_setsv()'s called within same perl statement */
> }
> @@ -1321,7 +1322,14 @@
> }
> }
> else if (strEQ(key, "Taint")) {
> - DBIc_set(imp_xxh,DBIcf_Taint, on);
> + /* 'Taint' is a shortcut for both in and out mode */
> + DBIc_set(imp_xxh,DBIcf_TaintIn|DBIcf_TaintOut, on);
> + }
> + else if (strEQ(key, "TaintIn")) {
> + DBIc_set(imp_xxh,DBIcf_TaintIn, on);
> + }
> + else if (strEQ(key, "TaintOut")) {
> + DBIc_set(imp_xxh,DBIcf_TaintOut, on);
> }
> else if (htype<=DBIt_DB && keylen==10 && strEQ(key, "CachedKids")) {
> D_imp_dbh(h); /* XXX also for drh */
> @@ -1536,7 +1544,14 @@
> valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_MultiThread));
> }
> else if (keylen==5 && strEQ(key, "Taint")) {
> - valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_Taint));
> + valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_TaintIn) &&
> + DBIc_has(imp_xxh,DBIcf_TaintOut));
> + }
> + else if (keylen==7 && strEQ(key, "TaintIn")) {
> + valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_TaintIn));
> + }
> + else if (keylen==8 && strEQ(key, "TaintOut")) {
> + valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_TaintOut));
> }
> else if (htype<=DBIt_DB && keylen==10 && strEQ(key, "AutoCommit")) {
> valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_AutoCommit));
> @@ -2144,7 +2159,7 @@
> }
>
> if (tainting && items > 1 /* method call has args */
> - && DBIc_is(imp_xxh, DBIcf_Taint) /* taint checks requested */
> + && DBIc_is(imp_xxh, DBIcf_TaintIn) /* taint checks requested */
> && !(ima && ima->flags & IMA_NO_TAINT_IN)
> ) {
> for(i=1; i < items; ++i) {
> @@ -2337,7 +2352,7 @@
> }
> PerlIO_printf(logfp,"%c%c <- %s",
> (call_depth > 1) ? '0'+call_depth-1 : ' ',
> - (DBIc_is(imp_xxh, DBIcf_Taint)) ? 'T' : ' ',
> + (DBIc_is(imp_xxh, DBIcf_TaintIn|DBIcf_TaintOut)) ? 'T' : ' ',
> meth_name);
> if (debug==1 && items>=2) { /* make level 1 more useful */
> /* we only have the first two parameters available here */
> @@ -2401,7 +2416,7 @@
> }
>
> if (tainting
> - && DBIc_is(imp_xxh, DBIcf_Taint) /* taint checks requested */
> + && DBIc_is(imp_xxh, DBIcf_TaintOut) /* taint checks requested */
> /* XXX this would taint *everything* being returned from *any* */
> /* method that doesn't have IMA_NO_TAINT_OUT set. */
> /* DISABLED: just tainting fetched data in get_fbav seems ok */
> Only in DBI: DBI.xs~
> diff -ur DBI-1.30/DBIXS.h DBI/DBIXS.h
> --- DBI-1.30/DBIXS.h Mon Jul 15 21:19:04 2002
> +++ DBI/DBIXS.h Tue Aug 20 18:52:14 2002
> @@ -227,11 +227,12 @@
> #define DBIcf_AutoCommit 0x000200 /* dbh only. used by drivers */
> #define DBIcf_LongTruncOk 0x000400 /* truncation to LongReadLen is okay */
> #define DBIcf_MultiThread 0x000800 /* allow multiple threads to enter */
> -#define DBIcf_Taint 0x001000 /* taint fetched data */
> +#define DBIcf_TaintIn 0x001000 /* taint fetched data */
> #define DBIcf_ShowErrorStatement 0x002000 /* include Statement in error */
> #define DBIcf_BegunWork 0x004000 /* between begin_work & commit/rollback */
> #define DBIcf_HandleError 0x008000 /* has coderef in HandleError attribute */
> #define DBIcf_Profile 0x010000 /* profile activity on this handle */
> +#define DBIcf_TaintOut 0x020000 /* Check inputs for taintedness */
>
> #define DBIcf_INHERITMASK /* what NOT to pass on to children */ \
> (U32)( DBIcf_COMSET | DBIcf_IMPSET | DBIcf_ACTIVE | DBIcf_IADESTROY
> \
> Only in DBI: DBIXS.h~
> Only in DBI: Makefile.old
> diff -ur DBI-1.30/lib/DBD/ExampleP.pm DBI/lib/DBD/ExampleP.pm
> --- DBI-1.30/lib/DBD/ExampleP.pm Mon Jul 15 21:19:20 2002
> +++ DBI/lib/DBD/ExampleP.pm Wed Aug 21 11:09:14 2002
> @@ -326,6 +326,8 @@
> $sth->finish;
> return;
> }
> + # untaint $f so that we can use this for DBI taint tests
> + ($f) = ($f =~ m/^(.*)$/);
> my $file = $haveFileSpec
> ? File::Spec->catfile($dir, $f) : "$dir/$f";
> # put in all the data fields
> @@ -334,7 +336,7 @@
>
> # return just what fields the query asks for
> my @new = @s{ @{$sth->{NAME}} };
> -
> +
> return $sth->_set_fbav(\@new);
> }
> *fetchrow_arrayref = \&fetch;
> Only in DBI/lib/DBD: ExampleP.pm~
> diff -ur DBI-1.30/lib/DBI/PurePerl.pm DBI/lib/DBI/PurePerl.pm
> --- DBI-1.30/lib/DBI/PurePerl.pm Mon Jul 15 21:19:22 2002
> +++ DBI/lib/DBI/PurePerl.pm Wed Aug 21 11:04:18 2002
> @@ -134,6 +134,8 @@
> ShowErrorStatement
> Statement
> Taint
> + TaintIn
> + TaintOut
> TraceLevel
> Version
> Warn
> @@ -492,6 +494,9 @@
> $i++;
> }
> }
> + if ($key eq "Taint") {
> + return ($h->{'TaintIn'} && $h->{'TaintOut'});
> + }
> my $v = $h->{$key};
> if (!defined $v && !exists $h->{$key}) {
> local $^W; # hide undef warnings
> @@ -507,6 +512,13 @@
> unless $value == -900 || $value == -901;
> $value = ($value == -901);
> }
> + elsif (($key eq 'Taint' || $key eq 'TaintIn' || $key eq 'TaintOut')) {
> + Carp::croak(sprintf "Can't set %s->{%s}: Taint mode not supported by
>DBI::PurePerl",$h,$key);
> + }
> + elsif ($key eq 'Taint') {
> + $h->{'TaintIn'} = $h->{'TaintOut'} = $value;
> + return 1;
> + }
> elsif (!$is_valid_attribute{$key} && $key =~ /^[A-Z]/ && !exists $h->{$key}) {
> Carp::croak(sprintf "Can't set %s->{%s}: unrecognised attribute or invalid
>value %s",
> $h,$key,$value);
> @@ -752,6 +764,8 @@
> InactiveDestroy
> Kids
> Taint
> + TaintIn
> + TaintOut
> TraceLevel
>
> (and probably others)
> Only in DBI/lib/DBI: PurePerl.pm~
> diff -ur DBI-1.30/t/10examp.t DBI/t/10examp.t
> --- DBI-1.30/t/10examp.t Mon Jul 15 21:19:08 2002
> +++ DBI/t/10examp.t Wed Aug 21 11:18:27 2002
> @@ -35,11 +35,13 @@
> DBI->trace(3,$trace_file); # enable trace before first driver load
>
> my $r;
> -my $dbh = DBI->connect('dbi:ExampleP(AutoCommit=>1 ,Taint = 1):', undef, undef);
> +my $dbh = DBI->connect('dbi:ExampleP(AutoCommit=>1):', undef, undef);
> die "Unable to connect to ExampleP driver: $DBI::errstr" unless $dbh;
> ok(0, $dbh);
> ok(0, ref $dbh);
>
> +$dbh->{Taint} = 1 unless $DBI::PurePerl;
> +
> if (0) {
> DBI->trace(9,undef);
> warn DBI::dump_handle($dbh,"dump_handle",1);
> @@ -67,7 +69,7 @@
> #$dbh->trace(2);
> $dbh->{AutoCommit} = 1;
> $dbh->{PrintError} = 0;
> -ok(0, $dbh->{Taint} == 1);
> +ok(0, $dbh->{Taint} == 1) unless $DBI::PurePerl && ok(0,1);
> ok(0, $dbh->{AutoCommit} == 1);
> ok(0, $dbh->{PrintError} == 0);
> #$dbh->trace(0); die;
> @@ -158,6 +160,38 @@
> ok(0, "@{[sort keys %{$csr_b->{NAME_uc_hash}}]}" eq "MODE NAME SIZE");
> ok(0, "@{[sort values %{$csr_b->{NAME_uc_hash}}]}" eq "0 1 2");
>
> +if ($DBI::PurePerl) {
> + warn " Taint mode switching tests skipped\n";
> + ok(0,1) foreach (1..15);
> +} else {
> + # Check Taint* attribute switching
> +
> + #$dbh->{'Taint'} = 1; # set in connect
> + ok(0, $dbh->{'Taint'});
> + ok(0, $dbh->{'TaintIn'} == 1);
> + ok(0, $dbh->{'TaintOut'} == 1);
> +
> + $dbh->{'TaintOut'} = 0;
> + ok(0, $dbh->{'Taint'} == 0);
> + ok(0, $dbh->{'TaintIn'} == 1);
> + ok(0, $dbh->{'TaintOut'} == 0);
> +
> + $dbh->{'Taint'} = 0;
> + ok(0, $dbh->{'Taint'} == 0);
> + ok(0, $dbh->{'TaintIn'} == 0);
> + ok(0, $dbh->{'TaintOut'} == 0);
> +
> + $dbh->{'TaintIn'} = 1;
> + ok(0, $dbh->{'Taint'} == 0);
> + ok(0, $dbh->{'TaintIn'} == 1);
> + ok(0, $dbh->{'TaintOut'} == 0);
> +
> + $dbh->{'TaintOut'} = 1;
> + ok(0, $dbh->{'Taint'} == 1);
> + ok(0, $dbh->{'TaintIn'} == 1);
> + ok(0, $dbh->{'TaintOut'} == 1);
> +}
> +
> # get a dir always readable on all platforms
> my $dir = getcwd() || cwd();
> $dir = VMS::Filespec::unixify($dir) if $^O eq 'VMS';
> @@ -173,7 +207,7 @@
> #$csr_a->trace(2);
> ok(0, $csr_a->bind_columns(undef, \($col0, $col1, $col2)) );
> ok(0, $csr_a->execute( $dir ));
> -ok(0, $csr_a->{Taint} = 1);
> +ok(0, $csr_a->{Taint} = 1) unless $DBI::PurePerl && ok(0,1);
>
> @row_a = $csr_a->fetchrow_array;
> ok(0, @row_a);
> @@ -189,29 +223,81 @@
> my $foo;
> return ! eval { ($foo=join('',@_)), kill 0; 1; };
> }
> -if (is_tainted($^X)) {
> +if (is_tainted($^X) && !$DBI::PurePerl) {
> print "Taint attribute test enabled\n";
> - ok(0, is_tainted($row_a[0]) );
> - ok(0, is_tainted($row_a[1]) );
> - ok(0, is_tainted($row_a[2]) );
> - # check simple method call values
> - ok(0, 1);
> - # check simple attribute values
> - ok(0, 1); # is_tainted($dbh->{AutoCommit}) );
> - # check nested attribute values (where a ref is returned)
> + $dbh->{'Taint'} = 0;
> + my $st;
> + eval { $st = $dbh->prepare($std_sql); };
> + ok(0, ref $st);
> +
> + ok(0, $st->{'Taint'} == 0);
> +
> + ok(0, $st->execute( $dir ));
> +
> + my @row = $st->fetchrow_array;
> + ok(0, @row);
> +
> + ok(0, !is_tainted($row[0]));
> + ok(0, !is_tainted($row[1]));
> + ok(0, !is_tainted($row[2]));
> +
> + $st->{'TaintIn'} = 1;
> +
> + @row = $st->fetchrow_array;
> + ok(0, @row);
> +
> + ok(0, !is_tainted($row[0]));
> + ok(0, !is_tainted($row[1]));
> + ok(0, !is_tainted($row[2]));
> +
> + $st->{'TaintOut'} = 1;
> +
> + @row = $st->fetchrow_array;
> + ok(0, @row);
> +
> + ok(0, is_tainted($row[0]));
> + ok(0, is_tainted($row[1]));
> + ok(0, is_tainted($row[2]));
> +
> + $st->finish;
> +
> + # check simple method call values
> + #ok(0, 1);
> + # check simple attribute values
> + #ok(0, 1); # is_tainted($dbh->{AutoCommit}) );
> + # check nested attribute values (where a ref is returned)
> #ok(0, is_tainted($csr_a->{NAME}->[0]) );
> - # check checking for tainted values
> - eval { $dbh->prepare($^X); 1; };
> - ok(0, $@ =~ /Insecure dependency/, $@);
> - eval { $csr_a->execute($^X); 1; };
> - ok(0, $@ =~ /Insecure dependency/, $@);
> + # check checking for tainted values
> +
> + $dbh->{'Taint'} = $csr_a->{'Taint'} = 1;
> + eval { $dbh->prepare($^X); 1; };
> + ok(0, $@ =~ /Insecure dependency/, $@);
> + eval { $csr_a->execute($^X); 1; };
> + ok(0, $@ =~ /Insecure dependency/, $@);
> undef $@;
> +
> + $dbh->{'TaintIn'} = $csr_a->{'TaintIn'} = 0;
> +
> + eval { $dbh->prepare($^X); 1; };
> + ok(0, !$@);
> + eval { $csr_a->execute($^X); 1; };
> + ok(0, !$@);
> +
> + # Reset taint status to what it was before this block, so that
> + # tests later in the file don't get confused
> + $dbh->{'Taint'} = $csr_a->{'Taint'} = 1;
> }
> else {
> warn " Taint attribute tests skipped\n";
> - ok(0,1) foreach (1..7);
> + ok(0,1) foreach (1..19);
> +}
> +
> +unless ($DBI::PurePerl) {
> + $csr_a->{Taint} = 0;
> + ok(0, $csr_a->{Taint} == 0);
> +} else {
> + ok(0, 1);
> }
> -$csr_a->{Taint} = 0;
>
> ok(0, $csr_b->bind_param(1, $dir));
> ok(0, $csr_b->execute());
> @@ -574,4 +660,4 @@
> }
> ok(0, (%tables == 0));
>
> -BEGIN { $tests = 215; }
> +BEGIN { $tests = 243; }
> Only in DBI/t: 10examp.t~
> Only in DBI/t: zz_01basics_pp.t
> Only in DBI/t: zz_02dbidrv_pp.t
> Only in DBI/t: zz_03hleak_pp.t
> Only in DBI/t: zz_04mods_pp.t
> Only in DBI/t: zz_05thrclone_pp.t
> Only in DBI/t: zz_10examp_pp.t
> Only in DBI/t: zz_15array_pp.t
> Only in DBI/t: zz_20meta_pp.t
> Only in DBI/t: zz_30subclass_pp.t
> Only in DBI/t: zz_40profile_pp.t
> Only in DBI/t: zz_60preparse_pp.t
> Only in DBI/t: zz_70shell_pp.t
> Only in DBI/t: zz_80proxy_pp.t