Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypt() returns tainted data even when input strings are detainted #9537

Closed
p5pRT opened this issue Oct 19, 2008 · 8 comments
Closed

crypt() returns tainted data even when input strings are detainted #9537

p5pRT opened this issue Oct 19, 2008 · 8 comments

Comments

@p5pRT
Copy link

p5pRT commented Oct 19, 2008

Migrated from rt.perl.org#59998 (status was 'resolved')

Searchable as RT59998$

@p5pRT
Copy link
Author

p5pRT commented Oct 19, 2008

From lpsolit@gmail.com

This is a bug report for perl from LpSolit@​gmail.com,
generated with the help of perlbug 1.36 running under perl 5.10.0.

Run the following script as​: perl -wT test.pl foo bar
$crypted3 is tainted despite the input string is detainted. Note that
$crypted2 is not tainted despite the code in bz_crypt() is very similar
to the one in bz_crypt2(). IMO, that's not the expected behavior.

#!/usr/bin/perl -wT

use strict;

sub is_tainted {
  return not eval { my $foo = join('',@​_), kill 0; 1; };
}

sub bz_crypt {
  my ($password) = @​_;

  my @​saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/');

  my $salt = '';
  for ( my $i=0 ; $i < 8 ; ++$i ) {
  $salt .= $saltchars[rand(64)];
  }

  # Crypt the password.
  my $cryptedpassword = crypt($password, $salt);

  # Return the crypted password.
  return $cryptedpassword;
}

sub bz_crypt2 {
  my ($password, $salt) = @​_;

  if (!defined $salt) {
  my @​saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/');

  $salt = '';
  for ( my $i=0 ; $i < 8 ; ++$i ) {
  $salt .= $saltchars[rand(64)];
  }
  }

  # Crypt the password.
  my $cryptedpassword = crypt($password, $salt);

  # Return the crypted password.
  return $cryptedpassword;
}

my ($pwd, $salt) = ($ARGV[0], $ARGV[1]);
# Detaint the password, but leave the salt tainted.
$pwd =~ /^(.*)$/;
$pwd = $1;

# Tainted salt, so the encrypted password should be tainted too.
my $crypted = bz_crypt2($pwd, $salt);
print "crypted is tainted? " . (is_tainted($crypted) ? "yes" : "no") . "
(expected​: yes)\n";

# Detainted password, so the encrypted password should not be tainted.
my $crypted2 = bz_crypt($pwd);
print "crypted2 is tainted? " . (is_tainted($crypted2) ? "yes" : "no") .
" (expected​: no)\n";

# Detainted password, so the encrypted password should not be tainted.
my $crypted3 = bz_crypt2($pwd);
print "crypted3 is tainted? " . (is_tainted($crypted3) ? "yes" : "no") .
" (expected​: no)\n";


Flags​:
  category=core
  severity=high


Site configuration information for perl 5.10.0​:

Configured by Mandriva at Thu Sep 18 16​:39​:24 EDT 2008.

Summary of my perl5 (revision 5 version 10 subversion 0) configuration​:
  Platform​:
  osname=linux, osvers=2.6.22.18-server-1mdv,
archname=i386-linux-thread-multi
  uname='linux n2.mandriva.com 2.6.22.18-server-1mdv #1 smp mon feb 11
16​:46​:24 est 2008 i686 intel(r) xeon(tm) cpu 2.80ghz gnulinux '
  config_args='-des -Dinc_version_list=5.8.8 5.8.7 5.8.6 5.8.5 5.8.4
5.8.3 5.8.2 5.8.1 5.8.0 5.6.1 5.6.0 -Darchname=i386-linux -Dcc=gcc
-Doptimize=-O2 -g -pipe -Wp,-D_FORTIFY_SOURCE=2 -fexceptions
-fstack-protector --param=ssp-buffer-size=4 -fomit-frame-pointer
-march=i586 -mtune=generic -fasynchronous-unwind-tables -DDEBUGGING=-g
-Dprefix=/usr -Dvendorprefix=/usr -Dsiteprefix=/usr
-Dsitebin=/usr/local/bin -Dsiteman1dir=/usr/local/share/man/man1
-Dsiteman3dir=/usr/local/share/man/man3 -Dman3ext=3pm -Dcf_by=Mandriva
-Dmyhostname=localhost -Dperladmin=root@​localhost
-Dcf_email=root@​localhost -Dd_dosuid -Ud_csh -Duseshrplib -Duseithreads
-Di_db -Di_ndbm -Di_gdbm'
  hint=recommended, useposix=true, d_sigaction=define
  useithreads=define, usemultiplicity=define
  useperlio=define, d_sfio=undef, uselargefiles=define, usesocks=undef
  use64bitint=undef, use64bitall=undef, uselongdouble=undef
  usemymalloc=n, bincompat5005=undef
  Compiler​:
  cc='gcc', ccflags ='-D_REENTRANT -D_GNU_SOURCE -fno-strict-aliasing
-pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
-I/usr/include/gdbm',
  optimize='-O2 -g -pipe -Wp,-D_FORTIFY_SOURCE=2 -fexceptions
-fstack-protector --param=ssp-buffer-size=4 -fomit-frame-pointer
-march=i586 -mtune=generic -fasynchronous-unwind-tables',
  cppflags='-D_REENTRANT -D_GNU_SOURCE -fno-strict-aliasing -pipe
-I/usr/local/include -I/usr/include/gdbm'
  ccversion='', gccversion='4.3.2', gccosandvers=''
  intsize=4, longsize=4, ptrsize=4, doublesize=8, byteorder=1234
  d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=12
  ivtype='long', ivsize=4, nvtype='double', nvsize=8, Off_t='off_t',
lseeksize=8
  alignbytes=4, prototype=define
  Linker and Libraries​:
  ld='gcc', ldflags =' -L/usr/local/lib'
  libpth=/usr/local/lib /lib /usr/lib
  libs=-lnsl -lgdbm -ldb -ldl -lm -lcrypt -lutil -lpthread -lc
  perllibs=-lnsl -ldl -lm -lcrypt -lutil -lpthread -lc
  libc=/lib/libc-2.8.so, so=so, useshrplib=true, libperl=libperl.so
  gnulibc_version='2.8'
  Dynamic Linking​:
  dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,-E
-Wl,-rpath,/usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE'
  cccdlflags='-fPIC', lddlflags='-shared -O2 -g -pipe
-Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector
--param=ssp-buffer-size=4 -fomit-frame-pointer -march=i586
-mtune=generic -fasynchronous-unwind-tables -L/usr/local/lib'

Locally applied patches​:
  Mandriva Linux patches


@​INC for perl 5.10.0​:
  /usr/lib/perl5/site_perl/5.10.0/i386-linux-thread-multi
  /usr/lib/perl5/site_perl/5.10.0
  /usr/lib/perl5/vendor_perl/5.10.0/i386-linux-thread-multi
  /usr/lib/perl5/vendor_perl/5.10.0
  /usr/lib/perl5/5.10.0/i386-linux-thread-multi
  /usr/lib/perl5/5.10.0
  /usr/lib/perl5/site_perl
  /usr/lib/perl5/vendor_perl
  .


Environment for perl 5.10.0​:
  HOME=/root
  LANG=fr_CH.UTF-8
  LANGUAGE=fr_CH.UTF-8​:fr
  LC_ADDRESS=fr_CH.UTF-8
  LC_COLLATE=fr_CH.UTF-8
  LC_CTYPE=fr_CH.UTF-8
  LC_IDENTIFICATION=fr_CH.UTF-8
  LC_MEASUREMENT=fr_CH.UTF-8
  LC_MESSAGES=fr_CH.UTF-8
  LC_MONETARY=fr_CH.UTF-8
  LC_NAME=fr_CH.UTF-8
  LC_NUMERIC=fr_CH.UTF-8
  LC_PAPER=fr_CH.UTF-8
  LC_SOURCED=1
  LC_TELEPHONE=fr_CH.UTF-8
  LC_TIME=fr_CH.UTF-8
  LD_LIBRARY_PATH (unset)
  LOGDIR (unset)

PATH=/sbin​:/usr/sbin​:/bin​:/usr/bin​:/usr/X11R6/bin​:/usr/local/bin​:/usr/local/sbin​:/root/bin
  PERL_BADLANG (unset)
  SHELL=/bin/bash

@p5pRT
Copy link
Author

p5pRT commented Nov 14, 2008

From @chipdude

The test program in

  http​://rt.perl.org/rt3/Ticket/Display.html?id=59998

