From 4c2f3ed6bb8863c189d60af02be78d3d969a024e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Salvador=20Fandi=C3=B1o?= Date: Thu, 4 Jun 2026 13:48:45 +0200 Subject: [PATCH 1/2] Harden module loading --- MANIFEST | 1 + lib/Net/OpenSSH/ModuleLoader.pm | 15 +++++++-------- t/module-loader.t | 31 +++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 t/module-loader.t diff --git a/MANIFEST b/MANIFEST index 7def16c..e11f0ef 100644 --- a/MANIFEST +++ b/MANIFEST @@ -23,6 +23,7 @@ t/test_server_key.pub t/test_user_key t/test_user_key.pub t/known_hosts +t/module-loader.t t/quoting.t t/uri.t examples/expect.pl diff --git a/lib/Net/OpenSSH/ModuleLoader.pm b/lib/Net/OpenSSH/ModuleLoader.pm index 149a517..37f18c4 100644 --- a/lib/Net/OpenSSH/ModuleLoader.pm +++ b/lib/Net/OpenSSH/ModuleLoader.pm @@ -11,23 +11,22 @@ our @EXPORT = qw(_load_module); sub _load_module { my ($module, $version) = @_; + $module =~ /\A[A-Za-z_]\w*(?:::\w+)*\z/ + or croak "bad Perl module name $module"; $loaded_module{$module} ||= do { my $err; do { local ($@, $SIG{__DIE__}); - my $ok = eval "require $module; 1"; + (my $path = "$module.pm") =~ s!::!/!g; + my $ok = eval { require $path; 1 }; $err = $@; $ok; } or croak "unable to load Perl module $module: $err"; }; if (defined $version) { - my $mv = do { - local ($@, $SIG{__DIE__}); - eval "\$${module}::VERSION"; - } || 0; - (my $mv1 = $mv) =~ s/_\d*$//; - croak "$module version $version required, $mv is available" - if $mv1 < $version; + local ($@, $SIG{__DIE__}); + eval { $module->VERSION($version); 1 } + or croak $@ || "$module version $version required"; } 1 } diff --git a/t/module-loader.t b/t/module-loader.t new file mode 100644 index 0000000..6fddd9c --- /dev/null +++ b/t/module-loader.t @@ -0,0 +1,31 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 5; + +use Net::OpenSSH::ModuleLoader; + +ok(_load_module('strict'), 'loads a valid module name'); + +eval { _load_module('strict; die "boom"') }; +like($@, qr/bad Perl module name/, 'rejects unsafe module names'); + +eval { _load_module('strict', 999_999) }; +like($@, qr/strict version 999999 required|strict version 999999 required--this is only version/, + 'uses standard VERSION checks for too-new requirements'); + +{ + package Test::Net::OpenSSH::ModuleLoader::Versioned; + our $VERSION = '1.02'; +} + +$INC{'Test/Net/OpenSSH/ModuleLoader/Versioned.pm'} = __FILE__; + +ok(_load_module('Test::Net::OpenSSH::ModuleLoader::Versioned', '1.01'), + 'accepts a sufficient version'); + +eval { _load_module('Test::Net::OpenSSH::ModuleLoader::Versioned', '1.03') }; +like($@, qr/Test::Net::OpenSSH::ModuleLoader::Versioned version 1.03 required/, + 'rejects an insufficient version'); From 4e504a6ed3b4c3acb857702da3f0120104b20381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Salvador=20Fandi=C3=B1o?= Date: Thu, 4 Jun 2026 16:35:26 +0200 Subject: [PATCH 2/2] Handle undefined module names --- lib/Net/OpenSSH/ModuleLoader.pm | 1 + t/module-loader.t | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Net/OpenSSH/ModuleLoader.pm b/lib/Net/OpenSSH/ModuleLoader.pm index 37f18c4..48a5580 100644 --- a/lib/Net/OpenSSH/ModuleLoader.pm +++ b/lib/Net/OpenSSH/ModuleLoader.pm @@ -11,6 +11,7 @@ our @EXPORT = qw(_load_module); sub _load_module { my ($module, $version) = @_; + defined $module or croak "bad Perl module name"; $module =~ /\A[A-Za-z_]\w*(?:::\w+)*\z/ or croak "bad Perl module name $module"; $loaded_module{$module} ||= do { diff --git a/t/module-loader.t b/t/module-loader.t index 6fddd9c..8e3db42 100644 --- a/t/module-loader.t +++ b/t/module-loader.t @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 5; +use Test::More tests => 6; use Net::OpenSSH::ModuleLoader; @@ -12,6 +12,9 @@ ok(_load_module('strict'), 'loads a valid module name'); eval { _load_module('strict; die "boom"') }; like($@, qr/bad Perl module name/, 'rejects unsafe module names'); +eval { _load_module(undef) }; +like($@, qr/bad Perl module name/, 'rejects undefined module names without warnings'); + eval { _load_module('strict', 999_999) }; like($@, qr/strict version 999999 required|strict version 999999 required--this is only version/, 'uses standard VERSION checks for too-new requirements');