diff --git a/README.md b/README.md index 3d10d7260..e2678cafe 100644 --- a/README.md +++ b/README.md @@ -886,6 +886,24 @@ A program that operates on the current directory will probably want to use `cwd` whereas a program that accepts a path from the user will probably want to use `fs`, perhaps with `open_dir` to constrain all access to be within that directory. +On systems that provide the `cap_enter` system call, you can ask the OS to enforce this. +See for an example. +This opens the directory given on the command line, calls `cap_enter` to drop privileges, +uses the directory, and then tries reading `/etc/passwd` using the standard library. +Running on FreeBSD, you should see: + +``` +mkdir /tmp/cap +dune exec -- ./examples/capsicum/main.exe /tmp/cap ++Opening directory ... ++Capsicum mode enabled ++Using the file-system via the directory resource works: ++Writing ... ++Read: "A test file" ++Bypassing Eio and accessing other resources should fail in Capsicum mode: +Fatal error: exception Sys_error("/etc/passwd: Not permitted in capability mode") +``` + ## Running processes Spawning a child process can be done using the [Eio.Process][] module: diff --git a/examples/capsicum/dune b/examples/capsicum/dune new file mode 100644 index 000000000..fb95515c7 --- /dev/null +++ b/examples/capsicum/dune @@ -0,0 +1,3 @@ +(executable + (name main) + (libraries eio_main)) diff --git a/examples/capsicum/main.ml b/examples/capsicum/main.ml new file mode 100644 index 000000000..f6fe71f1f --- /dev/null +++ b/examples/capsicum/main.ml @@ -0,0 +1,40 @@ +open Eio.Std + +let ( / ) = Eio.Path.( / ) + +let test_eio dir = + traceln "Using the file-system via the directory resource works:"; + let test_file = dir / "capsicum-test.txt" in + traceln "Writing %a..." Eio.Path.pp test_file; + Eio.Path.save test_file "A test file" ~create:(`Exclusive 0o644); + traceln "Read: %S" (Eio.Path.load test_file); + Eio.Path.unlink test_file + +let test_legacy () = + traceln "Bypassing Eio and accessing other resources should fail in Capsicum mode:"; + let ch = open_in "/etc/passwd" in + let len = in_channel_length ch in + let data = really_input_string ch len in + close_in ch; + traceln "Was able to read /etc/passwd:@.%s" (String.trim data) + +let () = + Eio_main.run @@ fun env -> + (* Parse command-line arguments *) + let path = + match Sys.argv with + | [| _; dir |] -> Eio.Stdenv.fs env / dir + | _ -> failwith "Usage: main.exe DIR" + in + if not (Eio.Path.is_directory path) then Fmt.failwith "%a is not a directory" Eio.Path.pp path; + (* Get access to resources before calling cap_enter: *) + Eio.Path.with_open_dir path @@ fun dir -> + traceln "Opened directory %a" Eio.Path.pp path; + (* Switch to capability mode, if possible: *) + begin match Eio_unix.Cap.enter () with + | Ok () -> traceln "Capsicum mode enabled" + | Error `Not_supported -> traceln "!! CAPSICUM PROTECTION NOT AVAILABLE !!" + end; + (* Run tests: *) + test_eio dir; + test_legacy () diff --git a/lib_eio/unix/cap.c b/lib_eio/unix/cap.c index 2c9d81ff0..cae6b6468 100644 --- a/lib_eio/unix/cap.c +++ b/lib_eio/unix/cap.c @@ -1,10 +1,13 @@ #include "primitives.h" -#define HAVE_CAP_ENTER - #include +#include + +#ifdef __FreeBSD__ +# define HAVE_CAPSICUM +#endif -#ifdef HAVE_CAP_ENTER +#ifdef HAVE_CAPSICUM # include #endif @@ -12,13 +15,13 @@ #include CAMLprim value eio_unix_cap_enter(value v_unit) { -#ifdef HAVE_CAP_ENTER +#ifdef HAVE_CAPSICUM int r = cap_enter(); if (r == -1 && errno != ENOSYS) caml_uerror("cap_enter", Nothing); - return Val_bool(r == 0) + return Val_bool(r == 0); #else - return Val_bool(0) + return Val_bool(0); #endif } diff --git a/lib_eio/unix/cap.ml b/lib_eio/unix/cap.ml index 9244f8823..fbb7841f0 100644 --- a/lib_eio/unix/cap.ml +++ b/lib_eio/unix/cap.ml @@ -1,5 +1,5 @@ external eio_cap_enter : unit -> bool = "eio_unix_cap_enter" -let cap_enter () = +let enter () = if eio_cap_enter () then Ok () else Error `Not_supported diff --git a/lib_eio/unix/cap.mli b/lib_eio/unix/cap.mli index fb3d08e74..a4718ef98 100644 --- a/lib_eio/unix/cap.mli +++ b/lib_eio/unix/cap.mli @@ -1 +1 @@ -val cap_enter : unit -> (unit, [`Not_supported]) result +val enter : unit -> (unit, [`Not_supported]) result