... seems to reveal that tainting is sticking in TARG SVs between function
invocations. This is Very Bad for programs that use tainting.

Consider this output with -Dts enabled and also a patch to flag which values
are tainted​:

  (foo59998​:43) nextstate
  =>
  (foo59998​:44) padsv($password)
  => PVMG("hi"\0)
  (foo59998​:44) padsv($salt)
  => PVMG("hi"\0) PVMG("LDZR.v34"\0)
  (foo59998​:44) crypt
  => PVMG("LDjbsV4FYpZ0k"\0) [tainted]
  (foo59998​:44) padsv($cryptedpassword)
  => PVMG("LDjbsV4FYpZ0k"\0) [tainted] UNDEF
  (foo59998​:44) sassign
  => PVMG("LDjbsV4FYpZ0k"\0) [tainted]
  (foo59998​:44) nextstate

As can be clearly seen, the inputs to crypt() are not tainted, but its
output is. This only occurs on the *second* call to the given function, so
it's not apparently a bug in pp_crypt() per se.

Shirley, TARGS should be subject to SV clearing by no later than end of
statement, so that there won't be magic left lying around to hurt future
generations. (Or else when tainting is enabled TARGs could be reallocated
fresh every time; but this essentially disables the whole optimization.)

Anyone have any deeper thoughts? I don't know offhand how to be sure that
TARG SVs are properly cleared at the right time, for example.

Incidentally here is the patch to modify the dump as above. I found it
invaluable in tracking down this kind of taint weirdness.

Inline Patch
diff --git a/dump.c b/dump.c
index fdb8dde..724baf8 100644
--- a/dump.c
+++ b/dump.c
@@ -532,6 +532,8 @@ Perl_sv_peek(pTHX_ SV *sv)
   finish:
     while (unref--)
        sv_catpv(t, ")");
+    if (PL_tainting && SvTAINTED(sv))
+       sv_catpv(t, " [tainted]");
     return SvPV_nolen(t);
 }
 

-- 

Chip Salzenberg <chip@​pobox.com>

@p5pRT
Copy link
Author

p5pRT commented Nov 14, 2008

The RT System itself - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Nov 14, 2008

From @chipdude

On Fri, Nov 14, 2008 at 02​:29​:13AM -0800, Chip Salzenberg wrote​:

I don't know offhand how to be sure that TARG SVs are properly cleared at
the right time, for example.

A thought occurs that the various GETTARGET* macros would be a good place.
They could ensure that a TARG is free of dark magicks when it is (re)used.
--
Chip Salzenberg <chip@​pobox.com>

@p5pRT
Copy link
Author

p5pRT commented Nov 15, 2008

From @rgs

2008/11/14 Chip Salzenberg <chip@​pobox.com>​:

The test program in

http​://rt.perl.org/rt3/Ticket/Display.html?id=59998

... seems to reveal that tainting is sticking in TARG SVs between function
invocations. This is Very Bad for programs that use tainting.

Consider this output with -Dts enabled and also a patch to flag which values
are tainted​:

(foo59998​:43) nextstate
=>
(foo59998​:44) padsv($password)
=> PVMG("hi"\0)
(foo59998​:44) padsv($salt)
=> PVMG("hi"\0) PVMG("LDZR.v34"\0)
(foo59998​:44) crypt
=> PVMG("LDjbsV4FYpZ0k"\0) [tainted]
(foo59998​:44) padsv($cryptedpassword)
=> PVMG("LDjbsV4FYpZ0k"\0) [tainted] UNDEF
(foo59998​:44) sassign
=> PVMG("LDjbsV4FYpZ0k"\0) [tainted]
(foo59998​:44) nextstate

As can be clearly seen, the inputs to crypt() are not tainted, but its
output is. This only occurs on the *second* call to the given function, so
it's not apparently a bug in pp_crypt() per se.

Shirley, TARGS should be subject to SV clearing by no later than end of
statement, so that there won't be magic left lying around to hurt future
generations. (Or else when tainting is enabled TARGs could be reallocated
fresh every time; but this essentially disables the whole optimization.)

Anyone have any deeper thoughts? I don't know offhand how to be sure that
TARG SVs are properly cleared at the right time, for example.

Great bug tracing, but I have no other clue for now.

Incidentally here is the patch to modify the dump as above. I found it
invaluable in tracking down this kind of taint weirdness.

