1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Sandbox profiles—lists of permitted operations.

use platform;

use std::path::PathBuf;

/// A sandbox profile, which specifies the set of operations that this process is allowed to
/// perform. Operations not in the list are implicitly prohibited.
///
/// If the process attempts to perform an operation in the list that this platform can prohibit
/// after the sandbox is entered via `activate()`, the operation will either fail or the process
/// will be immediately terminated. You can check whether an operation can be prohibited on this
/// platform with `Operation::prohibition_support()`.
///
/// All profiles implicitly prohibit *at least* the following operations. Future versions of `gaol`
/// may add operations to selectively allow these.
///
///    * Opening any file for writing.
///
///    * Creating new processes.
///
///    * Opening named pipes or System V IPC resources.
///
///    * Accessing System V semaphores.
///
///    * Sending signals to other processes.
///
///    * Tracing other processes.
///
///    * Accepting inbound network connections.
///
///    * Any operation that requires superuser privileges on the current operating system.
///
/// All profiles implicitly *allow* the following operations:
///
///    * All pure computation (user-mode CPU instructions that do not cause a context switch to
///      supervisor mode).
///
///    * Memory allocation (for example, via `brk` or anonymous `mmap` on Unix).
///
///    * Use of synchronization primitives (mutexes, condition variables).
///
///    * Changing memory protection and use policies: for example, marking pages non-writable or
///      informing the kernel that memory pages may be discarded. (It may be possible to restrict
///      this in future versions.)
///
///    * Spawning new threads.
///
///    * Responding to signals (e.g. `signal`, `sigaltstack`).
///
///    * Read, write, and memory map of already-opened file descriptors or handles.
///
///    * Determining how much has been sent on a file descriptor.
///
///    * Sending or receiving on already-opened sockets, including control messages on Unix.
///
///    * I/O multiplexing on already-opened sockets and/or file descriptors (`select`/`poll`).
///
///    * Opening and closing file descriptors and sockets (but not necessarily connecting them
///      to anything).
///
///    * Determining the user ID.
///
///    * Querying and altering thread scheduling options such as CPU affinity.
///
///    * Exiting the process.
///
/// Because of platform limitations, patterns within one profile are not permitted to overlap; the
/// behavior is undefined if they do. For example, you may not allow metadata reads of the subpath
/// rooted at `/dev` while allowing full reads of `/dev/null`; you must instead allow full reads of
/// `/dev` or make the profile more restrictive.
#[derive(Clone, Debug)]
pub struct Profile {
    allowed_operations: Vec<Operation>,
}

/// An operation that this process is allowed to perform.
#[derive(Clone, Debug)]
pub enum Operation {
    /// All file-related reading operations may be performed on this file.
    FileReadAll(PathPattern),
    /// Metadata (for example, `stat` or `readlink`) of this file may be read.
    FileReadMetadata(PathPattern),
    /// Outbound network connections to the given address may be initiated.
    NetworkOutbound(AddressPattern),
    /// System information may be read (via `sysctl` on Unix).
    SystemInfoRead,
    /// Platform-specific operations.
    PlatformSpecific(platform::Operation),
}

/// Describes a path or paths on the filesystem.
#[derive(Clone, Debug)]
pub enum PathPattern {
    /// One specific path.
    Literal(PathBuf),
    /// A directory and all of its contents, recursively.
    Subpath(PathBuf),
}

/// Describes a network address.
#[derive(Clone, Debug)]
pub enum AddressPattern {
    /// All network addresses.
    All,
    /// TCP connections on the given port.
    Tcp(u16),
    /// A local socket at the given path (for example, a Unix socket).
    LocalSocket(PathBuf),
}

impl Profile {
    /// Creates a new profile with the given set of allowed operations.
    ///
    /// If the operations cannot be allowed precisely on this platform, this returns an error. You
    /// can then inspect the operations via `OperationSupport::support()` to see which ones cannot
    /// be allowed and modify the set of allowed operations as necessary. We are deliberately
    /// strict here to reduce the probability of applications accidentally allowing operations due
    /// to platform limitations.
    pub fn new(allowed_operations: Vec<Operation>) -> Result<Profile,()> {
        if allowed_operations.iter().all(|operation| {
            match operation.support() {
                OperationSupportLevel::NeverAllowed | OperationSupportLevel::CanBeAllowed => true,
                OperationSupportLevel::CannotBeAllowedPrecisely |
                OperationSupportLevel::AlwaysAllowed => false,
            }
        }) {
            Ok(Profile {
                allowed_operations: allowed_operations,
            })
        } else {
            Err(())
        }
    }

    /// Returns the list of allowed operations.
    pub fn allowed_operations(&self) -> &[Operation] {
        self.allowed_operations.as_slice()
    }
}

/// How precisely an operation can be allowed on this platform.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum OperationSupportLevel {
    /// This operation is never allowed on this platform.
    NeverAllowed,
    /// This operation can be precisely allowed on this platform.
    CanBeAllowed,
    /// This operation cannot be allowed precisely on this platform, but another set of operations
    /// allows it to be allowed on a more coarse-grained level. For example, on Linux, it is not
    /// possible to allow access to specific ports, but it is possible to allow network access
    /// entirely.
    CannotBeAllowedPrecisely,
    /// This operation is always allowed on this platform.
    AlwaysAllowed,
}

/// Allows operations to be queried to determine how precisely they can be allowed on this
/// platform.
pub trait OperationSupport {
    /// Returns an `OperationSupportLevel` describing how well this operation can be allowed on
    /// this platform.
    fn support(&self) -> OperationSupportLevel;
}