Skip to content

Commit

Permalink
Merge pull request #697 from talex5/cap-enter
Browse files Browse the repository at this point in the history
Add Eio_unix.Cap module to enable Capsicum mode
  • Loading branch information
talex5 authored Feb 26, 2024
2 parents 261b583 + ca121ee commit 31ee72e
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 1 deletion.
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);

0 comments on commit 31ee72e

Please sign in to comment.