-
Notifications
You must be signed in to change notification settings - Fork 18
FreeBSD sandbox module, using jail(8) #156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| open Lwt.Infix | ||
|
|
||
| let invoke_fetcher base destdir = | ||
| Os.with_pipe_between_children @@ fun ~r ~w -> | ||
| let stdin = `FD_move_safely r in | ||
| let stdout = `FD_move_safely w in | ||
| let stderr = stdout in | ||
| let fetcher = | ||
| Os.exec ~stdout ~stderr ["fetch"; "-q" ; "-o" ; "-" ; base ] | ||
| in | ||
| let extracter = | ||
| Os.sudo ~stdin [ "tar" ; "-C"; destdir ; "-xzpf"; "-" ] | ||
| in | ||
| fetcher >>= fun () -> | ||
| extracter | ||
|
|
||
| let fetch ~log ~rootfs base = | ||
| let _ = log in | ||
| Lwt.catch | ||
| (fun () -> | ||
| invoke_fetcher base rootfs >>= fun () -> | ||
| let env = [] in | ||
| Lwt.return env) | ||
| (function | ||
| | Sys_error s -> | ||
| Fmt.failwith "Archive fetcher encountered a system error: %s" s | ||
| | e -> Lwt.fail e) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| (** Fetching of base images as .tar.gz archives *) | ||
|
|
||
| include S.FETCHER |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| open Lwt.Infix | ||
| open Sexplib.Conv | ||
|
|
||
| let ( / ) = Filename.concat | ||
|
|
||
| type t = { | ||
| jail_name_prefix : string; | ||
| } | ||
|
|
||
| type config = unit [@@deriving sexp] | ||
|
|
||
| (* Find out the user name to use within the jail, by parsing the | ||
| /etc/passwd file within the jail filesystem. This is roughly | ||
| equivalent to what Unix.getpwuid would do. | ||
| Note that the gid is currently ignored. *) | ||
| let jail_username rootdir config = | ||
| match config.Config.user with | ||
| | `Windows w -> w.name | ||
| | `Unix { uid; _ } -> | ||
| let pwdfile = rootdir / "etc" / "passwd" in | ||
| let uidstr = string_of_int uid in | ||
| let rec parse_line ch = | ||
| match In_channel.input_line ch with | ||
| | None -> None | ||
| | Some line -> | ||
| let fields = String.split_on_char ':' line in begin | ||
| match fields with | ||
| | name :: _pass :: uid :: _ -> | ||
| if uid = uidstr then Some name else parse_line ch | ||
| | _ -> parse_line ch | ||
| end | ||
| in | ||
| match In_channel.with_open_text pwdfile parse_line with | ||
| | None -> Fmt.failwith "No user found for uid %d" uid | ||
| | Some name -> name | ||
|
|
||
| (* Compute the complete set of arguments passed to the jail(8) command: | ||
| jail username, jail path, command to run, etc. *) | ||
| let jail_options config rootdir = | ||
| let username = jail_username rootdir config in | ||
| let commandline = | ||
| let env = List.rev_map (fun (k, v) -> k ^ "=" ^ v) config.env in | ||
| let commandline = List.rev (List.rev_map Filename.quote config.argv) in | ||
| let commandline = | ||
| match env with | ||
| | [] -> commandline | ||
| | _ -> "env" :: List.rev_append env commandline | ||
| in | ||
| let commandline = | ||
| String.concat " " | ||
| ([ "cd" ; Filename.quote config.cwd ; "&&" ] @ commandline) | ||
| in | ||
| (* Ask for a login shell in order to properly source opam settings. *) | ||
| [ "command=/usr/bin/su" ; "-l" ; username ; "-c" ; commandline ] | ||
| in | ||
| let path = "path=" ^ rootdir in | ||
| let devfs_setup = "mount.devfs" in | ||
| let options = | ||
| let options = [ path ; devfs_setup ] in | ||
| match config.network with | ||
| | [ "host" ] -> | ||
| "ip4=inherit" :: "ip6=inherit" :: "host=inherit" :: options | ||
| | _ -> options | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we restrict the jail networking to only access itself here? |
||
| in | ||
| List.rev_append options commandline | ||
|
|
||
| let copy_to_log ~src ~dst = | ||
| let buf = Bytes.create 4096 in | ||
| let rec aux () = | ||
| Lwt_unix.read src buf 0 (Bytes.length buf) >>= function | ||
| | 0 -> Lwt.return_unit | ||
| | n -> Build_log.write dst (Bytes.sub_string buf 0 n) >>= aux | ||
| in | ||
| aux () | ||
|
|
||
| let jail_id = ref 0 | ||
|
|
||
| let run ~cancelled ?stdin:stdin ~log (t : t) config rootdir = | ||
| let cwd = rootdir in | ||
| let jail_name = t.jail_name_prefix ^ "_" ^ string_of_int !jail_id in | ||
| incr jail_id; | ||
| Os.with_pipe_from_child @@ fun ~r:out_r ~w:out_w -> | ||
| let rootdir = rootdir / "rootfs" in | ||
| let workdir = rootdir / config.Config.cwd in | ||
| (* Make sure the work directory exists prior to starting the jail. *) | ||
| begin | ||
| match Os.check_dir workdir with | ||
| | `Present -> Lwt.return_unit | ||
| | `Missing -> Os.sudo [ "mkdir" ; "-p" ; workdir ] | ||
| end >>= fun () -> | ||
| let stdout = `FD_move_safely out_w in | ||
| let stderr = stdout in | ||
| let copy_log = copy_to_log ~src:out_r ~dst:log in | ||
| let proc = | ||
| let cmd = | ||
| let options = jail_options config rootdir in | ||
| "jail" :: "-c" :: jail_name :: options | ||
| in | ||
| let stdin = Option.map (fun x -> `FD_move_safely x) stdin in | ||
| let pp f = Os.pp_cmd f ("", cmd) in | ||
| (* This is similar to | ||
| Os.sudo_result ~cwd ?stdin ~stdout ~stderr ~pp cmd | ||
| but also unmounting the in-jail devfs if necessary, see below. *) | ||
| let cmd = if Os.running_as_root then cmd else "sudo" :: "--" :: cmd in | ||
| Logs.info (fun f -> f "Exec %a" Os.pp_cmd ("", cmd)); | ||
| !Os.lwt_process_exec ~cwd ?stdin ~stdout ~stderr ~pp | ||
| ("", Array.of_list cmd) >>= function | ||
| | Ok 0 -> | ||
| (* If the command within the jail completes, the jail is automatically | ||
| removed, but without performing any of the stop and release actions, | ||
| thus we can not use "exec.stop" to unmount the in-jail devfs | ||
| filesystem. Do this here, ignoring the exit code of umount(8). *) | ||
| let cmd = [ "sudo" ; "/sbin/umount" ; rootdir / "dev" ] in | ||
| Os.exec ~is_success:(fun _ -> true) cmd >>= fun () -> | ||
| Lwt_result.ok Lwt.return_unit | ||
| | Ok n -> Lwt.return @@ Fmt.error_msg "%t failed with exit status %d" pp n | ||
| | Error e -> Lwt_result.fail e | ||
| in | ||
| Lwt.on_termination cancelled (fun () -> | ||
| let rec aux () = | ||
| if Lwt.is_sleeping proc then ( | ||
| let pp f = Fmt.pf f "jail -r obuilder" in | ||
| Os.sudo_result ~cwd [ "jail" ; "-r" ; jail_name ] ~pp >>= function | ||
| | Ok () -> Lwt.return_unit | ||
| | Error (`Msg _) -> | ||
| Lwt_unix.sleep 10.0 >>= aux | ||
| ) else Lwt.return_unit (* Process has already finished *) | ||
| in | ||
| Lwt.async aux | ||
| ); | ||
| proc >>= fun r -> | ||
| copy_log >>= fun () -> | ||
| if Lwt.is_sleeping cancelled then | ||
| Lwt.return (r :> (unit, [`Msg of string | `Cancelled]) result) | ||
| else | ||
| Lwt_result.fail `Cancelled | ||
|
|
||
| let create ~state_dir:_ _c = | ||
| Lwt.return { | ||
| (* Compute a unique (across obuilder instances) name prefix for the jail. *) | ||
| jail_name_prefix = "name=obuilder_" ^ (Int.to_string (Unix.getpid ())); | ||
| } | ||
|
|
||
| let finished () = | ||
| Lwt.return () | ||
|
|
||
| open Cmdliner | ||
|
|
||
| let cmdliner : config Term.t = | ||
| Term.(const ()) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -110,6 +110,9 @@ module Zfs = struct | |
| | `And_snapshots -> ["-r"; "-f"] | ||
| | `And_snapshots_and_clones -> ["-R"; "-f"] | ||
| in | ||
| (* At least under FreeBSD, pass the -f option to make sure any dangling | ||
| devfs mount within the filesystem gets automatically unmounted. *) | ||
| let opts = "-f" :: opts in | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs validation on linux and macOS ZFS implementations.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| Os.sudo (["zfs"; "destroy"] @ opts @ ["--"; Dataset.full_name t ds]) | ||
|
|
||
| let destroy_snapshot t ds snapshot mode = | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given
runcis Linux can this rule become(enabled_if (<> %{system} linux))?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be
(enabled_if (= %{system} linux)), but yes, I suppose this could work.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it turns out making this change breaks a few builds (such as debian 11.4 on arm32 and powerpc64, see build report), so I'll revert that particular change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is to do with the variable expansion in
duneif you add a defaultsandbox.dummy.mlwithand setup a copy rule for
Win32that should work. I'm not sure what is happening with the POWER build, the reported value forsystemis clearly not doing what I expect.