context&rnd:needs cleanup/eac:done
This commit is contained in:
parent
f57961fb84
commit
427ef068c5
5 changed files with 521 additions and 333 deletions
|
@ -1,5 +1,5 @@
|
|||
% // vim: set ft=tex:
|
||||
\chapter{An Introduction To Rust}
|
||||
\chapter{Rust}
|
||||
\label{rnd::rust}
|
||||
As described by the maintainers, \gls{Rust} is a "systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety."\footnote{\url{www.rust-lang.org}}.
|
||||
During early development it had a runtime-dependent garbage collector which has since been dropped from the language, making it a viable candidate for \gls{os} development.
|
||||
|
@ -45,21 +45,59 @@ By analyzing control flow and evaluation of constant expressions, code paths can
|
|||
It will also allow type checking to be more effective, as the MIR is simpler than the HIR.
|
||||
An example of a potential optimization is given in the borrow checker explanation along with the \cnameref{rnd::rust::feat::own-borrow::mir-improvement}.
|
||||
|
||||
% TODO: Tokens
|
||||
|
||||
\subsection{Syntax}
|
||||
In \gls{os} development, macros are often preferred over functions because they are processed at compile time and induce no runtime overhead.
|
||||
In \gls{Rust}, they are deeply integrated into the language syntax, and are not trivially usable.
|
||||
\section{Macros And Extensions}
|
||||
In order to extend Rust for specific use-cases in \gls{os} development with macros or other means, the syntax and its extension mechanisms need to be understood.
|
||||
The following information has been extracted from the \url{https://danielkeep.github.io/tlborm/book}.
|
||||
In \gls{os} development, macros are often preferred over functions because they are processed at compile time and induce no runtime overhead.
|
||||
In \gls{Rust}, they are deeply integrated into the language and are not intuitively usable.
|
||||
This is because they require the developer to write pattern matches on token trees and understand the abstract syntax tree creation.
|
||||
|
||||
\paragraph{Tokens Trees}
|
||||
Rust is a C-like language and doesn't look too much different for simple code.
|
||||
It starts with tokens, which are similar to other languages.
|
||||
The most comprehensive literature on Rust's Macro system, including a thorough explanation of available language is provided as a digital book in the rustdoc format\footnote{url{https://danielkeep.github.io/tlborm/book}}.
|
||||
|
||||
\subsection{Macro Rules}
|
||||
A simple macro is presented in \cpnameref{rnd::imezzos-preemptive-multitasking::timer-interrupt-scheduling::macro}, where it is used as a template for interrupt handler definitions.
|
||||
|
||||
% \subsubsection{Macro Recursion Limit}
|
||||
% Macro recursion can be limited via the attribute:
|
||||
%
|
||||
% \mintinline{rust}{#![recursion_limit="10"]}
|
||||
|
||||
\subsection{Language Extensions Example: Software Fault Isolation}
|
||||
Language extensions allow the addition of almost arbitrary functionality to the language.
|
||||
They can be used for the definition of additional analysis rules to extend safety checks.
|
||||
|
||||
Developing these is difficult, but the effort is justified under conditions where regular macros are insufficient.
|
||||
The mechanics of language extensions are beyond the scope of this study, instead an example is presented demonstrating the results one can achieve with them.
|
||||
|
||||
\citeauthor{Balasubramanian2017} have achieved an implementation of Information Control Flow (IFC) analysis in \gls{Rust}, which lets the programmer annotate variables with security contexts.\cite{Balasubramanian2017}.
|
||||
|
||||
IFC enables the enforcement of security contexts in information flow without hardware support and detects a violation at compile time.
|
||||
|
||||
The following buffer implementation is initialized with the first data element it receives.
|
||||
\begin{minted}[breaklines,highlightlines={6,7}]{rust}
|
||||
struct Buffer{data: Option <Vec <u8 >>}
|
||||
impl Buffer {
|
||||
fn new () -> Buffer {Buffer{data: None }}
|
||||
fn append (& mut self , mut v: Vec <u8 >) {
|
||||
match self.data {
|
||||
None => self.data = Some(v),
|
||||
Some(ref mut d) => d.append (& mut v) }
|
||||
} }
|
||||
\end{minted}
|
||||
|
||||
In this sample, variables from two different security contexts are stored in the buffer.
|
||||
The \code{println!} macro is considered \code{non-secret} and may not read the data since it contain \code{secret} items.
|
||||
\begin{minted}[breaklines,highlightlines={2-3,5-6,9}]{rust}
|
||||
let mut buf = Buffer::new();
|
||||
#[ label(non-secret )] // security annotation for IFC
|
||||
let nonsec = vec![1,2,3];
|
||||
|
||||
#[ label(secret )] // security annotation for IFC
|
||||
let sec = vec![4,5,6];
|
||||
buf.append(nonsec);
|
||||
buf.append(sec); // buf now contains secret data
|
||||
println!("{:?}" , buf.data ); // ERROR : leaks secret data
|
||||
\end{minted}
|
||||
|
||||
\subsection{Extensions}
|
||||
\subsubsection{Definition Of Additional Analysis Rules To Extend Safety Checks}
|
||||
% TODO: Business Logic Checks
|
||||
% Examples:
|
||||
% TLB needs to be reset on Task Change
|
||||
|
@ -72,19 +110,10 @@ It starts with tokens, which are similar to other languages.
|
|||
% TODO * Tasks can't access unallocated (physical) memory
|
||||
% TODO * Tasks can't access other tasks memory
|
||||
|
||||
|
||||
\subsection{Macro Rules}
|
||||
A rather simple macro is presented in \cpnameref{rnd::imezzos-preemptive-multitasking::timer-interrupt-scheduling::macro}.
|
||||
|
||||
\subsubsection{Macro Recursion Limit}
|
||||
Macro recursion can be limited via the attribute:
|
||||
|
||||
\mintinline{rust}{#![recursion_limit="10"]}
|
||||
|
||||
\subsection{Compiler Plugins}
|
||||
The Rust Unstable Book \url{https://doc.rust-lang.org/unstable-book/language-features/plugin.html}
|
||||
has a section on compiler plugins, which are user-provided libraries that extend the compiler's behavior with new syntax extensions, lint checks, etc.
|
||||
This is
|
||||
% \subsection{Compiler Plugins}
|
||||
% The Rust Unstable Book \url{https://doc.rust-lang.org/unstable-book/language-features/plugin.html}
|
||||
% has a section on compiler plugins, which are user-provided libraries that extend the compiler's behavior with new syntax extensions, lint checks, etc.
|
||||
% This is
|
||||
|
||||
\subsection{Cargo}
|
||||
\glsentrydesc{cargo}.
|
||||
|
@ -94,7 +123,7 @@ This is
|
|||
Using \gls{cargo}, arguments for the \gls{llvm} \gls{compiler} can be passed all the way down to by creating the \code{$PROJECT_DIR/.cargo/config} file.
|
||||
The following is an example which has been used to experiment with stack protection in \cref{rnd::weakness-mitig-prev::stack-protection::stack-clash::user-space}.
|
||||
|
||||
\begin{minted}{yaml}
|
||||
\begin{minted}[breaklines]{yaml}
|
||||
[build]
|
||||
rustflags = [
|
||||
"-C", "llvm-args=-safe-stack-layout -enable-stackovf-sanitizer -asan-stack -warn-stack-size=1000",
|
||||
|
@ -121,18 +150,18 @@ Standalone tools of \gls{llvm} might not expose the same functionality as the \g
|
|||
The following sentence is placed here according to the Don't-Repeat-Yourself principle as it would have otherwise been in almost every subsection:
|
||||
Developers unfamiliar with this concept are likely to take a while to get used to it, but safety-gains are well worth the effort.
|
||||
|
||||
\subsection{Memory Management}
|
||||
- TODO: Static Variables on Stack, handled by compiler
|
||||
% \subsection{Memory Management}
|
||||
% - TODO: Static Variables on Stack, handled by compiler
|
||||
%
|
||||
% - TODO: Heap requires implemented allocator
|
||||
%
|
||||
% - TODO: BSYS SS17 GITHUB IO Rust Memory Layout - 4
|
||||
% - TODO: How can memory be dynamically allocated and still safety checked?
|
||||
|
||||
- TODO: Heap requires implemented allocator
|
||||
|
||||
- TODO: BSYS SS17 GITHUB IO Rust Memory Layout - 4
|
||||
- TODO: How can memory be dynamically allocated and still safety checked?
|
||||
|
||||
\subsubsection{Custom Allocators}
|
||||
- TODO: mention ralloc by redox
|
||||
- TODO: simple allocator by Blog OS
|
||||
- TODO: Who owns global 'static variables?
|
||||
% \subsubsection{Custom Allocators}
|
||||
% - TODO: mention ralloc by redox
|
||||
% - TODO: simple allocator by Blog OS
|
||||
% - TODO: Who owns global 'static variables?
|
||||
|
||||
\subsection{Ownership And Borrows}
|
||||
\citeauthor{Beingessner2015} explores the ownership model in relation to some of the weaknesses explained in \cref{context::weaknesses-mem-safety}.
|
||||
|
@ -175,29 +204,35 @@ In the latter case, the borrow checker would complain.
|
|||
This is because it does not consider the two constant indices to borrow different items from the vector, but considers the whole vector to be borrowed by the first statement, causing an error for the second borrow attempt of the vector.
|
||||
|
||||
\subsection{Static Analyser}
|
||||
- TODO: How does the Rust's static analysis work, theoretically and practically
|
||||
- TODO: mention electrolyte, formal verification for Rust
|
||||
- TODO: How does static typing help with preventing programming errors
|
||||
The static analyser has been studies extensively throughout this part.
|
||||
Specifically \Cref{rnd::weakness-mitig-prev::stack-protection::stack-clash::user-space}, which tests the capability of static detection of obvious stack overflow scenarios.
|
||||
|
||||
- TODO: explain lints
|
||||
%- TODO: How does the Rust's static analysis work, theoretically and practically
|
||||
%- TODO: mention electrolyte, formal verification for Rust
|
||||
%- TODO: How does static typing help with preventing programming errors
|
||||
%
|
||||
%- TODO: explain lints
|
||||
|
||||
\subsection{Inline Assembly}
|
||||
Inline assembly is explained by example in \cref{rnd::imezzos-preemptive-multitasking::timer-interrupt-scheduling}
|
||||
Inline assembly is explored two examples within this study.
|
||||
Inside the scheduler to instruct the compiler's register clobbering: \cpnameref{rnd::imezzos-preemptive-multitasking::timer-interrupt-scheduling},
|
||||
and in the redirection of the boot task shown in \cpnameref{rnd::imezzos-preemptive-multitasking::tasks-stacks::unsafe::jmp}.
|
||||
|
||||
A more formal and helpful tutorial which is suggested, has been found in form of a web article.\footnote{\url{http://embed.rs/articles/2016/arm-inline-assembly-rust/}}
|
||||
|
||||
\subsection{Lifetimes}
|
||||
- TODO: Where are global 'static variables allocated?
|
||||
% \subsection{Lifetimes}
|
||||
% Lifetimes were not used intensively
|
||||
|
||||
\subsection{Type Safety}
|
||||
- TODO: demonstrate casts
|
||||
|
||||
- TODO: demonstrate raw pointers:
|
||||
% https://rustbyexample.com/flow_control/match/destructuring/destructure_pointers.html
|
||||
% - TODO: demonstrate casts
|
||||
%
|
||||
% - TODO: demonstrate raw pointers:
|
||||
% % https://rustbyexample.com/flow_control/match/destructuring/destructure_pointers.html
|
||||
%
|
||||
% - TODO: discuss the equivalents of void*?
|
||||
|
||||
- TODO: discuss the equivalents of void*?
|
||||
|
||||
\subsection{Single Field Structs}
|
||||
\subsubsection{Single Field Structs}
|
||||
Structs with a single field can be used to wrap a under a different type name, and make it distinguishable for the type system.
|
||||
This is different from a type alias, which wouldn't prevent the example situation given below.
|
||||
This extended example\footnote{\url{https://aturon.github.io/features/types/newtype.html}} shows one way of preventing the mix-up of common length units.
|
||||
|
@ -250,12 +285,8 @@ error[E0308]: mismatched types
|
|||
- .as_miles()
|
||||
\end{minted}
|
||||
|
||||
\subsection{Empty Types}
|
||||
\label{rnd::rust::type-safety::empty-types}
|
||||
Empty types are abstract types that can not be instantiated.
|
||||
|
||||
\subsubsection{Unreachable Code Paths}
|
||||
They can be used to statically prevent certain code paths, declaring them impossible.
|
||||
\subsubsection{Uninstantiable Types}
|
||||
They can be used to statically prevent certain code paths or mark other impossible conditions in the code.
|
||||
The simplest example is a function that is defined to never return:
|
||||
|
||||
\begin{minted}[linenos,breaklines]{rust}
|
||||
|
@ -297,19 +328,16 @@ This demonstrates that the empty enum cannot be instantiated, and is merely a sy
|
|||
Rust includes the \code{!} type for this purpose, and the function could've been written as \mintinline{rust}{fn never_returns() -> ! { loop{} }}.
|
||||
This pattern can be used in \gls{os} development for the \gls{os}'s function that runs the main loop, and is not supposed to return.
|
||||
|
||||
\subsubsection{In Combination With Traits And PhantomData}
|
||||
Emtpy enums can be used for more advanced use-cases in combination with traits, as shown in \cref{rnd::existing-os-dev-with-rust::systems::blog-os::mm}, where the lowest level of the page hierarchy is prevented from calling the \code{next_table} method.
|
||||
\paragraph{In Combination With Traits And PhantomData}
|
||||
Emtpy enums can be used for more advanced use-cases in combination with traits, as shown in \cref{rnd::existing-os-dev-with-rust::systems::blog-os::mm}, where the lowest level of the page hierarchy is prevented from calling the \code{next_table()} method.
|
||||
|
||||
\subsection{Inner- and Outer Mutability}
|
||||
Some types in \gls{Rust} provide interior mutability, so that their \emph{value} can be mutated even though they have not been declared using \code{mut}.
|
||||
|
||||
An example of this is found in with the \code{spin::Mutex} type used in %\cpnameref{}.
|
||||
This study has two usages of types with interior mutability:
|
||||
\code{AtomicUsize}, used in \cpnameref{code::imezzos-preemptive-multitasking::clock::tick} and \code{spin::Mutex} used in \cpnameref{rnd::example::mutex}.
|
||||
|
||||
Other examples which are not covered in this study include \code{Rc}, \code{Arc}, \code{RefCell}.
|
||||
|
||||
\section{Limitations}
|
||||
* TODO: deadlock example
|
||||
* TODO: cyclic reference memory leak example
|
||||
Other types which are not covered in this study include \code{Rc}, \code{Arc}, \code{RefCell}.
|
||||
|
||||
\chapter{Weakness Mitigation And Prevention}
|
||||
\label{rnd::weakness-mitig-prev}
|
||||
|
@ -592,14 +620,14 @@ The \gls{os} can then check if the guard page was accessed or if the stack is pe
|
|||
As this code was extracted from a binary, the estimated stack size must have been calculcated at compile-time.
|
||||
This is fortunate and drives the investigation further if this check could be performed entirely at compile-time.
|
||||
|
||||
\paragraph{Compile-Time Prevention}
|
||||
\paragraph{Attempted Compile-Time Prevention}
|
||||
\label{rnd::weakness-mitig-prev::stack-protection::stack-clash::user-space::compile-time}
|
||||
%The compile-time prevention of the stack clash depends on the ability to predict the stack size and its boundaries accurately.
|
||||
%This investigation justifies a separate chapter, please see \cref{rnd::stack-size-estimation}.
|
||||
%
|
||||
%\chapter{Compile-Time Stack-Size Estimation}
|
||||
%\label{rnd::stack-size-estimation}
|
||||
By estimating the stack size at compile-time the stack clash -- covered in \cref{rnd::weakness-mitig-prev::stack-protection::stack-clash}) -- and other undesired stack scenarios, could be predicted without running into them.
|
||||
By estimating the stack size at compile-time the stack clash -- covered in \cref{rnd::weakness-mitig-prev::stack-protection::stack-clash} -- and other undesired stack scenarios, could be predicted without running into them.
|
||||
In theory, this analysis requires a prediction of the worst-case stack growth for each procedure based on source code information.
|
||||
This maximum stack growth size must then be compared to stack size limit, as well as the distance and the size of the guard area; it must be equal or less than all given limits.
|
||||
This could effectively prevent the stack from overflowing and from touching or leaping over the guard area.
|
||||
|
@ -671,7 +699,7 @@ fn main() {
|
|||
The highlighted lines in \cref{code::examples::huge-stack-rust} construct a slice on the stack with the size of $8 * 0x100000000 = 0x800000000 = 4,294,967,296$ Bytes (4GiB), which would fill the main memory of any 32-Bit system and should definitely be enough to trigger the configured stack warning.
|
||||
|
||||
Unexpectedly this program compiled without a warning;
|
||||
It was expected that the \gls{compiler} detects this huge statically allocated stack array, compares it to the configured maximum allowed size and reports the violation.
|
||||
It was expected that the \gls{compiler} detects this huge statically allocated stack slice, compares it to the configured maximum allowed size and reports the violation.
|
||||
At runtime it crashes with this message:
|
||||
|
||||
\begin{minted}{md}
|
||||
|
@ -958,11 +986,11 @@ The main author of Redox OS has become an active contributor to the Rust languag
|
|||
The biggest achievement from the perspective of this study is the successful integration into Rust's libstd, which happened continuously and cannot be referenced easily.
|
||||
This allows programmers to use Rust with all it's features to develop programs for Redox OS.
|
||||
|
||||
\subsection{Tock OS}
|
||||
Tock OS is "an embedded operating system designed for running multiple concurrent, mutually distrustful applications on low-memory and low-power microcontrollers."\cite{TockOS}
|
||||
|
||||
\subsubsection{Task Model}
|
||||
\subsubsection{Memory Management}
|
||||
% \subsection{Tock OS}
|
||||
% Tock OS is "an embedded operating system designed for running multiple concurrent, mutually distrustful applications on low-memory and low-power microcontrollers."\cite{TockOS}
|
||||
%
|
||||
% \subsubsection{Task Model}
|
||||
% \subsubsection{Memory Management}
|
||||
|
||||
\subsection{intermezzOS}
|
||||
"intermezzOS is a teaching operating system, specifically focused on introducing systems programming concepts to experienced developers from other areas of programming."\footnote{\url{https://intermezzos.github.io/}}
|
||||
|
@ -1059,7 +1087,7 @@ The build process gives an impression of what is required to build an \gls{os} e
|
|||
|
||||
\section{Development State}
|
||||
The anticipated development of preemptive multitasking has been reached.
|
||||
Tasks are represented by plain \code{fn()} instances.
|
||||
Tasks are represented by plain \code{fn() -> !} instances.
|
||||
The tasks and the task table are statically defined in the \gls{os} source code.
|
||||
Task switches are driven by the Programmable-Interrupt-Timer, for which a driver has been implemented.
|
||||
The task scheduler works in a round-robin fashion and detects stack overflows.
|
||||
|
@ -1204,11 +1232,19 @@ impl Clock for Pit {
|
|||
\end{minted}
|
||||
The \code{start} method is the first occurrence of \code{unsafe}, which is required to perform raw I/O port access using \code{outb}.
|
||||
\code{tick} is extremely simple, it uses a method to atomically add one, requesting a specific ordering: \textit{SeqCstr: Like AcqRel with the additional guarantee that all threads see all sequentially consistent operations in the same order}.\footnote{\url{https://doc.rust-lang.org/core/sync/atomic/enum.Ordering.html}}
|
||||
This method is called in the \gls{os} timer interrupt handler.
|
||||
This method is called in the \gls{os} timer interrupt handler, as indicated in \cref{code::imezzos-preemptive-multitasking::clock::tick}.
|
||||
|
||||
|
||||
% TODO: Is the static analysis of hardware specific assembly code possible and useful at all?
|
||||
% TODO: LLVM knows about the target and can potentially give hints about hardware specific instructions
|
||||
\begin{listing}[ht!]
|
||||
\begin{minted}[breaklines]{rust}
|
||||
let timer = make_idt_entry!(isr32, esf: &mut ExceptionStackFrame, true, {
|
||||
...
|
||||
unsafe { CLOCK.tick() };
|
||||
...
|
||||
\end{minted}
|
||||
\caption{Ticking The Clock}
|
||||
\label{code::imezzos-preemptive-multitasking::clock::tick}
|
||||
\end{listing}
|
||||
\FloatBarrier
|
||||
|
||||
\section{Timer Interrupt For Scheduling and Dispatching}
|
||||
\label{rnd::imezzos-preemptive-multitasking::timer-interrupt-scheduling}
|
||||
|
@ -1273,25 +1309,62 @@ Thanks to the pull-request described in \cpnameref{rnd::existing-os-dev-with-rus
|
|||
It enables the proper handling of the first argument, and in combination with the \emph{clobber} registers shown in \cref{code::imezzos::ir-handler-macro} line 11, enables the compiler to generate a functoin pro- and epilogue to automatically \code{PUSH/POP} all named registers from the stack.
|
||||
As a result, the inline assembly string provided by the programmer is empty, which alleviates the necessatiy of \code{unsafe}.
|
||||
|
||||
\paragraph{Inline Assembly}
|
||||
Further, the inline assembly is interesting.
|
||||
|
||||
\paragraph{Inline Assembly}
|
||||
|
||||
\begin{minted}[breaklines]{rust}
|
||||
let timer = make_idt_entry!(isr32, esf: &mut ExceptionStackFrame, true, {
|
||||
...
|
||||
unsafe { CLOCK.tick() };
|
||||
...
|
||||
\end{minted}
|
||||
TODO
|
||||
|
||||
|
||||
\section{Tasks and Stacks}
|
||||
\label{rnd::imezzos-preemptive-multitasking::tasks-stacks}
|
||||
The implementation of the tasks has been kept straight forward, using static variables.
|
||||
The implementation of the tasks has been kept straight forward, using global static variables and simple struct types instead of traits.
|
||||
Tasks are defined globally with the simple \code{fn() -> !} type.
|
||||
|
||||
\subsection{Initial Unsafe JMP}
|
||||
\label{rnd::imezzos-preemptive-multitasking::tasks-stacks::unsafe::jmp}
|
||||
To redirect the codepath of the boot task, the solution in \cref{code::imezzos::jump-to-task0} has been implemented.
|
||||
The function \code{schedule_and_dispatch()} is called from the \gls{os} \code{main() -> !} method, which will never return.
|
||||
It shows the combination of Rust high-level code, which leverages the highlighted lifetime to seamlessly lock and drop the \code{TSI} variable.
|
||||
|
||||
The numbered circle show another interesting usage of the language.
|
||||
The variables are declare immutable (no \code{mut}), and are initialized within the nested scope.
|
||||
Another write to these variables would result in a compilation error.
|
||||
They end up being passed to the inline assembly, where they are further processed.
|
||||
|
||||
\begin{listing}[ht!]
|
||||
\begin{minted}[escapeinside=??,breaklines,highlightlines={7-14,16-30}]{rust}
|
||||
#[naked]
|
||||
fn schedule_and_dispatch() {
|
||||
let rbp; ?\tikzmarkcircle{1}?
|
||||
let rsp; ?\tikzmarkcircle{2}?
|
||||
let rip; ?\tikzmarkcircle{3}?
|
||||
|
||||
{
|
||||
let tsi = TSI.lock();
|
||||
let te = tsi.get_current_task();
|
||||
|
||||
rbp = te.stack.top; ?\tikzmarkcircle{1}?
|
||||
rsp = te.esf.stack_pointer; ?\tikzmarkcircle{2}?
|
||||
rip = te.esf.instruction_pointer; ?\tikzmarkcircle{3}?
|
||||
};
|
||||
|
||||
unsafe {
|
||||
asm!("
|
||||
mov rbp, $0 "?\tikzmarkcircle{1}?"
|
||||
jmp $1 "?\tikzmarkcircle{2}?"
|
||||
"
|
||||
: // output operands
|
||||
: // input operands
|
||||
"r"(rbp) ?\tikzmarkcircle{1}?
|
||||
"r"(rip) ?\tikzmarkcircle{3}?
|
||||
"{rsp}="(rsp) ?\tikzmarkcircle{2}?
|
||||
: // clobbers
|
||||
: // options
|
||||
"intel" "volatile"
|
||||
);
|
||||
};
|
||||
}
|
||||
\end{minted}
|
||||
\caption{intermezzOS: Initial Jump To Task 0}
|
||||
\label{code::imezzos::jump-to-task0}
|
||||
\end{listing}
|
||||
|
||||
\subsection{Declaration and Intantiation}
|
||||
\paragraph{Global Stacks}
|
||||
\label{rnd::imezzos-preemptive-multitasking::tasks-stacks::dni}
|
||||
\Cref{code::imezzos::stack-and-tasks-1} defines a \code{Stack} with a top and a bottom address based which are offset by a constant.
|
||||
Subsequent stacks grow the multiplier by 10, which keeps space between the stacks.
|
||||
|
@ -1310,7 +1383,8 @@ const TASK0_STACK: Stack = Stack {
|
|||
\label{code::imezzos::stack-and-tasks-1}
|
||||
\end{listing}
|
||||
|
||||
\Cref{code::imezzos::stack-and-tasks-2} defines a \code{TaskEntry} in a static array of the same.
|
||||
\paragraph{Global TaskList}
|
||||
\Cref{code::imezzos::stack-and-tasks-2} defines a \code{TaskEntry} in a static slice of the same.
|
||||
The highlighted lines are unique to each task.
|
||||
In the given order, they represent their first instruction, their initial top of stack, and their initial set of \gls{cpu} registers.
|
||||
Except for the instruction pointer, these variables have their own type and cannot easily be mixed up.
|
||||
|
@ -1341,7 +1415,9 @@ Except for the instruction pointer, these variables have their own type and cann
|
|||
\label{code::imezzos::stack-and-tasks-2}
|
||||
\end{listing}
|
||||
|
||||
\Cref{code::imezzos::stack-and-tasks-3} wraps this array by a \code{Mutex}, which is returned by the expression and stored as a \code{lazy_static} reference as explained in the previous section.
|
||||
\paragraph{Mutex With Interior Mutability}
|
||||
\label{rnd::example::mutex}
|
||||
\Cref{code::imezzos::stack-and-tasks-3} wraps this slice in a \code{spin::Mutex}, which is returned by the expression and stored as a \code{lazy_static} reference as explained in the previous section.
|
||||
The \code{Mutex} type is interesting, as it provides \emph{interior mutability}.
|
||||
This explains how the tasklist can be mutated at runtime, even though it is not declared as \code{mut}.
|
||||
|
||||
|
@ -1363,12 +1439,91 @@ static ref TSI: Mutex<tasks::TaskStateInformation> = {
|
|||
\end{listing}
|
||||
|
||||
\subsection{Preemptive Task Switches}
|
||||
What follows in \cref{code::imezzos::taskswitch-1,} is the most low-level part in this study, the actual context switch within the interrupt handler.
|
||||
|
||||
It does these things: (* marks unsafe actions)
|
||||
\begin{enumerate}
|
||||
\item * Read the \gls{sf} Base-Pointer (line 2)
|
||||
\item Calculate the offset to the Registers on the \gls{sf}
|
||||
\item * Cast this address to a type that contains all registers (line 5-6)
|
||||
\item ? Pass the Exception\gls{sf} and the registers along to \code{manage_tasks}
|
||||
\item Acknowledge the interrupt
|
||||
\end{enumerate}
|
||||
|
||||
Looking at this code in retrospection suggests that \code{manage_tasks} could be marked as \code{unsafe} too because it does \code{unsafe} things inside.
|
||||
Through the values it consumed, it is able to directly modify the stack contents.
|
||||
Arguably it is done through modeled types, but it is not the way the \gls{stack} was designed to be used by a programmer.
|
||||
|
||||
\begin{listing}[ht!]
|
||||
\begin{minted}[breaklines,highlightlines={2,5-6},linenos]{rust}
|
||||
let timer = make_idt_entry!(isr32, esf: &mut ExceptionStackFrame, true, {
|
||||
let rbp_on_stack: *mut usize = unsafe { (get_register!("rbp") as *mut usize) };
|
||||
let rax_offset = 1 - (mem::size_of::<tasks::TaskRegisters>() as isize / 8);
|
||||
let rax_on_stack: *mut usize = unsafe { rbp_on_stack.offset(rax_offset) };
|
||||
let registers_on_stack: &mut tasks::TaskRegisters =
|
||||
unsafe { mem::transmute::<*mut usize, &mut tasks::TaskRegisters>(rax_on_stack) };
|
||||
|
||||
...
|
||||
manage_tasks(esf, registers_on_stack);
|
||||
pic::eoi_for(32);
|
||||
};
|
||||
\end{minted}
|
||||
\caption{intermezzOS: Taskswitch - 1}
|
||||
\label{code::imezzos::taskswitch-1}
|
||||
\end{listing}
|
||||
|
||||
\subsection{Task Definitions}
|
||||
The task definitions are straight forward as explained.
|
||||
|
||||
\paragraph{Idle Task}
|
||||
\Cref{code::imezzos::idel-task} shows the system's idle task, which infinitively calls \code{hlt()} after finishing the boot process.
|
||||
|
||||
\begin{listing}[ht!]
|
||||
\begin{minted}[breaklines,highlightlines={5}]{rust}
|
||||
#[deny(unsafe_code)]
|
||||
fn task0() -> !{
|
||||
unsafe { CLOCK.start() };
|
||||
|
||||
kprintln!(CONTEXT,
|
||||
"System clock set up. Frequency: {} / Resolution: {}ns",
|
||||
CLOCK.frequency,
|
||||
CLOCK.resolution);
|
||||
|
||||
kprintln!(CONTEXT,
|
||||
"Kernel initialized, final step: enabling interrupts");
|
||||
CONTEXT.idt.enable_interrupts();
|
||||
|
||||
loop {
|
||||
hlt();
|
||||
}
|
||||
}
|
||||
\end{minted}
|
||||
\caption{intermezzOS: Idle Task}
|
||||
\label{code::imezzos::idel-task}
|
||||
\end{listing}
|
||||
|
||||
|
||||
\section{Safety Concerns}
|
||||
|
||||
\section{Safety}
|
||||
\subsection{Protecting Static Resources}
|
||||
TODO
|
||||
With this straight forward task implementation any task has access to the globally defined reference variables which hold the system state.
|
||||
For this reason, the \code{Clock} trait makes use of the \code{unsafe} keyword.
|
||||
|
||||
As seen in this code snippet, the tasks can be prevented from accessing any \code{unsafe} by adding the appropriate annotation.
|
||||
Of course, this does not make sense for the system's idle task, but it is suitable for an example.
|
||||
|
||||
\begin{listing}[ht!]
|
||||
\begin{minted}[breaklines,highlightlines={5}]{rust}
|
||||
#[deny(unsafe_code)]
|
||||
fn task0() {
|
||||
unsafe { CLOCK.start() };
|
||||
...
|
||||
}
|
||||
\end{minted}
|
||||
\end{listing}
|
||||
|
||||
This causes the compiler to abort compilation with the following error:
|
||||
|
||||
\begin{minted}{md}
|
||||
error: usage of an `unsafe` block
|
||||
--> src/main.rs:499:5
|
||||
|
@ -1383,12 +1538,34 @@ note: lint level defined here
|
|||
|
|
||||
\end{minted}
|
||||
|
||||
\subsection{Risk Of Stack-Overflow}
|
||||
TODO
|
||||
- TODO: reference stack protection
|
||||
Give a practical example what this could look like with an extension attribute
|
||||
\subsection{Vulnerable To In-Kernel Stack Overflow}
|
||||
As investigated in \cpnameref{rnd::weakness-mitig-prev::stack-protection::stack-clash::user-space::compile-time}, \gls{Rust} does not detect stack overflows at compile-time.
|
||||
Without a paging implementation that sets up a guard area for each task, there is no guarantees on memory-safety within this \gls{os}.
|
||||
|
||||
\chapter{Result Summary}
|
||||
- TODO
|
||||
TODO
|
||||
The trivial runtime mitigation is to employ a boundary check in the task management function, as shown in \cref{code::imezzos:stack-of-detect}, and in addition place the task's stacks far apart.
|
||||
If the stack pointer of the preempted task is not within it's known stack boundaries, the task is blocked from further scheduling.
|
||||
This solution leaves each task enough time to overflow it's stack by enough space to reach a memory area of another task or the \gls{os}.
|
||||
This may happen within one scheduling period before it can be detected.
|
||||
|
||||
\begin{listing}[ht!]
|
||||
\begin{minted}[breaklines,highlightlines={5}]{rust}
|
||||
fn manage_tasks(esf: &mut ExceptionStackFrame, registers: &mut tasks::TaskRegisters) {
|
||||
...
|
||||
if let Some(mut tsi) = TSI.try_lock() {
|
||||
...
|
||||
if !tsi.get_current_task().stack.contains(esf.stack_pointer) {
|
||||
kprintln_try!(CONTEXT,
|
||||
"Stack overflow in task {}!\nStack: {:x}\nESF: {:x}\nREGS: {:x}",
|
||||
tsi.current_task,
|
||||
tsi.get_current_task().stack,
|
||||
esf,
|
||||
registers);
|
||||
tsi.get_current_task_mut().blocked = true;
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
\end{minted}
|
||||
\caption{intermezzOS: Runtime Stack Overflow Detection}
|
||||
\label{code::imezzos:stack-of-detect}
|
||||
\end{listing}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue