Subject: | Net::SSH::Perl w/o Crypt::RSA is subject to infinite recursion w/perl 5.8 or earlier |
Net::SSH::Perl is fond of using the eval "use
ModuleWhichMayNotBePresent;" idiom, particularly when it comes to
detecting if RSA support is available or not, but with perl 5.8 and
earlier this idiom can have surprising and unintended consequences. For
example, consider:
[8]snapdragon<~/show/>ls . A
.:
A A.pm t.pl
A:
B.pm
[9]snapdragon<~/show/>cat t.pl
use A;
eval { A->x; };
print "First attempt fails with: $@----\n" if ($@);
A->x;
[10]snapdragon<~/show/>cat A.pm
package A;
use strict;
use warnings;
sub x {
eval "use A::B;";
die "eval use of A::B from A::x failed: $@" if ($@);
print "eval use of A::B from A::x did not fail\n";
A::B->z;
}
sub z { print "well hello there stranger!\n"; }
1;
[11]snapdragon<~/show/>cat A/B.pm
package A::B;
use strict;
use warnings;
use A;
use base 'A';
use ClassThatDoesNotExist;
sub z { print "we'll never get this far\n"; }
1;
[12]snapdragon<~/show/>perl -v
This is perl, v5.8.4 built for i386-linux-thread-multi
Copyright 1987-2004, Larry Wall
Perl may be copied only under the terms of either the Artistic License
or the
GNU General Public License, which may be found in the Perl 5 source kit.
Complete documentation for Perl, including FAQ lists, should be found on
this system using `man perl' or `perldoc perl'. If you have access to the
Internet, point your browser at http://www.perl.com/, the Perl Home Page.
[13]snapdragon<~/show/>perl t.pl
First attempt fails with: eval use of A::B from A::x failed: Can't
locate ClassThatDoesNotExist.pm in @INC (@INC contains: /etc/perl
/usr/local/lib/perl/5.8.4 /usr/local/share/perl/5.8.4 /usr/lib/perl5
/usr/share/perl5 /usr/lib/perl/5.8 /usr/share/perl/5.8
/usr/local/lib/site_perl .) at A/B.pm line 8.
BEGIN failed--compilation aborted at A/B.pm line 8.
Compilation failed in require at (eval 1) line 1.
BEGIN failed--compilation aborted at (eval 1) line 1.
----
eval use of A::B from A::x did not fail
well hello there stranger!
The second time A::x is called $@ is false, and because of the ordering
of the use base 'A'; statement in B.pm, the z subroutine from A.pm is
inherited, but never overridden. With perl 5.10, the situation has
improved and things don't get as out of hand:
[287]deadhour<~/show/>perl -v
This is perl, v5.10.0 built for i486-linux-gnu-thread-multi
Copyright 1987-2007, Larry Wall
Perl may be copied only under the terms of either the Artistic License
or the
GNU General Public License, which may be found in the Perl 5 source kit.
Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl". If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.
[288]deadhour<~/show/>perl t.pl
First attempt fails with: eval use of A::B from A::x failed: Can't
locate ClassThatDoesNotExist.pm in @INC (@INC contains: /etc/perl
/usr/local/lib/perl/5.10.0 /usr/local/share/perl/5.10.0 /usr/lib/perl5
/usr/share/perl5 /usr/lib/perl/5.10 /usr/share/perl/5.10
/usr/local/lib/site_perl .) at A/B.pm line 8.
BEGIN failed--compilation aborted at A/B.pm line 8.
Compilation failed in require at (eval 1) line 1.
BEGIN failed--compilation aborted at (eval 1) line 1.
----
eval use of A::B from A::x failed: Attempt to reload A/B.pm aborted.
Compilation failed in require at (eval 3) line 1.
BEGIN failed--compilation aborted at (eval 3) line 1.
So, what, you ask, does this have to do with Net::SSH::Perl? Well, take
a look at Net::SSH::Perl::Key, specifically at the definition of the
read_private subroutine (which happens to be done in a BEGIN block using
a method factory of sorts). Now poke around in that module and notice
how many places the eval "use $class;" idiom is present. Finally, take
a look at the Net::SSH::Perl::Key::RSA class paying special attention to
the order of use statements. In the RSA class, use base qw(
Net::SSH::Perl::Key ); is placed before use Crypt::RSA; and this has the
unfortunate side-effect of letting the Net::SSH::Perl::Key::RSA inhert
Net::SSH::Perl::Key's read_private implementation, although the intent
is clearly that it should be overriden latter on...except if Crypt::RSA
isn't installed, that never happens. This sets us up for the situation
where Net::SSH::Perl::Key's read_private is called from
Net::SSH::Perl::Key::RSA, which results in run-away recursion.
For that to happen we have to twice attempt to eval "use
Net::SSH::Perl::Key::RSA;" in a single session when Crypt::RSA hasn't
been installed, which unfortuately is all too possible to do accidently.
A user with a RSA identity configured (or both RSA and DSA identities
available), connecting to a host which attempts a ssh-rsa key exchange
will trigger just a situation.
I don't really have a suggestion for a better run-time class detection
idiom (except to say it'd probably work better with older perl versions
if the idiom was forced to evaluate once at compile time and have the
runtime logic refer to some global indicator, instead of expecting
repeated evals to do something they're not going to do). At the very
least, in the Net::SSH::Perl::Key::RSA class, it'd be a good idea to
move the "use base qw( Net::SSH::Perl::Key );" statement until after the
use Crypt::RSA stuff so that if Crypt::RSA isn't installed, the hunk of
the Net::SSH::Perl::Key::RSA namespace that's left around doesn't have
anything nasty lurking in it. This would defang the infinite recursion
case and instead stick the user with a "Can't locate object method"
error, which is easier on resource consumption.