返回 DeepSeek-Reasonix
confinedread_unix.go
根目录 / internal / fileutil / confinedread_unix.go
1 //go:build !windows && !plan9
2
3 package fileutil
4
5 import (
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10 "syscall"
11 )
12
13 // OpenFileBeneath opens rel first, validates the identity held by that open
14 // descriptor against a resolved path beneath root, then returns the same
15 // descriptor for reading. A concurrent symlink or directory swap either fails
16 // the identity check or happens after the descriptor has pinned the safe file.
17 func OpenFileBeneath(root, rel string) (*os.File, error) {
18 root = filepath.Clean(strings.TrimSpace(root))
19 if root == "" || root == "." {
20 return nil, fmt.Errorf("workspace root is empty")
21 }
22 rel, err := confinedRelativePath(rel)
23 if err != nil {
24 return nil, err
25 }
26 rootFile, err := os.Open(root)
27 if err != nil {
28 return nil, fmt.Errorf("open workspace root: %w", err)
29 }
30 defer rootFile.Close()
31 openedRoot, err := rootFile.Stat()
32 if err != nil {
33 return nil, fmt.Errorf("stat workspace root: %w", err)
34 }
35 abs := filepath.Join(root, rel)
36 // O_NONBLOCK prevents a workspace FIFO from stalling admission before the
37 // caller can inspect and reject its descriptor type. It is inert for regular
38 // files, which are the only pinned-context targets accepted by the caller.
39 file, err := os.OpenFile(abs, os.O_RDONLY|syscall.O_NONBLOCK, 0)
40 if err != nil {
41 return nil, err
42 }
43 fail := func(err error) (*os.File, error) {
44 _ = file.Close()
45 return nil, err
46 }
47 opened, err := file.Stat()
48 if err != nil {
49 return fail(err)
50 }
51 resolvedRoot, err := filepath.EvalSymlinks(root)
52 if err != nil {
53 return fail(fmt.Errorf("resolve workspace root: %w", err))
54 }
55 currentRoot, err := os.Stat(resolvedRoot)
56 if err != nil {
57 return fail(fmt.Errorf("stat resolved workspace root: %w", err))
58 }
59 if !os.SameFile(openedRoot, currentRoot) {
60 return fail(fmt.Errorf("workspace root changed while file was being opened"))
61 }
62 resolvedTarget, err := filepath.EvalSymlinks(abs)
63 if err != nil {
64 return fail(fmt.Errorf("resolve workspace file: %w", err))
65 }
66 resolvedRel, err := filepath.Rel(resolvedRoot, resolvedTarget)
67 if err != nil || resolvedRel == ".." || strings.HasPrefix(resolvedRel, ".."+string(filepath.Separator)) {
68 return fail(fmt.Errorf("file resolves outside workspace root"))
69 }
70 current, err := os.Stat(resolvedTarget)
71 if err != nil {
72 return fail(err)
73 }
74 if !os.SameFile(opened, current) {
75 return fail(fmt.Errorf("workspace path changed while it was being opened"))
76 }
77 return file, nil
78 }
79
79 lines GO