diff --git a/lib/Test/MockFile.pm b/lib/Test/MockFile.pm index 6c5d4cf..1a4c79c 100644 --- a/lib/Test/MockFile.pm +++ b/lib/Test/MockFile.pm @@ -3752,6 +3752,13 @@ sub __symlink ($$) { return 0; } + # Permission check: symlink needs write+execute on parent dir + if ( defined $_mock_uid && !_check_parent_perms( $mock->{'path'}, 2 | 1 ) ) { + $! = EACCES; + _maybe_throw_autodie( 'symlink', @_ ); + return 0; + } + # Convert the mock to a symlink pointing to $oldname $mock->{'readlink'} = $oldname; $mock->{'mode'} = 07777 | S_IFLNK; @@ -3842,6 +3849,13 @@ sub __link ($$) { return 0; } + # Permission check: link needs write+execute on parent dir of destination + if ( defined $_mock_uid && !_check_parent_perms( $new_mock->{'path'}, 2 | 1 ) ) { + $! = EACCES; + _maybe_throw_autodie( 'link', @_ ); + return 0; + } + # Copy file attributes from source to destination $new_mock->{'contents'} = $source_mock->{'contents'}; $new_mock->{'has_content'} = 1; diff --git a/t/perms.t b/t/perms.t index 664da5f..88e4a1a 100644 --- a/t/perms.t +++ b/t/perms.t @@ -336,4 +336,48 @@ subtest 'open > on new file checks parent directory perms' => sub { } 1000, 1000; }; +# ========================================================================= +# symlink permission checks (needs write+exec on parent) +# ========================================================================= + +subtest 'symlink permission checks on parent directory' => sub { + my $parent = Test::MockFile->new_dir( '/perms/sdir', { mode => 0555, uid => 1000, gid => 1000 } ); + my $target = Test::MockFile->file('/perms/sdir/mylink'); + + with_user { + ok( !symlink( '/some/target', '/perms/sdir/mylink' ), 'cannot symlink in read-only parent dir' ); + is( $! + 0, EACCES, 'symlink errno is EACCES' ); + } 1000, 1000; + + my $parent2 = Test::MockFile->new_dir( '/perms/sdir2', { mode => 0755, uid => 1000, gid => 1000 } ); + my $target2 = Test::MockFile->file('/perms/sdir2/mylink2'); + + with_user { + ok( symlink( '/some/target', '/perms/sdir2/mylink2' ), 'can symlink in writable parent dir' ); + } 1000, 1000; +}; + +# ========================================================================= +# link permission checks (needs write+exec on parent of destination) +# ========================================================================= + +subtest 'link permission checks on parent directory' => sub { + my $parent = Test::MockFile->new_dir( '/perms/ldir', { mode => 0555, uid => 1000, gid => 1000 } ); + my $source = Test::MockFile->file( '/perms/ldir/src', 'data' ); + my $dest = Test::MockFile->file('/perms/ldir/hardlink'); + + with_user { + ok( !link( '/perms/ldir/src', '/perms/ldir/hardlink' ), 'cannot link in read-only parent dir' ); + is( $! + 0, EACCES, 'link errno is EACCES' ); + } 1000, 1000; + + my $parent2 = Test::MockFile->new_dir( '/perms/ldir2', { mode => 0755, uid => 1000, gid => 1000 } ); + my $source2 = Test::MockFile->file( '/perms/ldir2/src2', 'data' ); + my $dest2 = Test::MockFile->file('/perms/ldir2/hardlink2'); + + with_user { + ok( link( '/perms/ldir2/src2', '/perms/ldir2/hardlink2' ), 'can link in writable parent dir' ); + } 1000, 1000; +}; + done_testing();