Thanks, applied.

@p5pRT
Copy link
Author

p5pRT commented Nov 17, 2008

From @chipdude

On Sun, Oct 19, 2008 at 01​:11​:22PM -0700, Frédéric Buclin wrote​:

$crypted3 is tainted despite the input string is detainted. Note that
$crypted2 is not tainted despite the code in bz_crypt() is very similar
to the one in bz_crypt2(). IMO, that's not the expected behavior.

I've tracked this down to a bug in two seemingly random opcodes​: crypt and
stringwise complement ("~"). The core of the bug is just missing set magic,
via the use of "SETs(TARG)" instead of the visually similar but more magical
"SETTARG".

The clearing of lexicals that usually is taken care of by pp_padsv is
bypassed when a lexical is the TARG of some other opcode. This is good,
*if* the given opcode calls the TARG's set magic like it's supposed to. But
the given opcodes didn't do so. Set magic on tainted values untaints them
[unless their inputs were tainted, of course]. Thereofre, missing set magic
in ppcode can 'create' lexicals tainted if they were tainted before in a
previous go-round.

The below patch fixes the reported bug with crypt, as well sa the discovered
bug with complement. And it also makes a small code cleanup, preferring
SETTARG to a longer equivalent.

Share and enjoy!

Inline Patch
diff --git a/pp.c b/pp.c
index 739a457..166c315 100644
--- a/pp.c
+++ b/pp.c
@@ -2553,7 +2553,7 @@ PP(pp_complement)
 	      sv_usepvn_flags(TARG, (char*)result, nchar, SV_HAS_TRAILING_NUL);
 	      SvUTF8_off(TARG);
 	  }
-	  SETs(TARG);
+	  SETTARG;
 	  RETURN;
 	}
 #ifdef LIBERAL
@@ -2569,8 +2569,7 @@ PP(pp_complement)
 #endif
 	for ( ; anum > 0; anum--, tmps++)
 	    *tmps = ~*tmps;
-
-	SETs(TARG);
+	SETTARG;
       }
       RETURN;
     }
@@ -3514,7 +3513,7 @@ PP(pp_crypt)
 #   else
     sv_setpv(TARG, PerlProc_crypt(tmps, SvPV_nolen_const(right)));
 #   endif
-    SETs(TARG);
+    SETTARG;
     RETURN;
 #else
     DIE(aTHX_
@@ -3899,9 +3898,7 @@ PP(pp_quotemeta)
     }
     else
 	sv_setpvn(TARG, s, len);
-    SETs(TARG);
-    if (SvSMAGICAL(TARG))
-	mg_set(TARG);
+    SETTARG;
     RETURN;
 }
 
diff --git a/t/op/taint.t b/t/op/taint.t
index f578423..29fc436 100755
--- a/t/op/taint.t
+++ b/t/op/taint.t
@@ -17,7 +17,7 @@ use Config;
 use File::Spec::Functions;
 
 BEGIN { require './test.pl'; }
-plan tests => 267;
+plan tests => 271;
 
 $| = 1;
 
@@ -1252,6 +1252,21 @@ foreach my $ord (78, 163, 256) {
     ok(!tainted($1), "\\S match with chr $ord");
 }
 
