BATsh
view release on metacpan or search on metacpan
t/0019-tempfile-security.t view on Meta::CPAN
close(_CAP_OLD);
my $buf = '';
if (open(_CAP_RFH, "< $tmpfile")) {
local $/;
$buf = <_CAP_RFH>;
close(_CAP_RFH);
}
unlink $tmpfile;
$buf = '' unless defined $buf;
return $buf;
}
my @tests = (
##################################################################
# 1. Command substitution: symlink race
##################################################################
# TS01: plant a symlink at the exact path _subst_tempfile() will
# try first in a fresh process ('batsh_cap_<pid>_1_1.tmp': depth
# and sequence both start at 1 for the first substitution), then
# confirm BATsh's own output is still correct AND the canary file
# the symlink points at was never created/written by BATsh (i.e.
# BATsh did not follow the symlink; it must have skipped past it
# via O_EXCL retry and used a different file instead).
sub {
if (!$HAVE_SYMLINK) {
_ok(1, 'TS01: skipped (no POSIX symlink/O_EXCL semantics here)');
return;
}
unlink $CANARY;
my $trap = File::Spec->catfile($TMPDIR, "batsh_cap_${$}_1_1.tmp");
unlink $trap;
symlink($CANARY, $trap);
my $out = _capture(sub {
BATsh->run_string('echo $(echo hello)');
});
$out =~ s/[\r\n]+\z//;
my $canary_written = (-e $CANARY) ? 1 : 0;
unlink $trap unless -e $CANARY; # remove dangling symlink, keep canary if written (for diagnosis)
_ok($out eq 'hello' && !$canary_written,
"TS01: subst does not follow pre-planted symlink (out=[$out] canary_written=$canary_written)");
},
##################################################################
# 2. Command substitution: file mode
##################################################################
# TS02: the capture file must be 0600 while it exists. We can't
# observe it mid-flight from outside the process, so we call the
# internal helper directly and check the mode of what it created.
sub {
if (!$POSIX_SEMANTICS) {
_ok(1, "TS02: skipped (POSIX mode bits not meaningful on $^O)");
return;
}
no strict 'refs';
my $path = &{"BATsh::SH::_subst_tempfile"}();
my $ok = 0;
if (defined $path && -f $path) {
my @st = stat($path);
$ok = (($st[2] & 07777) == 0600) ? 1 : 0;
}
close(*BATsh::SH::_SUBST_CAPFH) if defined fileno(*BATsh::SH::_SUBST_CAPFH);
unlink $path if defined $path;
_ok($ok, 'TS02: subst capture file created with mode 0600');
},
##################################################################
# 3. SH pipeline: symlink race
##################################################################
# TS03: plant a symlink at the exact path _shp_tempfile() will try
# first for the first stage of a fresh process's first pipeline
# ('batsh_shp_<pid>_1_0_1.tmp': pipe-depth 1, segment idx 0,
# sequence 1), then confirm the pipeline's output is still correct
# and the canary was never created.
sub {
if (!$HAVE_SYMLINK) {
_ok(1, 'TS03: skipped (no POSIX symlink/O_EXCL semantics here)');
return;
}
unlink $CANARY;
my $trap = File::Spec->catfile($TMPDIR, "batsh_shp_${$}_1_0_1.tmp");
unlink $trap;
symlink($CANARY, $trap);
my $out = _capture(sub {
BATsh->run_string('echo hello | perl -ne "print uc"');
});
$out =~ s/[\r\n]+\z//;
my $canary_written = (-e $CANARY) ? 1 : 0;
unlink $trap unless -e $CANARY;
_ok($out eq 'HELLO' && !$canary_written,
"TS03: pipe stage does not follow pre-planted symlink (out=[$out] canary_written=$canary_written)");
},
##################################################################
# 4. SH pipeline: file mode
##################################################################
sub {
if (!$POSIX_SEMANTICS) {
_ok(1, "TS04: skipped (POSIX mode bits not meaningful on $^O)");
return;
}
no strict 'refs';
my $stub = File::Spec->catfile($TMPDIR, "batsh_shp19_$$");
my $path = &{"BATsh::SH::_shp_tempfile"}($stub);
my $ok = 0;
if (defined $path && -f $path) {
my @st = stat($path);
$ok = (($st[2] & 07777) == 0600) ? 1 : 0;
}
close(*BATsh::SH::_SH_PIPE_WFH) if defined fileno(*BATsh::SH::_SH_PIPE_WFH);
unlink $path if defined $path;
_ok($ok, 'TS04: pipe stage file created with mode 0600');
},
##################################################################
# 5. No leftover temp files after a normal run
##################################################################
sub {
my @before = glob(File::Spec->catfile($TMPDIR, 'batsh_cap_*'));
push @before, glob(File::Spec->catfile($TMPDIR, 'batsh_shp_*'));
_capture(sub {
BATsh->run_string(
'echo $(echo a|perl -ne "print uc"); '
. 'echo b | perl -ne "print uc" | perl -ne "print lc"');
});
my @after = glob(File::Spec->catfile($TMPDIR, 'batsh_cap_*'));
push @after, glob(File::Spec->catfile($TMPDIR, 'batsh_shp_*'));
_ok(scalar(@after) <= scalar(@before),
'TS05: no stray capture/pipe temp files left after a normal run');
},
);
print "1.." . scalar(@tests) . "\n";
my ($run, $fail) = (0, 0);
sub _ok {
my ($ok, $name) = @_;
$run++; $fail++ unless $ok;
$name = '' unless defined $name;
print +($ok ? '' : 'not ') . "ok $run - $name\n";
}
$_->() for @tests;
unlink $CANARY;
END { $? = 1 if $fail }
( run in 0.740 second using v1.01-cache-2.11-cpan-14f38c9f855 )