oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package provisioner
4
5import (
6 "errors"
7 "io"
8 "os"
9 "path/filepath"
10 "strings"
11
12 "golang.org/x/sys/unix"
13)
14
15var ErrRefused = errors.New("provisioner: refused")
16var ErrUnavailable = errors.New("provisioner: unavailable")
17
18// privateDirectory retains an opened directory so later operations cannot follow
19// a replacement path or a symlink supplied in a file name.
20type privateDirectory struct{ file *os.File }
21
22func openPrivateDirectory(path string) (*privateDirectory, error) {
23 if !filepath.IsAbs(path) || filepath.Clean(path) != path || path == "/" {
24 return nil, ErrRefused
25 }
26 fd, err := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
27 if err != nil {
28 return nil, ErrUnavailable
29 }
30 for _, part := range strings.Split(strings.TrimPrefix(path, "/"), "/") {
31 next, e := unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
32 unix.Close(fd)
33 if e != nil {
34 return nil, ErrRefused
35 }
36 fd = next
37 }
38 var stat unix.Stat_t
39 var fs unix.Statfs_t
40 if unix.Fstat(fd, &stat) != nil || unix.Fstatfs(fd, &fs) != nil {
41 unix.Close(fd)
42 return nil, ErrUnavailable
43 }
44 if stat.Uid != uint32(os.Getuid()) || stat.Mode&0777 != 0700 || (fs.Type != unix.EXT4_SUPER_MAGIC && fs.Type != unix.XFS_SUPER_MAGIC) {
45 unix.Close(fd)
46 return nil, ErrRefused
47 }
48 return &privateDirectory{os.NewFile(uintptr(fd), path)}, nil
49}
50func (d *privateDirectory) close() error { return d.file.Close() }
51func leaf(name string) bool {
52 return name != "" && name != "." && name != ".." && len(name) <= 128 && !strings.ContainsAny(name, "/\\\x00")
53}
54func (d *privateDirectory) create(name string, body []byte) error {
55 if !leaf(name) || len(body) > 128<<10 {
56 return ErrRefused
57 }
58 fd, e := unix.Openat(int(d.file.Fd()), name, unix.O_WRONLY|unix.O_CREAT|unix.O_EXCL|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0600)
59 if e != nil {
60 return ErrRefused
61 }
62 f := os.NewFile(uintptr(fd), name)
63 // A failed exclusive write remains present: recovery must validate or refuse
64 // the partial record instead of replacing evidence of an interrupted write.
65 _, e = f.Write(body)
66 if e == nil {
67 e = f.Sync()
68 }
69 closeErr := f.Close()
70 if e != nil || closeErr != nil || d.file.Sync() != nil {
71 return ErrUnavailable
72 }
73 return nil
74}
75func (d *privateDirectory) read(name string, limit int) ([]byte, error) {
76 if !leaf(name) || limit <= 0 || limit > 128<<10 {
77 return nil, ErrRefused
78 }
79 fd, e := unix.Openat(int(d.file.Fd()), name, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
80 if e != nil {
81 return nil, ErrRefused
82 }
83 f := os.NewFile(uintptr(fd), name)
84 defer f.Close()
85 var s unix.Stat_t
86 if unix.Fstat(fd, &s) != nil {
87 return nil, ErrUnavailable
88 }
89 if s.Mode&unix.S_IFMT != unix.S_IFREG || s.Mode&0777 != 0600 || s.Uid != uint32(os.Getuid()) || s.Nlink != 1 || s.Size > int64(limit) {
90 return nil, ErrRefused
91 }
92 body, e := io.ReadAll(io.LimitReader(f, int64(limit)+1))
93 if e != nil {
94 return nil, ErrUnavailable
95 }
96 if len(body) > limit {
97 return nil, ErrRefused
98 }
99 return body, nil
100}
101func (d *privateDirectory) lock() (*os.File, error) {
102 fd, e := unix.Openat(int(d.file.Fd()), "lock", unix.O_RDWR|unix.O_CREAT|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0600)
103 if e != nil {
104 return nil, ErrRefused
105 }
106 f := os.NewFile(uintptr(fd), "lock")
107 var s unix.Stat_t
108 if unix.Fstat(fd, &s) != nil || s.Mode&unix.S_IFMT != unix.S_IFREG || s.Mode&0777 != 0600 || s.Uid != uint32(os.Getuid()) || s.Nlink != 1 {
109 f.Close()
110 return nil, ErrRefused
111 }
112 if unix.Flock(fd, unix.LOCK_EX|unix.LOCK_NB) != nil {
113 f.Close()
114 return nil, ErrRefused
115 }
116 if f.Sync() != nil || d.file.Sync() != nil {
117 f.Close()
118 return nil, ErrUnavailable
119 }
120 return f, nil
121}
122
123func checkRegistryFile(d *privateDirectory) error {
124 fd, e := unix.Openat(int(d.file.Fd()), "registry.db", unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
125 if e != nil {
126 return ErrRefused
127 }
128 defer unix.Close(fd)
129 var s unix.Stat_t
130 if unix.Fstat(fd, &s) != nil {
131 return ErrUnavailable
132 }
133 if s.Mode&unix.S_IFMT != unix.S_IFREG || s.Mode&0777 != 0600 || s.Uid != uint32(os.Getuid()) || s.Nlink != 1 || s.Size > int64(maxRecords)*(128<<10) {
134 return ErrRefused
135 }
136 return nil
137}
138