···11+(* From Obuilder see License file at end of document *)
22+let ( / ) = Eio.Path.( / )
33+let ( // ) = Filename.concat
44+55+type config = { fast_sync : bool }
66+77+let get_machine () =
88+ let ch = Unix.open_process_in "uname -m" in
99+ let arch = input_line ch in
1010+ match Unix.close_process_in ch with
1111+ | Unix.WEXITED 0 -> String.trim arch
1212+ | _ -> failwith "Failed to get arch with 'uname -m'"
1313+1414+let get_arches () =
1515+ if Sys.unix then
1616+ match get_machine () with
1717+ | "x86_64" -> [ "SCMP_ARCH_X86_64"; "SCMP_ARCH_X86"; "SCMP_ARCH_X32" ]
1818+ | "aarch64" -> [ "SCMP_ARCH_AARCH64"; "SCMP_ARCH_ARM" ]
1919+ | _ -> []
2020+ else []
2121+2222+let secret_file id = "secret-" ^ string_of_int id
2323+2424+module Json_config = struct
2525+ let mount ?(options = []) ~ty ~src dst =
2626+ `Assoc
2727+ [
2828+ ("destination", `String dst);
2929+ ("type", `String ty);
3030+ ("source", `String src);
3131+ ("options", `List (List.map (fun x -> `String x) options));
3232+ ]
3333+3434+ let strings xs = `List (List.map (fun x -> `String x) xs)
3535+ let namespace x = `Assoc [ ("type", `String x) ]
3636+3737+ (* This is a subset of the capabilities that Docker uses by default.
3838+ These control what root can do in the container.
3939+ If the init process is non-root, permitted, effective and ambient sets are cleared.
4040+ See capabilities(7) for full details. *)
4141+ let default_linux_caps =
4242+ [
4343+ "CAP_CHOWN";
4444+ (* Make arbitrary changes to file UIDs and GIDs *)
4545+ "CAP_DAC_OVERRIDE";
4646+ (* Bypass file read, write, and execute permission checks. *)
4747+ "CAP_FSETID";
4848+ (* Set SUID/SGID bits. *)
4949+ "CAP_FOWNER";
5050+ (* Bypass permission checks. *)
5151+ "CAP_MKNOD";
5252+ (* Create special files using mknod. *)
5353+ "CAP_SETGID";
5454+ (* Make arbitrary manipulations of process GIDs. *)
5555+ "CAP_SETUID";
5656+ (* Make arbitrary manipulations of process UIDs. *)
5757+ "CAP_SETFCAP";
5858+ (* Set arbitrary capabilities on a file. *)
5959+ "CAP_SETPCAP";
6060+ (* Add any capability from bounding set to inheritable set. *)
6161+ "CAP_SYS_CHROOT";
6262+ (* Use chroot. *)
6363+ "CAP_KILL";
6464+ (* Bypass permission checks for sending signals. *)
6565+ "CAP_AUDIT_WRITE"
6666+ (* Write records to kernel auditing log. *)
6767+ (* Allowed by Docker, but disabled here (because we use host networking):
6868+ "CAP_NET_RAW"; (* Use RAW and PACKET sockets / bind to any address *)
6969+ "CAP_NET_BIND_SERVICE"; (* Bind a socket to Internet domain privileged ports. *)
7070+ *);
7171+ ]
7272+7373+ let seccomp_syscalls ~fast_sync =
7474+ if fast_sync then
7575+ [
7676+ `Assoc
7777+ [
7878+ (* Sync calls are pointless for the builder, because if the computer crashes then we'll
7979+ just throw the build dir away and start again. And btrfs sync is really slow.
8080+ Based on https://bblank.thinkmo.de/using-seccomp-to-filter-sync-operations.html
8181+ Note: requires runc >= v1.0.0-rc92. *)
8282+ ( "names",
8383+ strings
8484+ [
8585+ "fsync";
8686+ "fdatasync";
8787+ "msync";
8888+ "sync";
8989+ "syncfs";
9090+ "sync_file_range";
9191+ ] );
9292+ ("action", `String "SCMP_ACT_ERRNO");
9393+ ("errnoRet", `Int 0);
9494+ (* Return error "success" *)
9595+ ];
9696+ ]
9797+ else []
9898+9999+ let seccomp_policy =
100100+ let fields =
101101+ [
102102+ ("defaultAction", `String "SCMP_ACT_ALLOW");
103103+ ("syscalls", `List (seccomp_syscalls ~fast_sync:true));
104104+ ]
105105+ in
106106+ `Assoc fields
107107+108108+ type config = {
109109+ cwd : string;
110110+ argv : string list;
111111+ hostname : string;
112112+ network : string list;
113113+ user : int * int;
114114+ env : string list;
115115+ entrypoint : string option;
116116+ }
117117+118118+ let make { cwd; argv; hostname; network; user; env; entrypoint } ~config_dir
119119+ ~results_dir : Yojson.Safe.t =
120120+ assert (entrypoint = None);
121121+ let user =
122122+ let uid, gid = user in
123123+ `Assoc [ ("uid", `Int uid); ("gid", `Int gid) ]
124124+ in
125125+ let network_ns =
126126+ match network with
127127+ | [ "host" ] -> []
128128+ | [] -> [ "network" ]
129129+ | xs ->
130130+ Fmt.failwith "Unsupported network configuration %a"
131131+ Fmt.Dump.(list string)
132132+ xs
133133+ in
134134+ let namespaces = network_ns @ [ "pid"; "ipc"; "uts"; "mount" ] in
135135+ `Assoc
136136+ [
137137+ ("ociVersion", `String "1.0.1-dev");
138138+ ( "process",
139139+ `Assoc
140140+ [
141141+ ("terminal", `Bool false);
142142+ ("user", user);
143143+ ("args", strings argv);
144144+ ("env", strings env);
145145+ ("cwd", `String cwd);
146146+ ( "capabilities",
147147+ `Assoc
148148+ [
149149+ ("bounding", strings default_linux_caps);
150150+ (* Limits capabilities gained on execve. *)
151151+ ("effective", strings default_linux_caps);
152152+ (* Checked by kernel to decide access *)
153153+ ("inheritable", strings default_linux_caps);
154154+ (* Preserved across an execve (if root, or cap in ambient set) *)
155155+ ("permitted", strings default_linux_caps);
156156+ (* Limiting superset for the effective capabilities *)
157157+ ] );
158158+ ( "rlimits",
159159+ `List
160160+ [
161161+ `Assoc
162162+ [
163163+ ("type", `String "RLIMIT_NOFILE");
164164+ ("hard", `Int 1024);
165165+ ("soft", `Int 1024);
166166+ ];
167167+ ] );
168168+ ("noNewPrivileges", `Bool false);
169169+ ] );
170170+ ( "root",
171171+ `Assoc
172172+ [
173173+ ("path", `String (results_dir // "rootfs"));
174174+ ("readonly", `Bool false);
175175+ ] );
176176+ ("hostname", `String hostname);
177177+ ( "mounts",
178178+ `List
179179+ (mount "/proc"
180180+ ~options:
181181+ [ (* TODO: copy to others? *) "nosuid"; "noexec"; "nodev" ]
182182+ ~ty:"proc" ~src:"proc"
183183+ :: mount "/dev" ~ty:"tmpfs" ~src:"tmpfs"
184184+ ~options:[ "nosuid"; "strictatime"; "mode=755"; "size=65536k" ]
185185+ :: mount "/dev/pts" ~ty:"devpts" ~src:"devpts"
186186+ ~options:
187187+ [
188188+ "nosuid";
189189+ "noexec";
190190+ "newinstance";
191191+ "ptmxmode=0666";
192192+ "mode=0620";
193193+ "gid=5";
194194+ (* tty *)
195195+ ]
196196+ :: mount
197197+ "/sys"
198198+ (* This is how Docker does it. runc's default is a bit different. *)
199199+ ~ty:"sysfs" ~src:"sysfs"
200200+ ~options:[ "nosuid"; "noexec"; "nodev"; "ro" ]
201201+ :: mount "/sys/fs/cgroup" ~ty:"cgroup" ~src:"cgroup"
202202+ ~options:[ "ro"; "nosuid"; "noexec"; "nodev" ]
203203+ :: mount "/dev/shm" ~ty:"tmpfs" ~src:"shm"
204204+ ~options:
205205+ [ "nosuid"; "noexec"; "nodev"; "mode=1777"; "size=65536k" ]
206206+ :: mount "/dev/mqueue" ~ty:"mqueue" ~src:"mqueue"
207207+ ~options:[ "nosuid"; "noexec"; "nodev" ]
208208+ :: mount "/etc/hosts" ~ty:"bind" ~src:(config_dir // "hosts")
209209+ ~options:[ "ro"; "rbind"; "rprivate" ]
210210+ ::
211211+ (if network = [ "host" ] then
212212+ [
213213+ mount "/etc/resolv.conf" ~ty:"bind" ~src:"/etc/resolv.conf"
214214+ ~options:[ "ro"; "rbind"; "rprivate" ];
215215+ ]
216216+ else [])) );
217217+ ( "linux",
218218+ `Assoc
219219+ [
220220+ ("namespaces", `List (List.map namespace namespaces));
221221+ ( "maskedPaths",
222222+ strings
223223+ [
224224+ "/proc/acpi";
225225+ "/proc/asound";
226226+ "/proc/kcore";
227227+ "/proc/keys";
228228+ "/proc/latency_stats";
229229+ "/proc/timer_list";
230230+ "/proc/timer_stats";
231231+ "/proc/sched_debug";
232232+ "/sys/firmware";
233233+ "/proc/scsi";
234234+ ] );
235235+ ( "readonlyPaths",
236236+ strings
237237+ [
238238+ "/proc/bus";
239239+ "/proc/fs";
240240+ "/proc/irq";
241241+ "/proc/sys";
242242+ "/proc/sysrq-trigger";
243243+ ] );
244244+ ("seccomp", seccomp_policy);
245245+ ] );
246246+ ]
247247+end
248248+249249+let next_id = ref 0
250250+251251+let spawn ~sw fs proc config dir =
252252+ let tmp = Filename.temp_dir ~perms:0o700 "shelter-run-" "" in
253253+ let eio_tmp = Eio.Path.(fs / tmp) in
254254+ let json_config = Json_config.make config ~config_dir:tmp ~results_dir:dir in
255255+ Eio.Path.save ~create:(`If_missing 0o644) (eio_tmp / "config.json")
256256+ (Yojson.Safe.pretty_to_string json_config ^ "\n");
257257+ Eio.Path.save ~create:(`If_missing 0o644) (eio_tmp / "hosts")
258258+ "127.0.0.1 localhost builder";
259259+ let id = string_of_int !next_id in
260260+ incr next_id;
261261+ let cmd = [ "runc"; "--root"; "runc"; "run"; id ] in
262262+ Eio.Process.spawn ~sw proc ~cwd:eio_tmp cmd
263263+264264+(*
265265+ Apache License
266266+ Version 2.0, January 2004
267267+ https://www.apache.org/licenses/
268268+269269+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
270270+271271+ 1. Definitions.
272272+273273+ "License" shall mean the terms and conditions for use, reproduction,
274274+ and distribution as defined by Sections 1 through 9 of this document.
275275+276276+ "Licensor" shall mean the copyright owner or entity authorized by
277277+ the copyright owner that is granting the License.
278278+279279+ "Legal Entity" shall mean the union of the acting entity and all
280280+ other entities that control, are controlled by, or are under common
281281+ control with that entity. For the purposes of this definition,
282282+ "control" means (i) the power, direct or indirect, to cause the
283283+ direction or management of such entity, whether by contract or
284284+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
285285+ outstanding shares, or (iii) beneficial ownership of such entity.
286286+287287+ "You" (or "Your") shall mean an individual or Legal Entity
288288+ exercising permissions granted by this License.
289289+290290+ "Source" form shall mean the preferred form for making modifications,
291291+ including but not limited to software source code, documentation
292292+ source, and configuration files.
293293+294294+ "Object" form shall mean any form resulting from mechanical
295295+ transformation or translation of a Source form, including but
296296+ not limited to compiled object code, generated documentation,
297297+ and conversions to other media types.
298298+299299+ "Work" shall mean the work of authorship, whether in Source or
300300+ Object form, made available under the License, as indicated by a
301301+ copyright notice that is included in or attached to the work
302302+ (an example is provided in the Appendix below).
303303+304304+ "Derivative Works" shall mean any work, whether in Source or Object
305305+ form, that is based on (or derived from) the Work and for which the
306306+ editorial revisions, annotations, elaborations, or other modifications
307307+ represent, as a whole, an original work of authorship. For the purposes
308308+ of this License, Derivative Works shall not include works that remain
309309+ separable from, or merely link (or bind by name) to the interfaces of,
310310+ the Work and Derivative Works thereof.
311311+312312+ "Contribution" shall mean any work of authorship, including
313313+ the original version of the Work and any modifications or additions
314314+ to that Work or Derivative Works thereof, that is intentionally
315315+ submitted to Licensor for inclusion in the Work by the copyright owner
316316+ or by an individual or Legal Entity authorized to submit on behalf of
317317+ the copyright owner. For the purposes of this definition, "submitted"
318318+ means any form of electronic, verbal, or written communication sent
319319+ to the Licensor or its representatives, including but not limited to
320320+ communication on electronic mailing lists, source code control systems,
321321+ and issue tracking systems that are managed by, or on behalf of, the
322322+ Licensor for the purpose of discussing and improving the Work, but
323323+ excluding communication that is conspicuously marked or otherwise
324324+ designated in writing by the copyright owner as "Not a Contribution."
325325+326326+ "Contributor" shall mean Licensor and any individual or Legal Entity
327327+ on behalf of whom a Contribution has been received by Licensor and
328328+ subsequently incorporated within the Work.
329329+330330+ 2. Grant of Copyright License. Subject to the terms and conditions of
331331+ this License, each Contributor hereby grants to You a perpetual,
332332+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
333333+ copyright license to reproduce, prepare Derivative Works of,
334334+ publicly display, publicly perform, sublicense, and distribute the
335335+ Work and such Derivative Works in Source or Object form.
336336+337337+ 3. Grant of Patent License. Subject to the terms and conditions of
338338+ this License, each Contributor hereby grants to You a perpetual,
339339+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
340340+ (except as stated in this section) patent license to make, have made,
341341+ use, offer to sell, sell, import, and otherwise transfer the Work,
342342+ where such license applies only to those patent claims licensable
343343+ by such Contributor that are necessarily infringed by their
344344+ Contribution(s) alone or by combination of their Contribution(s)
345345+ with the Work to which such Contribution(s) was submitted. If You
346346+ institute patent litigation against any entity (including a
347347+ cross-claim or counterclaim in a lawsuit) alleging that the Work
348348+ or a Contribution incorporated within the Work constitutes direct
349349+ or contributory patent infringement, then any patent licenses
350350+ granted to You under this License for that Work shall terminate
351351+ as of the date such litigation is filed.
352352+353353+ 4. Redistribution. You may reproduce and distribute copies of the
354354+ Work or Derivative Works thereof in any medium, with or without
355355+ modifications, and in Source or Object form, provided that You
356356+ meet the following conditions:
357357+358358+ (a) You must give any other recipients of the Work or
359359+ Derivative Works a copy of this License; and
360360+361361+ (b) You must cause any modified files to carry prominent notices
362362+ stating that You changed the files; and
363363+364364+ (c) You must retain, in the Source form of any Derivative Works
365365+ that You distribute, all copyright, patent, trademark, and
366366+ attribution notices from the Source form of the Work,
367367+ excluding those notices that do not pertain to any part of
368368+ the Derivative Works; and
369369+370370+ (d) If the Work includes a "NOTICE" text file as part of its
371371+ distribution, then any Derivative Works that You distribute must
372372+ include a readable copy of the attribution notices contained
373373+ within such NOTICE file, excluding those notices that do not
374374+ pertain to any part of the Derivative Works, in at least one
375375+ of the following places: within a NOTICE text file distributed
376376+ as part of the Derivative Works; within the Source form or
377377+ documentation, if provided along with the Derivative Works; or,
378378+ within a display generated by the Derivative Works, if and
379379+ wherever such third-party notices normally appear. The contents
380380+ of the NOTICE file are for informational purposes only and
381381+ do not modify the License. You may add Your own attribution
382382+ notices within Derivative Works that You distribute, alongside
383383+ or as an addendum to the NOTICE text from the Work, provided
384384+ that such additional attribution notices cannot be construed
385385+ as modifying the License.
386386+387387+ You may add Your own copyright statement to Your modifications and
388388+ may provide additional or different license terms and conditions
389389+ for use, reproduction, or distribution of Your modifications, or
390390+ for any such Derivative Works as a whole, provided Your use,
391391+ reproduction, and distribution of the Work otherwise complies with
392392+ the conditions stated in this License.
393393+394394+ 5. Submission of Contributions. Unless You explicitly state otherwise,
395395+ any Contribution intentionally submitted for inclusion in the Work
396396+ by You to the Licensor shall be under the terms and conditions of
397397+ this License, without any additional terms or conditions.
398398+ Notwithstanding the above, nothing herein shall supersede or modify
399399+ the terms of any separate license agreement you may have executed
400400+ with Licensor regarding such Contributions.
401401+402402+ 6. Trademarks. This License does not grant permission to use the trade
403403+ names, trademarks, service marks, or product names of the Licensor,
404404+ except as required for reasonable and customary use in describing the
405405+ origin of the Work and reproducing the content of the NOTICE file.
406406+407407+ 7. Disclaimer of Warranty. Unless required by applicable law or
408408+ agreed to in writing, Licensor provides the Work (and each
409409+ Contributor provides its Contributions) on an "AS IS" BASIS,
410410+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
411411+ implied, including, without limitation, any warranties or conditions
412412+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
413413+ PARTICULAR PURPOSE. You are solely responsible for determining the
414414+ appropriateness of using or redistributing the Work and assume any
415415+ risks associated with Your exercise of permissions under this License.
416416+417417+ 8. Limitation of Liability. In no event and under no legal theory,
418418+ whether in tort (including negligence), contract, or otherwise,
419419+ unless required by applicable law (such as deliberate and grossly
420420+ negligent acts) or agreed to in writing, shall any Contributor be
421421+ liable to You for damages, including any direct, indirect, special,
422422+ incidental, or consequential damages of any character arising as a
423423+ result of this License or out of the use or inability to use the
424424+ Work (including but not limited to damages for loss of goodwill,
425425+ work stoppage, computer failure or malfunction, or any and all
426426+ other commercial damages or losses), even if such Contributor
427427+ has been advised of the possibility of such damages.
428428+429429+ 9. Accepting Warranty or Additional Liability. While redistributing
430430+ the Work or Derivative Works thereof, You may choose to offer,
431431+ and charge a fee for, acceptance of support, warranty, indemnity,
432432+ or other liability obligations and/or rights consistent with this
433433+ License. However, in accepting such obligations, You may act only
434434+ on Your own behalf and on Your sole responsibility, not on behalf
435435+ of any other Contributor, and only if You agree to indemnify,
436436+ defend, and hold each Contributor harmless for any liability
437437+ incurred by, or claims asserted against, such Contributor by reason
438438+ of your accepting any such warranty or additional liability.
439439+440440+ END OF TERMS AND CONDITIONS
441441+442442+ Copyright 2020 Thomas Leonard
443443+444444+ Licensed under the Apache License, Version 2.0 (the "License");
445445+ you may not use this file except in compliance with the License.
446446+ You may obtain a copy of the License at
447447+448448+ https://www.apache.org/licenses/LICENSE-2.0
449449+450450+ Unless required by applicable law or agreed to in writing, software
451451+ distributed under the License is distributed on an "AS IS" BASIS,
452452+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
453453+ See the License for the specific language governing permissions and
454454+ limitations under the License. *)
+78-33
src/lib/shelter/shelter_main.ml
···1818 time : int64;
1919 env : string list;
2020 cwd : string;
2121+ user : int * int;
2122 diff : Diff.t;
2223 }
2324 [@@deriving repr]
···174175 (list s);
175176 store
176177177177-let run (_config : config) fs clock _proc
178178+let run (config : config) fs clock proc
178179 (((H.Store ((module S), store) : entry H.t) as s), ctx) = function
179180 | Set_mode mode ->
180181 with_latest ~default:(fun _ -> Ok (s, ctx)) s @@ fun (_, entry) ->
···238239 History.
239240 {
240241 mode = Void.RW;
241241- build = Store.Build.Image "alpine";
242242+ build = Store.Build.Image config.image;
242243 args = command;
243244 time = 0L;
244245 diff = [];
245246 (* TODO: extract with fetch *)
246247 env = [];
247248 cwd = "/";
249249+ user = (0, 0);
248250 })
249251 s
250252 @@ fun (_, e) -> e
251253 in
252252- let build =
254254+ let build, env, (uid, gid) =
253255 match entry.build with
254254- | Store.Build.Image img -> Store.fetch ctx img
255255- | Store.Build.Build cid -> cid
256256+ | Store.Build.Image img ->
257257+ let build, env, user = Store.fetch ctx img in
258258+ (build, env, Option.value ~default:(0, 0) user)
259259+ | Store.Build.Build cid -> (cid, entry.env, entry.user)
256260 in
257261 let hash_entry = { entry with build = Build build; args = command } in
258262 let new_cid = Store.cid (Repr.to_string History.t hash_entry) in
···263267 try
264268 let new_entry, diff =
265269 with_rootfs @@ fun rootfs ->
266266- let void =
267267- Void.empty
268268- |> Void.rootfs ~mode:entry.mode rootfs
269269- |> Void.cwd entry.cwd
270270- |> Void.exec ~env:entry.env
271271- [
272272- "/bin/ash";
273273- "-c";
274274- String.concat " " command ^ " && env > /shelter-env";
275275- ]
270270+ let spawn sw =
271271+ if config.no_runc then
272272+ let rootfs = Filename.concat rootfs "rootfs" in
273273+ let void =
274274+ Void.empty
275275+ |> Void.rootfs ~mode:entry.mode rootfs
276276+ |> Void.cwd entry.cwd
277277+ (* TODO: Support UIDs |> Void.uid 1000 *)
278278+ |> Void.exec ~env
279279+ [
280280+ config.shell;
281281+ "-c";
282282+ String.concat " " command ^ " && env > /tmp/shelter-env";
283283+ ]
284284+ in
285285+ `Void (Void.spawn ~sw void |> Void.exit_status)
286286+ else
287287+ let config =
288288+ Runc.Json_config.
289289+ {
290290+ cwd = entry.cwd;
291291+ argv =
292292+ [
293293+ config.shell;
294294+ "-c";
295295+ String.concat " " command ^ " && env > /tmp/shelter-env";
296296+ ];
297297+ hostname = "";
298298+ network = [];
299299+ user = (uid, gid);
300300+ env = entry.env;
301301+ entrypoint = None;
302302+ }
303303+ in
304304+ `Runc (Runc.spawn ~sw fs proc config rootfs)
276305 in
277306 Switch.run @@ fun sw ->
307307+ let res = spawn sw in
278308 let start = Mtime_clock.now () in
279279- let proc = Void.spawn ~sw void in
280309 let res =
281281- Void.exit_status proc |> Eio.Promise.await |> Void.to_eio_status
310310+ match res with
311311+ | `Runc r -> Eio.Process.await r
312312+ | `Void v -> Void.to_eio_status (Eio.Promise.await v)
282313 in
283314 let stop = Mtime_clock.now () in
284284- (* Extract env *)
285285- let env_path = Eio.Path.(fs / rootfs / "shelter-env") in
286286- let env = Eio.Path.(load env_path) |> String.split_on_char '\n' in
287287- Eio.Path.unlink env_path;
288288- let cwd =
289289- List.find_map
290290- (fun v ->
291291- match Astring.String.cut ~sep:"=" v with
292292- | Some ("PWD", dir) -> Some dir
293293- | _ -> None)
294294- env
295295- |> Option.value ~default:hash_entry.cwd
296296- in
297315 let span = Mtime.span start stop in
298316 let time = Mtime.Span.to_uint64_ns span in
299317 (* Add command to history regardless of exit status *)
300318 let _ : (unit, string) result =
301319 LNoise.history_add (String.concat " " command)
302320 in
303303- if res = `Exited 0 then
321321+ if res = `Exited 0 then (
322322+ (* Extract env *)
323323+ let env_path =
324324+ Eio.Path.(fs / rootfs / "rootfs" / "tmp" / "shelter-env")
325325+ in
326326+ let env =
327327+ Eio.Path.(load env_path)
328328+ |> String.split_on_char '\n'
329329+ |> List.filter (fun s -> not (String.equal "" s))
330330+ in
331331+ Eio.Path.unlink env_path;
332332+ let cwd =
333333+ List.find_map
334334+ (fun v ->
335335+ match Astring.String.cut ~sep:"=" v with
336336+ | Some ("PWD", dir) -> Some dir
337337+ | _ -> None)
338338+ env
339339+ |> Option.value ~default:hash_entry.cwd
340340+ in
304341 if entry.mode = RW then
305305- Ok { hash_entry with build = Build new_cid; time; env; cwd }
306306- else Ok { hash_entry with cwd; env }
342342+ Ok
343343+ {
344344+ hash_entry with
345345+ build = Build new_cid;
346346+ time;
347347+ env;
348348+ cwd;
349349+ user = (uid, gid);
350350+ }
351351+ else Ok { hash_entry with time; cwd; env; user = (uid, gid) })
307352 else Error (Eio.Process.Child_error res)
308353 in
309354 match new_entry with
+1
src/lib/shelter/shelter_main.mli
···88 time : int64;
99 env : string list;
1010 cwd : string;
1111+ user : int * int;
1112 diff : Diff.t;
1213 }
1314 [@@deriving repr]
+24-7
src/lib/shelter/store.ml
···126126 in
127127 Cid.v ~version:`Cidv1 ~base:`Base32 ~codec:`Raw ~hash
128128129129+let not_empty s = not String.(equal empty s)
130130+131131+let get_uid_gid ~username rootfs =
132132+ let passwd =
133133+ Eio.Path.load Eio.Path.(rootfs / "etc" / "passwd")
134134+ |> String.split_on_char '\n'
135135+ |> List.map (String.split_on_char ':')
136136+ |> List.map (List.filter not_empty)
137137+ in
138138+ List.find_map
139139+ (function
140140+ | user :: _ :: uid :: gid :: _ when String.equal user username ->
141141+ Some (int_of_string uid, int_of_string gid)
142142+ | _ -> None)
143143+ passwd
144144+129145let fetch t image =
130146 let cid = cid image in
131147 let cids = cid |> Cid.to_string in
132148 let dataset = Datasets.build t.pool cids in
133149 let dir = Eio.Path.(t.fs / ("/" ^ (Datasets.build t.pool cids :> string))) in
134134- if Zfs.exists t.zfs (dataset :> string) Zfs.Types.filesystem then cid
135135- else (
136136- create_and_mount t dataset;
137137- let _dir : string = Fetch.get_image ~dir ~proc:t.proc image in
138138- snapshot t (Datasets.snapshot dataset);
139139- cid)
150150+ create_and_mount t dataset;
151151+ let _dir : string = Fetch.get_image ~dir ~proc:t.proc image in
152152+ snapshot t (Datasets.snapshot dataset);
153153+ let username = Fetch.get_user t.proc image in
154154+ ( cid,
155155+ Fetch.get_env t.proc image,
156156+ get_uid_gid ~username Eio.Path.(dir / "rootfs") )
140157141158module Run = struct
142159 let with_build t cid fn =
143160 let ds = Datasets.build t.pool (Cid.to_string cid) in
144161 Fun.protect ~finally:(fun () -> unmount_dataset t ds) @@ fun () ->
145162 mount_dataset t ds;
146146- fn ("/" ^ (ds :> string) ^ "/rootfs")
163163+ fn ("/" ^ (ds :> string))
147164148165 let with_clone t ~src new_cid fn =
149166 let ds = Datasets.build t.pool (Cid.to_string src) in
+19-2
vendor/void/src/void.ml
···1111 args : string list;
1212 env : string list;
1313 cwd : string;
1414+ uid : int;
1515+ (* TODO: gid *)
1416 rootfs : (string * mode) option;
1517 mounts : mount list;
1618}
···6062 (action_pivot_root, new_root, new_root_flags, tmpfs, mounts)));
6163 }
62646565+external action_setuid : unit -> Fork_action.fork_fn
6666+ = "void_fork_setuid"
6767+6868+let action_setuid = action_setuid ()
6969+7070+let setuid (uid : int) = Fork_action.
7171+ {
7272+ run =
7373+ (fun k ->
7474+ k
7575+ (Obj.repr
7676+ (action_setuid, uid)));
7777+ }
7878+6379external action_map_uid_gid : unit -> Fork_action.fork_fn
6480 = "void_fork_map_uid_gid"
6581···100116101117type path = string
102118103103-let empty = { args = []; env = []; rootfs = None; mounts = []; cwd = "/" }
119119+let empty = { args = []; env = []; rootfs = None; mounts = []; cwd = "/"; uid = 0 }
104120105121let actions v : Fork_action.t list =
106122 let root, tmpfs, root_mode =
···128144 let mounts = pivot_root root root_flags tmpfs mounts in
129145 let uid, gid = Unix.(getuid (), getgid ()) in
130146 let user_namespace = map_uid_gid ~uid ~gid in
131131- [ user_namespace; mounts; Process.Fork_action.chdir v.cwd; e ]
147147+ [ user_namespace; mounts; setuid v.uid; Process.Fork_action.chdir v.cwd; e ]
132148133149let rootfs ~mode path v = { v with rootfs = Some (path, mode) }
134150let cwd cwd v = { v with cwd }
151151+let uid uid v = { v with uid }
135152let exec ?(env=[]) args v = { v with args; env }
136153137154let mount ~mode ~src ~tgt v =
+3
vendor/void/src/void.mli
···5151val cwd : string -> void -> void
5252(** Set the current working directory *)
53535454+val uid : int -> void -> void
5555+(** Set the UID *)
5656+5457val exec : ?env:string list -> string list -> void -> void
5558(** Make a void configuration ready to be spawned *)
5659