Skip to content
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

Add Eio_unix.Cap module to enable Capsicum mode #697

Merged
merged 1 commit into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,25 @@ 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 reject accesses
that don't use capabilities.
[examples/capsicum/](./examples/capsicum/) contains an example that
restricts itself to using a directory passed on the command-line, and then
tries reading `/etc/passwd` via the stdlib.
Running on FreeBSD, you should see:

```
mkdir /tmp/cap
dune exec -- ./examples/capsicum/main.exe /tmp/cap
+Opened directory <fs:/tmp/cap>
+Capsicum mode enabled
+Using the file-system via the directory resource works:
+Writing <cap:capsicum-test.txt>...
+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:
Expand Down Expand Up @@ -1810,3 +1829,4 @@ Some background about the effects system can be found in:
[Dev meetings]: https://docs.google.com/document/d/1ZBfbjAkvEkv9ldumpZV5VXrEc_HpPeYjHPW_TiwJe4Q
[Olly]: https://github.com/tarides/runtime_events_tools
[eio-trace]: https://github.com/ocaml-multicore/eio-trace
[cap_enter]: https://man.freebsd.org/cgi/man.cgi?query=cap_enter
3 changes: 3 additions & 0 deletions examples/capsicum/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(executable
(name main)
(libraries eio_main))
40 changes: 40 additions & 0 deletions examples/capsicum/main.ml
Original file line number Diff line number Diff line change
@@ -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 ()
27 changes: 27 additions & 0 deletions lib_eio/unix/cap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include "primitives.h"

#include <errno.h>
#include <sys/param.h>

#ifdef __FreeBSD__
# define HAVE_CAPSICUM
#endif

#ifdef HAVE_CAPSICUM
# include <sys/capsicum.h>
#endif

#include <caml/mlvalues.h>
#include <caml/unixsupport.h>

CAMLprim value eio_unix_cap_enter(value v_unit) {
#ifdef HAVE_CAPSICUM
int r = cap_enter();
if (r == -1 && errno != ENOSYS)
caml_uerror("cap_enter", Nothing);

return Val_bool(r == 0);
#else
return Val_bool(0);
#endif
}
5 changes: 5 additions & 0 deletions lib_eio/unix/cap.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
external eio_cap_enter : unit -> bool = "eio_unix_cap_enter"

let enter () =
if eio_cap_enter () then Ok ()
else Error `Not_supported
11 changes: 11 additions & 0 deletions lib_eio/unix/cap.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
val enter : unit -> (unit, [`Not_supported]) result
(** Call {{:https://man.freebsd.org/cgi/man.cgi?query=cap_enter}cap_enter}.

Once in capability mode, access to global name spaces, such as file system
or IPC name spaces, is prevented by the operating system. A program can call
this after opening any directories, files or network sockets that it will need,
to prevent accidental access to other resources.

The standard environment directories {!Eio.Stdenv.fs} and {!Eio.Stdenv.cwd} cannot
be used after calling this, but directories opened from them via {!Eio.Path.with_open_dir}
will continue to work. *)
3 changes: 2 additions & 1 deletion lib_eio/unix/dune
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
(foreign_stubs
(language c)
(include_dirs include)
(names fork_action stubs))
(names fork_action stubs cap))
(libraries eio eio.utils unix threads mtime.clock.os))

(rule
Expand All @@ -15,6 +15,7 @@
(run %{bin:lintcstubs_arity_cmt}
%{dep:.eio_unix.objs/byte/eio_unix__Fd.cmt}
%{dep:.eio_unix.objs/byte/eio_unix__Private.cmt}
%{dep:.eio_unix.objs/byte/eio_unix__Cap.cmt}
%{dep:.eio_unix.objs/byte/eio_unix__Fork_action.cmt}))))

(rule
Expand Down
1 change: 1 addition & 0 deletions lib_eio/unix/eio_unix.ml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module Ipaddr = Net.Ipaddr

module Process = Process
module Net = Net
module Cap = Cap
module Pi = Pi

module Stdenv = struct
Expand Down
3 changes: 3 additions & 0 deletions lib_eio/unix/eio_unix.mli
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ val pipe : Switch.t -> source_ty r * sink_ty r
module Process = Process
(** Spawning child processes with extra control. *)

module Cap = Cap
(** Capsicum security. *)

(** The set of resources provided to a process on a Unix-compatible system. *)
module Stdenv : sig
type base = <
Expand Down
1 change: 1 addition & 0 deletions lib_eio/unix/primitives.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ CAMLprim value eio_unix_fork_execve(value);
CAMLprim value eio_unix_fork_chdir(value);
CAMLprim value eio_unix_fork_fchdir(value);
CAMLprim value eio_unix_fork_dups(value);
CAMLprim value eio_unix_cap_enter(value);
CAMLprim value eio_unix_readlinkat(value, value, value);
CAMLprim value eio_unix_is_blocking(value);
Loading