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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
//! Functions for handling upcalls from the kernel.

pub use crate::arch::upcall::UpcallFrame;
use crate::object::ObjID;

/// Information about an exception.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
#[repr(C)]
pub struct ExceptionInfo {
    /// CPU-reported exception code.
    pub code: u64,
    /// Arch-specific additional info.
    pub info: u64,
}

impl ExceptionInfo {
    /// Construct new exception info.
    pub fn new(code: u64, info: u64) -> Self {
        Self { code, info }
    }
}

/// Information about a memory access error to an object.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
#[repr(C)]
pub struct ObjectMemoryFaultInfo {
    /// Object ID of attempted access.
    pub object_id: ObjID,
    /// The kind of error.
    pub error: ObjectMemoryError,
    /// The kind of memory access that caused the error.
    pub access: MemoryAccessKind,
    /// The virtual address at which the error occurred.
    pub addr: usize,
}

impl ObjectMemoryFaultInfo {
    /// Construct a new upcall info for memory fault.
    pub fn new(
        object_id: ObjID,
        error: ObjectMemoryError,
        access: MemoryAccessKind,
        addr: usize,
    ) -> Self {
        Self {
            object_id,
            error,
            access,
            addr,
        }
    }
}

/// Kinds of object memory errors.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
#[repr(u8)]
pub enum ObjectMemoryError {
    /// Tried to access an object's null page
    NullPageAccess,
    /// Tried to access outside of an object
    OutOfBounds(usize),
}

/// Information about a non-object-related memory access violation.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
#[repr(C)]
pub struct MemoryContextViolationInfo {
    /// The virtual address that caused the exception.
    pub address: u64,
    /// The kind of memory access.
    pub kind: MemoryAccessKind,
}

impl MemoryContextViolationInfo {
    pub fn new(address: u64, kind: MemoryAccessKind) -> Self {
        Self { address, kind }
    }
}

/// Kinds of memory access.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
#[repr(u8)]
pub enum MemoryAccessKind {
    Read,
    Write,
    InstructionFetch,
}

/// Possible upcall reasons and info.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(C)]
pub enum UpcallInfo {
    Exception(ExceptionInfo),
    ObjectMemoryFault(ObjectMemoryFaultInfo),
    MemoryContextViolation(MemoryContextViolationInfo),
}

impl UpcallInfo {
    /// The number of upcall info variants
    pub const NR_UPCALLS: usize = 3;
    /// Get the number associated with this variant
    pub fn number(&self) -> usize {
        match self {
            UpcallInfo::Exception(_) => 0,
            UpcallInfo::ObjectMemoryFault(_) => 1,
            UpcallInfo::MemoryContextViolation(_) => 2,
        }
    }
}

/// A collection of data about this upcall, and the [UpcallInfo] for this
/// particular upcall.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(C)]
pub struct UpcallData {
    /// Info for this upcall, including reason and elaboration data.
    pub info: UpcallInfo,
    /// Upcall flags
    pub flags: UpcallHandlerFlags,
    /// Source context
    pub source_ctx: ObjID,
    /// The thread ID for this thread.
    pub thread_id: ObjID,
}

/// Information for handling an upcall, per-thread. By default, a thread starts with
/// all these fields initialized to zero, and the mode set to [UpcallMode::Abort].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(C)]
pub struct UpcallTarget {
    /// Address to jump to when handling via a call to the same context.
    pub self_address: usize,
    /// Address to jump to when handling via a call to supervisor context.
    pub super_address: usize,
    /// Address of supervisor stack to use, when switching to supervisor context.
    pub super_stack: usize,
    /// Size of the super stack.
    pub super_stack_size: usize,
    /// Value to use for stack pointer, when switching to supervisor context.
    pub super_thread_ptr: usize,
    /// Supervisor context to use, when switching to supervisor context.
    pub super_ctx: ObjID,
    /// Per-upcall options.
    pub options: [UpcallOptions; UpcallInfo::NR_UPCALLS],
}

impl UpcallTarget {
    /// Construct a new upcall target.
    pub fn new(
        self_address: Option<unsafe extern "C-unwind" fn(*mut UpcallFrame, *const UpcallData) -> !>,
        super_address: Option<
            unsafe extern "C-unwind" fn(*mut UpcallFrame, *const UpcallData) -> !,
        >,
        super_stack: usize,
        super_stack_size: usize,
        super_thread_ptr: usize,
        super_ctx: ObjID,
        options: [UpcallOptions; UpcallInfo::NR_UPCALLS],
    ) -> Self {
        Self {
            self_address: self_address.map(|addr| addr as usize).unwrap_or_default(),
            super_address: super_address.map(|addr| addr as usize).unwrap_or_default(),
            super_stack,
            super_thread_ptr,
            super_ctx,
            options,
            super_stack_size,
        }
    }
}

/// The exit code the kernel will use when killing a thread that cannot handle
/// an upcall (e.g. the kernel fails to push the upcall stack frame, or the mode is set
/// to [UpcallMode::Abort]).
pub const UPCALL_EXIT_CODE: u64 = 127;

bitflags::bitflags! {
    /// Flags controlling upcall handling.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct UpcallFlags : u8 {
    /// Whether or not to suspend the thread before handling (or aborting from) the upcall.
    const SUSPEND = 1;
}
}

bitflags::bitflags! {
    /// Flags passed to the upcall handler in [UpcallData].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct UpcallHandlerFlags : u8 {
    /// Whether or not to suspend the thread before handling (or aborting from) the upcall.
    const SWITCHED_CONTEXT = 1;
}
}

/// Possible modes for upcall handling.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum UpcallMode {
    /// Handle this upcall by immediate abort. If the SUSPEND flag is set, the thread will
    /// still abort when unsuspended.
    Abort,
    /// Handle this upcall by calling, without trying to transfer to supervisor context. Upcall
    /// data, including frame data, will be placed on the current stack, and the thread pointer
    /// is unchanged.
    CallSelf,
    /// Handle this upcall by calling into supervisor context. If the thread is already in
    /// supervisor context, this acts like [UpcallMode::CallSelf]. Otherwise, the thread's stack
    /// and thread pointer are updated to the super_stack and super_thread_pointer values in the
    /// upcall target respectively, and the active security context is switched to the supervisor
    /// context (super_ctx).
    CallSuper,
}

/// Options for a single upcall.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct UpcallOptions {
    /// Flags for the upcall.
    pub flags: UpcallFlags,
    /// The mode for the upcall.
    pub mode: UpcallMode,
}