carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package privatefile
4
5import (
6 "io"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "golang.org/x/sys/unix"
12)
13
14// Walk using retained directory descriptors so a renamed ancestor cannot turn
15// a checked path into a symlink traversal.
16func parent(path string) (*os.File, string, error) {
17 if !filepath.IsAbs(path) || filepath.Clean(path) != path || path == "/" {
18 return nil, "", ErrUnavailable
19 }
20 parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
21 fd, err := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
22 if err != nil {
23 return nil, "", ErrUnavailable
24 }
25 for _, part := range parts[:len(parts)-1] {
26 next, e := unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
27 unix.Close(fd)
28 if e != nil {
29 return nil, "", ErrUnavailable
30 }
31 fd = next
32 }
33 return os.NewFile(uintptr(fd), "private directory"), parts[len(parts)-1], nil
34}
35func valid(f *os.File, max int) bool {
36 var st unix.Stat_t
37 return unix.Fstat(int(f.Fd()), &st) == nil && st.Mode&unix.S_IFMT == unix.S_IFREG && st.Mode&07777 == 0600 && st.Uid == uint32(os.Geteuid()) && st.Nlink == 1 && st.Size >= 0 && st.Size <= int64(max)
38}
39func read(path string, max int) ([]byte, error) {
40 if max <= 0 || max == int(^uint(0)>>1) {
41 return nil, ErrUnavailable
42 }
43 p, name, e := parent(path)
44 if e != nil {
45 return nil, ErrUnavailable
46 }
47 defer p.Close()
48 fd, e := unix.Openat(int(p.Fd()), name, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
49 if e != nil {
50 return nil, ErrUnavailable
51 }
52 f := os.NewFile(uintptr(fd), "private input")
53 if !valid(f, max) {
54 f.Close()
55 return nil, ErrUnavailable
56 }
57 b, e := io.ReadAll(io.LimitReader(f, int64(max)+1))
58 ok := valid(f, max)
59 ce := f.Close()
60 if e != nil || ce != nil || !ok || len(b) > max {
61 clear(b)
62 return nil, ErrUnavailable
63 }
64 return b, nil
65}
66
67type durableFile interface {
68 Write([]byte) (int, error)
69 Sync() error
70 Close() error
71}
72
73func finish(f durableFile, p interface{ Sync() error }, b []byte) error {
74 n, e := f.Write(b)
75 if e != nil || n != len(b) {
76 f.Close()
77 return ErrUnavailable
78 }
79 if f.Sync() != nil {
80 f.Close()
81 return ErrUnavailable
82 }
83 if f.Close() != nil {
84 return ErrUnavailable
85 }
86 if p.Sync() != nil {
87 return ErrUnavailable
88 }
89 return nil
90}
91func create(path string, b []byte, max int) error {
92 if max <= 0 || len(b) > max {
93 return ErrUnavailable
94 }
95 p, name, e := parent(path)
96 if e != nil {
97 return ErrUnavailable
98 }
99 defer p.Close()
100 var st unix.Stat_t
101 if unix.Fstat(int(p.Fd()), &st) != nil || st.Uid != uint32(os.Geteuid()) || st.Mode&0077 != 0 {
102 return ErrUnavailable
103 }
104 fd, e := unix.Openat(int(p.Fd()), name, unix.O_WRONLY|unix.O_CREAT|unix.O_EXCL|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0600)
105 if e != nil {
106 return ErrUnavailable
107 }
108 f := os.NewFile(uintptr(fd), "private output")
109 if !valid(f, max) {
110 f.Close()
111 return ErrUnavailable
112 }
113 return finish(f, p, b)
114}
115