+{
+    # 59998
+    sub cr { my $x = crypt($_[0], $_[1]); $x }
+    sub co { my $x = ~$_[0]; $x }
+    my ($a, $b);
+    $a = cr('hello', 'foo' . $TAINT);
+    $b = cr('hello', 'foo');
+    ok(tainted($a),  "tainted crypt");
+    ok(!tainted($b), "untainted crypt");
+    $a = co('foo' . $TAINT);
+    $b = co('foo');
+    ok(tainted($a),  "tainted complement");
+    ok(!tainted($b), "untainted complement");
+}
+
 # This may bomb out with the alarm signal so keep it last
 SKIP: {
     skip "No alarm()"  unless $Config{d_alarm};

-- 

Chip Salzenberg <chip@​pobox.com>

@p5pRT
Copy link
Author

p5pRT commented Nov 17, 2008

@chipdude - Status changed from 'open' to 'resolved'

@p5pRT p5pRT closed this as completed Nov 17, 2008
@p5pRT
Copy link
Author

p5pRT commented Nov 17, 2008

From @Tux

On Sun, 16 Nov 2008 23​:14​:30 -0800, Chip Salzenberg <chip@​pobox.com>
wrote​:

On Sun, Oct 19, 2008 at 01​:11​:22PM -0700, Frédéric Buclin wrote​:

$crypted3 is tainted despite the input string is detainted. Note that
$crypted2 is not tainted despite the code in bz_crypt() is very similar
to the one in bz_crypt2(). IMO, that's not the expected behavior.

I've tracked this down to a bug in two seemingly random opcodes​: crypt and
stringwise complement ("~"). The core of the bug is just missing set magic,
via the use of "SETs(TARG)" instead of the visually similar but more magical
"SETTARG".

Thanks, applied as change #34860
Also thanks for the clear explanation

The clearing of lexicals that usually is taken care of by pp_padsv is
bypassed when a lexical is the TARG of some other opcode. This is good,
*if* the given opcode calls the TARG's set magic like it's supposed to. But
the given opcodes didn't do so. Set magic on tainted values untaints them
[unless their inputs were tainted, of course]. Thereofre, missing set magic
in ppcode can 'create' lexicals tainted if they were tainted before in a
previous go-round.

The below patch fixes the reported bug with crypt, as well sa the discovered
bug with complement. And it also makes a small code cleanup, preferring
SETTARG to a longer equivalent.

Share and enjoy!

diff --git a/pp.c b/pp.c
index 739a457..166c315 100644
--- a/pp.c
+++ b/pp.c
@​@​ -2553,7 +2553,7 @​@​ PP(pp_complement)
sv_usepvn_flags(TARG, (char*)result, nchar, SV_HAS_TRAILING_NUL);
SvUTF8_off(TARG);
}
- SETs(TARG);
+ SETTARG;
RETURN;
}
#ifdef LIBERAL
@​@​ -2569,8 +2569,7 @​@​ PP(pp_complement)
#endif
for ( ; anum > 0; anum--, tmps++)
*tmps = ~*tmps;
-
- SETs(TARG);
+ SETTARG;
}
RETURN;
}
@​@​ -3514,7 +3513,7 @​@​ PP(pp_crypt)
# else
sv_setpv(TARG, PerlProc_crypt(tmps, SvPV_nolen_const(right)));
# endif
- SETs(TARG);
+ SETTARG;
RETURN;
#else
DIE(aTHX_
@​@​ -3899,9 +3898,7 @​@​ PP(pp_quotemeta)
}
else
sv_setpvn(TARG, s, len);
- SETs(TARG);
- if (SvSMAGICAL(TARG))
- mg_set(TARG);
+ SETTARG;
RETURN;
}

diff --git a/t/op/taint.t b/t/op/taint.t
index f578423..29fc436 100755
--- a/t/op/taint.t
+++ b/t/op/taint.t
@​@​ -17,7 +17,7 @​@​ use Config;
use File​::Spec​::Functions;

BEGIN { require './test.pl'; }
-plan tests => 267;
+plan tests => 271;

$| = 1;

@​@​ -1252,6 +1252,21 @​@​ foreach my $ord (78, 163, 256) {
ok(!tainted($1), "\\S match with chr $ord");
}

+{
+ # 59998
+ sub cr { my $x = crypt($_[0], $_[1]); $x }
+ sub co { my $x = ~$_[0]; $x }
+ my ($a, $b);
+ $a = cr('hello', 'foo' . $TAINT);
+ $b = cr('hello', 'foo');
+ ok(tainted($a), "tainted crypt");
+ ok(!tainted($b), "untainted crypt");
+ $a = co('foo' . $TAINT);
+ $b = co('foo');
+ ok(tainted($a), "tainted complement");
+ ok(!tainted($b), "untainted complement");
+}
+
# This may bomb out with the alarm signal so keep it last
SKIP​: {
skip "No alarm()" unless $Config{d_alarm};

--
H.Merijn Brand Amsterdam Perl Mongers http​://amsterdam.pm.org/
using & porting perl 5.6.2, 5.8.x, 5.10.x, 5.11.x on HP-UX 10.20, 11.00,
11.11, 11.23, and 11.31, SuSE 10.1, 10.2, and 10.3, AIX 5.2, and Cygwin.
http​://mirrors.develooper.com/hpux/ http​://www.test-smoke.org/
http​://qa.perl.org http​://www.goldmark.org/jeff/stupid-disclaimers/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant