16
16
17
17
(* This module provides (optional) sandboxing, allowing operations to be restricted to a subtree.
18
18
19
- For now, sandboxed directories use realpath and [O_NOFOLLOW], which is probably quite slow,
20
- and requires duplicating a load of path lookup logic from the kernel.
21
- It might be better to hold a directory FD rather than a path.
22
- On FreeBSD we could use O_RESOLVE_BENEATH and let the OS handle everything for us.
23
- On other systems we would have to resolve one path component at a time. *)
19
+ On FreeBSD we use O_RESOLVE_BENEATH and let the OS handle everything for us.
20
+ On other systems we resolve one path component at a time. *)
24
21
25
22
open Eio.Std
26
23
27
24
module Fd = Eio_unix. Fd
28
25
26
+ (* When renaming, we get a plain [Eio.Fs.dir]. We need extra access to check
27
+ that the new location is within its sandbox. *)
28
+ type (_, _, _) Eio.Resource.pi + = Posix_dir : ('t , 't -> Low_level .dir_fd , [> `Posix_dir ]) Eio.Resource .pi
29
+
30
+ let as_posix_dir (Eio.Resource. T (t , ops )) =
31
+ match Eio.Resource. get_opt ops Posix_dir with
32
+ | None -> None
33
+ | Some fn -> Some (fn t)
34
+
29
35
module rec Dir : sig
30
36
include Eio.Fs.Pi. DIR
31
37
32
- val v : label :string -> sandbox :bool -> string -> t
33
-
34
- val resolve : t -> string -> string
35
- (* * [resolve t path] returns the real path that should be used to access [path].
36
- For sandboxes, this is [realpath path] (and it checks that it is within the sandbox).
37
- For unrestricted access, this returns [path] unchanged.
38
- @raise Eio.Fs.Permission_denied if sandboxed and [path] is outside of [dir_path]. *)
38
+ val v : label :string -> path :string -> Low_level .dir_fd -> t
39
39
40
- val with_parent_dir : t -> string -> (Fd .t option -> string -> 'a ) -> 'a
41
- (* * [with_parent_dir t path fn] runs [fn dir_fd rel_path],
42
- where [rel_path] accessed relative to [dir_fd] gives access to [path].
43
- For unrestricted access, this just runs [fn None path].
44
- For sandboxes, it opens the parent of [path] as [dir_fd] and runs [fn (Some dir_fd) (basename path)]. *)
40
+ val fd : t -> Low_level .dir_fd
45
41
end = struct
46
42
type t = {
43
+ fd : Low_level .dir_fd ;
47
44
dir_path : string ;
48
- sandbox : bool ;
49
45
label : string ;
50
- mutable closed : bool ;
51
46
}
52
47
53
- let resolve t path =
54
- if t.sandbox then (
55
- if t.closed then Fmt. invalid_arg " Attempt to use closed directory %S" t.dir_path;
56
- if Filename. is_relative path then (
57
- let dir_path = Err. run Low_level. realpath t.dir_path in
58
- let full = Err. run Low_level. realpath (Filename. concat dir_path path) in
59
- let prefix_len = String. length dir_path + 1 in
60
- if String. length full > = prefix_len && String. sub full 0 prefix_len = dir_path ^ Filename. dir_sep then
61
- full
62
- else if full = dir_path then
63
- full
64
- else
65
- raise @@ Eio.Fs. err (Permission_denied (Err. Outside_sandbox (full, dir_path)))
66
- ) else (
67
- raise @@ Eio.Fs. err (Permission_denied Err. Absolute_path )
68
- )
69
- ) else path
70
-
71
- let with_parent_dir t path fn =
72
- if t.sandbox then (
73
- if t.closed then Fmt. invalid_arg " Attempt to use closed directory %S" t.dir_path;
74
- let dir, leaf = Filename. dirname path, Filename. basename path in
75
- let dir, leaf =
76
- if leaf = " .." then path, " ."
77
- else dir, leaf
78
- in
79
- let dir = resolve t dir in
80
- Switch. run ~name: " with_parent_dir" @@ fun sw ->
81
- let dirfd = Low_level. openat ~sw ~mode: 0 dir Low_level.Open_flags. (directory + rdonly + nofollow) in
82
- fn (Some dirfd) leaf
83
- ) else fn None path
84
-
85
- let v ~label ~sandbox dir_path = { dir_path; sandbox; label; closed = false }
48
+ let fd t = t.fd
86
49
87
- (* Sandboxes use [O_NOFOLLOW] when opening files ([resolve] already removed any symlinks).
88
- This avoids a race where symlink might be added after [realpath] returns. *)
89
- let opt_nofollow t =
90
- if t.sandbox then Low_level.Open_flags. nofollow else Low_level.Open_flags. empty
50
+ let v ~label ~path :dir_path fd = { fd; dir_path; label }
91
51
92
52
let open_in t ~sw path =
93
- let fd = Err. run (Low_level. openat ~mode: 0 ~sw (resolve t path)) Low_level.Open_flags. (opt_nofollow t + rdonly) in
53
+ let fd = Err. run (Low_level. openat ~mode: 0 ~sw t.fd path) Low_level.Open_flags. rdonly in
94
54
(Flow. of_fd fd :> Eio.File.ro_ty Eio.Resource.t )
95
55
96
- let rec open_out t ~sw ~append ~create path =
56
+ let open_out t ~sw ~append ~create path =
97
57
let mode, flags =
98
58
match create with
99
59
| `Never -> 0 , Low_level.Open_flags. empty
@@ -102,73 +62,44 @@ end = struct
102
62
| `Exclusive perm -> perm, Low_level.Open_flags. (creat + excl)
103
63
in
104
64
let flags = if append then Low_level.Open_flags. (flags + append) else flags in
105
- let flags = Low_level.Open_flags. (flags + rdwr + opt_nofollow t) in
106
- match
107
- with_parent_dir t path @@ fun dirfd path ->
108
- Low_level. openat ?dirfd ~sw ~mode path flags
109
- with
65
+ let flags = Low_level.Open_flags. (flags + rdwr) in
66
+ match Low_level. openat ~sw ~mode t.fd path flags with
110
67
| fd -> (Flow. of_fd fd :> Eio.File.rw_ty r )
111
- | exception Unix. Unix_error (ELOOP, _ , _ ) ->
112
- (* The leaf was a symlink (or we're unconfined and the main path changed, but ignore that).
113
- A leaf symlink might be OK, but we need to check it's still in the sandbox.
114
- todo: possibly we should limit the number of redirections here, like the kernel does. *)
115
- let target = Unix. readlink path in
116
- let full_target =
117
- if Filename. is_relative target then
118
- Filename. concat (Filename. dirname path) target
119
- else target
120
- in
121
- open_out t ~sw ~append ~create full_target
122
68
| exception Unix. Unix_error (code , name , arg ) ->
123
69
raise (Err. wrap code name arg)
124
70
125
71
let mkdir t ~perm path =
126
- with_parent_dir t path @@ fun dirfd path ->
127
- Err. run (Low_level. mkdir ?dirfd ~mode: perm) path
72
+ Err. run (Low_level. mkdir ~mode: perm t.fd) path
128
73
129
74
let unlink t path =
130
- with_parent_dir t path @@ fun dirfd path ->
131
- Err. run (Low_level. unlink ?dirfd ~dir: false ) path
75
+ Err. run (Low_level. unlink ~dir: false t.fd) path
132
76
133
77
let rmdir t path =
134
- with_parent_dir t path @@ fun dirfd path ->
135
- Err. run (Low_level. unlink ?dirfd ~dir: true ) path
78
+ Err. run (Low_level. unlink ~dir: true t.fd) path
136
79
137
80
let stat t ~follow path =
138
81
let buf = Low_level. create_stat () in
139
- if follow then (
140
- Err. run (Low_level. fstatat ~buf ~follow: true ) (resolve t path);
141
- ) else (
142
- with_parent_dir t path @@ fun dirfd path ->
143
- Err. run (Low_level. fstatat ~buf ?dirfd ~follow: false ) path;
144
- );
82
+ Err. run (Low_level. fstatat ~buf ~follow t.fd) path;
145
83
Flow. eio_of_stat buf
146
84
147
85
let read_dir t path =
148
- (* todo: need fdopendir here to avoid races *)
149
- let path = resolve t path in
150
- Err. run Low_level. readdir path
86
+ Err. run (Low_level. readdir t.fd) path
151
87
|> Array. to_list
152
88
153
89
let read_link t path =
154
- with_parent_dir t path @@ fun dirfd path ->
155
- Err. run (Low_level. read_link ?dirfd) path
90
+ Err. run (Low_level. read_link t.fd) path
156
91
157
92
let rename t old_path new_dir new_path =
158
- match Handler. as_posix_dir new_dir with
93
+ match as_posix_dir new_dir with
159
94
| None -> invalid_arg " Target is not an eio_posix directory!"
160
- | Some new_dir ->
161
- with_parent_dir t old_path @@ fun old_dir old_path ->
162
- with_parent_dir new_dir new_path @@ fun new_dir new_path ->
163
- Err. run (Low_level. rename ?old_dir old_path ?new_dir) new_path
164
-
165
- let close t = t.closed < - true
95
+ | Some new_dir -> Err. run (Low_level. rename t.fd old_path new_dir) new_path
166
96
167
97
let open_dir t ~sw path =
168
- Switch. check sw;
98
+ let flags = Low_level.Open_flags. (rdonly + directory +? path) in
99
+ let fd = Err. run (Low_level. openat ~sw ~mode: 0 t.fd path) flags in
169
100
let label = Filename. basename path in
170
- let d = v ~label (resolve t path) ~sandbox: true in
171
- Switch. on_release sw ( fun () -> close d);
101
+ let full_path = if Filename. is_relative path then Filename. concat t.dir_path path else path in
102
+ let d = v ~label ~path: full_path ( Fd fd) in
172
103
Eio.Resource. T (d, Handler. v)
173
104
174
105
let pp f t = Fmt. string f (String. escaped t.label)
@@ -190,24 +121,13 @@ end = struct
190
121
end
191
122
and Handler : sig
192
123
val v : (Dir .t , [`Dir | `Close ]) Eio.Resource .handler
193
-
194
- val as_posix_dir : [> `Dir ] r -> Dir .t option
195
124
end = struct
196
- (* When renaming, we get a plain [Eio.Fs.dir]. We need extra access to check
197
- that the new location is within its sandbox. *)
198
- type (_, _, _) Eio.Resource.pi + = Posix_dir : ('t , 't -> Dir .t , [> `Posix_dir ]) Eio.Resource .pi
199
-
200
- let as_posix_dir (Eio.Resource. T (t , ops )) =
201
- match Eio.Resource. get_opt ops Posix_dir with
202
- | None -> None
203
- | Some fn -> Some (fn t)
204
-
205
125
let v = Eio.Resource. handler [
206
126
H (Eio.Fs.Pi. Dir , (module Dir ));
207
- H (Posix_dir , Fun. id );
127
+ H (Posix_dir , Dir. fd );
208
128
]
209
129
end
210
130
211
131
(* Full access to the filesystem. *)
212
- let fs = Eio.Resource. T (Dir. v ~label: " fs" ~sandbox: false " ." , Handler. v)
213
- let cwd = Eio.Resource. T (Dir. v ~label: " cwd" ~sandbox: true " ." , Handler. v)
132
+ let fs = Eio.Resource. T (Dir. v ~label: " fs" ~path: " ." Fs , Handler. v)
133
+ let cwd = Eio.Resource. T (Dir. v ~label: " cwd" ~path: " ." Cwd , Handler. v)
0 commit comments