Reject special files before preparing inputs

This commit is contained in:
2026-08-29 16:13:36 +00:00
parent a68e8e31a4
commit e7e3bef1e4
4 changed files with 95 additions and 4 deletions

View File

@@ -635,6 +635,13 @@ func requireRegularReadableFile(path string, label string) error {
if strings.TrimSpace(path) == "" {
return fmt.Errorf("%s path is required", label)
}
declared, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("inspect %s %q: %w", label, path, err)
}
if declared.Mode()&os.ModeSymlink != 0 || !declared.Mode().IsRegular() {
return fmt.Errorf("%s %q is not a regular file without symlinks", label, path)
}
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("open %s %q: %w", label, path, err)
@@ -644,8 +651,13 @@ func requireRegularReadableFile(path string, label string) error {
if statErr != nil {
return fmt.Errorf("stat %s %q: %w", label, path, statErr)
}
if !info.Mode().IsRegular() {
return fmt.Errorf("%s %q is not a regular file", label, path)
current, currentErr := os.Lstat(path)
if currentErr != nil {
return fmt.Errorf("reinspect %s %q: %w", label, path, currentErr)
}
if !info.Mode().IsRegular() || current.Mode()&os.ModeSymlink != 0 || !current.Mode().IsRegular() ||
!os.SameFile(info, declared) || !os.SameFile(info, current) {
return fmt.Errorf("%s %q changed while being opened", label, path)
}
if closeErr != nil {
return fmt.Errorf("close %s %q: %w", label, path, closeErr)

View File

@@ -0,0 +1,45 @@
//go:build unix
package stage
import (
"context"
"path/filepath"
"strings"
"testing"
"time"
"gitea.maximumdirect.net/eric/narratio/internal/config"
"golang.org/x/sys/unix"
)
func TestPrepareStageRejectsSpellCatalogFIFOWithoutBlocking(t *testing.T) {
env, m := setupPrepareEnv(t)
root := filepath.Dir(env.Config.SessionPath)
writeFile(t, filepath.Join(root, "audio", "a.flac"), "audio")
env.Config.Session.Inputs.AudioFiles = []string{"./audio/a.flac"}
sourcePath := filepath.Join(root, "spells.fifo")
if err := unix.Mkfifo(sourcePath, 0o644); err != nil {
t.Fatalf("Mkfifo() error = %v", err)
}
env.Config.StableInputs.SpellCatalogFile = config.ResolvedInputFile{
Path: "./spells.fifo",
ConfigPath: env.Config.CampaignPath,
Source: "campaign_config",
}
done := make(chan error, 1)
go func() {
_, err := (prepareStage{}).Run(context.Background(), env, m)
done <- err
}()
select {
case err := <-done:
if err == nil || !strings.Contains(err.Error(), "not a regular file") {
t.Fatalf("prepare.Run() error = %v, want regular-file rejection", err)
}
case <-time.After(time.Second):
t.Fatal("prepare.Run() blocked while inspecting a FIFO spell catalog")
}
}