fix: skip inaccessible children when listing directories (#5958)

This commit is contained in:
Puneet Dixit 2026-06-04 16:19:07 +05:30 committed by GitHub
parent 1086903c14
commit 7b7ff8ae8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 144 additions and 2 deletions

View file

@ -18,6 +18,7 @@ import (
"path"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
@ -452,8 +453,7 @@ func (i *FileInfo) addSubtitle(fPath string) {
}
func (i *FileInfo) readListing(checker rules.Checker, readHeader bool, calcImgRes bool) error {
afs := &afero.Afero{Fs: i.Fs}
dir, err := afs.ReadDir(i.Path)
dir, err := readDir(i.Fs, i.Path)
if err != nil {
return err
}
@ -535,3 +535,56 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool, calcImgRe
i.Listing = listing
return nil
}
func readDir(afs afero.Fs, dirname string) ([]os.FileInfo, error) {
dir, err := afero.ReadDir(afs, dirname)
if err == nil {
return dir, nil
}
dir, fallbackErr := readDirNames(afs, dirname)
if fallbackErr != nil {
return nil, err
}
return dir, nil
}
func readDirNames(afs afero.Fs, dirname string) ([]os.FileInfo, error) {
file, err := afs.Open(dirname)
if err != nil {
return nil, err
}
names, err := file.Readdirnames(-1)
if closeErr := file.Close(); err == nil && closeErr != nil {
err = closeErr
}
if err != nil {
return nil, err
}
sort.Strings(names)
dir := make([]os.FileInfo, 0, len(names))
for _, name := range names {
fPath := path.Join(dirname, name)
info, err := lstatIfPossible(afs, fPath)
if err != nil {
log.Printf("Skipping inaccessible file %s: %v", fPath, err)
continue
}
dir = append(dir, info)
}
return dir, nil
}
func lstatIfPossible(afs afero.Fs, name string) (os.FileInfo, error) {
if lstaterFs, ok := afs.(afero.Lstater); ok {
info, _, err := lstaterFs.LstatIfPossible(name)
return info, err
}
return afs.Stat(name)
}

View file

@ -2,6 +2,7 @@ package files
import (
"os"
"path"
"path/filepath"
"testing"
@ -131,3 +132,91 @@ func TestStatRejectsLinkedAncestorEscape(t *testing.T) {
t.Fatalf("expected in-scope file to be served, got %v", err)
}
}
type allowAllChecker struct{}
func (allowAllChecker) Check(string) bool {
return true
}
type inaccessibleChildFs struct {
afero.Fs
child string
}
func (fs inaccessibleChildFs) Open(name string) (afero.File, error) {
file, err := fs.Fs.Open(name)
if err != nil {
return nil, err
}
if path.Clean(name) == "/" {
return inaccessibleChildDir{File: file}, nil
}
return file, nil
}
func (fs inaccessibleChildFs) Stat(name string) (os.FileInfo, error) {
if path.Clean(name) == fs.child {
return nil, os.ErrPermission
}
return fs.Fs.Stat(name)
}
func (fs inaccessibleChildFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
if path.Clean(name) == fs.child {
return nil, false, os.ErrPermission
}
if lstater, ok := fs.Fs.(afero.Lstater); ok {
return lstater.LstatIfPossible(name)
}
info, err := fs.Fs.Stat(name)
return info, false, err
}
type inaccessibleChildDir struct {
afero.File
}
func (dir inaccessibleChildDir) Readdir(int) ([]os.FileInfo, error) {
return nil, os.ErrPermission
}
func TestReadListingSkipsInaccessibleChildren(t *testing.T) {
memFs := afero.NewMemMapFs()
for _, dir := range []string{"/media", "/proton-mount"} {
if err := memFs.Mkdir(dir, 0o755); err != nil {
t.Fatal(err)
}
}
file, err := NewFileInfo(&FileOptions{
Fs: inaccessibleChildFs{Fs: memFs, child: "/proton-mount"},
Path: "/",
Expand: true,
Checker: allowAllChecker{},
})
if err != nil {
t.Fatal(err)
}
if file.Listing == nil {
t.Fatal("expected root listing")
}
if got := len(file.Items); got != 1 {
t.Fatalf("expected one accessible child, got %d", got)
}
if got := file.Items[0].Name; got != "media" {
t.Fatalf("expected accessible child to be listed, got %q", got)
}
if got := file.NumDirs; got != 1 {
t.Fatalf("expected one listed directory, got %d", got)
}
}