WIP: describe stack handling with pictures
This commit is contained in:
parent
28dd1fe2c2
commit
b4f23fdd2f
8 changed files with 815 additions and 366 deletions
|
@ -2,12 +2,284 @@
|
|||
\chapter{Topic Refinement}
|
||||
% TODO: is this chapter required?
|
||||
|
||||
\chapter{System Programming Conventions}
|
||||
\label{rnd::sysprog-conventions}
|
||||
|
||||
\section{Stack Frame Handling on AMD64}
|
||||
\label{rnd::sysprog-conventions::stackframe-amd64}
|
||||
The usage of the \gls{stack} is tightly coupled with control flow instructions in conjunction with two registers, the Stack-Frame Base Pointer (RBP) and the Stack Pointer (RSP).
|
||||
The instructions that use these registers and explicitly or implicitly work with the stack\cite[p.~83]{AMD64Vol1} can be grouped into the following categories.
|
||||
Together they can be used to perform \gls{stack} based procedure calls, as demonstrated in the following \cref{context::introduction::hw-supported-mm::procedure-call-example}.
|
||||
|
||||
\paragraph{Direct Stack Data Management} with PUSH and POP.
|
||||
|
||||
PUSH takes value operand which is to be pushed onto the stack.
|
||||
The address in RSP moves towards numerically lower addresses with every PUSH instruction, which stores a new data entry on top.
|
||||
The order is to first change the RSP and then copy the value at its new address.
|
||||
|
||||
POP takes a storage reference operand - \gls{CPU} register or memory address.
|
||||
It works in the opposite direction to PUSH.
|
||||
First, consuming the top-most data entry and storing it on the operand location, then moving the RSP address towards the numerically higher RBP address.
|
||||
|
||||
When RBP and RSP point to the same address, the stack is considered empty.
|
||||
|
||||
\paragraph{Procedure Calls} with CALL and RET. \\
|
||||
These instructions control the instruction flow by calling another instruction procedure\footnote{loosely synonymous with function}.
|
||||
|
||||
The CALL instruction takes the address of the instruction that is to be called.
|
||||
Before jumping to the instruction at the given address, it PUSHes the current RIP (instruction pointer) register onto the \gls{stack}.
|
||||
|
||||
RET takes no operand, but instead POPs the \gls{stack}'s top entry.
|
||||
The consumed value is used as a jump address.
|
||||
|
||||
As PUSH and POP use the RSP register, the called procedure is responsible to finish with the RSP at the same position as when it was entered.
|
||||
For example, PUSHing some value onto the stack before the end of the function would cause the RET to jump to that address instead of returning to the caller.
|
||||
|
||||
\paragraph{Called Procedure Setup} \emph{not} with ENTER and LEAVE.
|
||||
|
||||
When a procedure is called the stack is set up with the following four components
|
||||
\cite[p.~48]{AMD64Vol1}:
|
||||
|
||||
\begin{enumerate}
|
||||
\item{%
|
||||
Parameters passed to the called procedure (created by the calling procedure). \\
|
||||
\textit{Only if parameters don't fit the \gls{CPU} registers}
|
||||
}
|
||||
\item{%
|
||||
Return address (created by the CALL instruction). \\
|
||||
\textit{Always used by CALL}
|
||||
}
|
||||
\item{%
|
||||
Array of stack-frame pointers (pointers to stack frames of procedures with smaller nesting-level depth) which are used to access the local variables of such procedures. \\
|
||||
\textit{Depends on support and implementation of nested functions in the \gls{compiler}}
|
||||
}
|
||||
\item{%
|
||||
Local variables used by the called procedure. \\
|
||||
\textit{This includes the variables passed via \gls{CPU} registers}
|
||||
}
|
||||
\end{enumerate}
|
||||
only necessary when there aren't enough \gls{CPU} to pass the parameters.
|
||||
Item 3 is only necessary when
|
||||
|
||||
The \gls{amd64} manual also lists ENTER and LEAVE as instructions to \textit{"provide support for procedure calls, and are mainly used in high-level languages."}\cite[p.~48]{AMD64Vol1}.
|
||||
The latter claim could not be verified by inspecting binaries produced by the \gls{C} and \gls{Rust} \glspl{compiler}.
|
||||
|
||||
Instead, these \glspl{compiler} generate a sequence of PUSH, MOV and SUB instructions to manage theset up the \gls{stack}.
|
||||
There are instructions before and after the procedure's logic, taking care of the technicalities of \gls{stack} management.
|
||||
These instruction groups within the called procedure are called prologue and epilogue.
|
||||
|
||||
\subsection{Full Procedure Call Example}
|
||||
\label{context::introduction::hw-supported-mm::procedure-call-example}
|
||||
This section combines the separate categories into one complete example that shows how the \gls{stack} is used by various \gls{CPU} instructions to perform procedure calls.
|
||||
The following code samples are extracted from a disassembled binary which was originally created using \gls{Rust}.
|
||||
The Assembler that's shown uses Intel Mnemonic, which generally operates from right to left.
|
||||
For example, \mint{nasm}{mov a, b} copies b to a.
|
||||
|
||||
\cref{code::context::examples::func-callee} shows the \gls{Rust} source code of the function \textit{sum}.
|
||||
|
||||
\section{4-Level Paging Hierarchy on \glsentrytext{amd64}}
|
||||
\label{rnd::sysprog-conventions::paging-amd64}
|
||||
On \gls{amd64} "a four-level page-translation data structure is provided to allow long-mode operating systems to translate a 64-Bit virtual-address space into a 52-Bit physical-address space."\cite[p.~18]{AMD64Vol2}.
|
||||
This allows the system to only hold the \textit{PML4} table, the which is currently referenced by the \textit{Page Map Base Register (CR3)}, available in main memory.
|
||||
|
||||
\cref{fig:virtual-addr-transl} shows the 64-Bit virtual address composition on \gls{amd64}, which uses four-levels of page tables.
|
||||
Counterintuitively the page-tables are not called level-\textit{n}-page-table, but the levels received distinct names in \citetitle{AMD64Vol2}.
|
||||
The most-significant Bits labelled as \textit{Sign Extend} are not used for addressing purposes, but must adhere the canonical address form and simply repeat the value of the most-significant implemented Bit \cite[p.~130]{AMD64Vol2}.
|
||||
The least significant Bits represent the offset within the physical page.
|
||||
The four groups in between are used to index the page-table at their respective level.
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=\textwidth]{gfx/Virtual-to-Physical-Address-Translation-Long-Mode.png}
|
||||
\caption{Virtual to Physical Address in Long Mode\cite{AMD64Vol2}}
|
||||
\label{fig:virtual-addr-transl}
|
||||
\end{figure}
|
||||
\subsubsection{Translation Scheme 4 KiB and 2 MiB Pages}
|
||||
The \gls{amd64} architecture allows configuring the page-size, two of which will be introduced in this section.
|
||||
\cref{tab:page-transl-vaddr-composition} displays the virtual address composition for the 4KiB and 2MiB page-size modes on \gls{amd64}.
|
||||
The direction from top to bottom in the table corresponds to most significant to least significant - left to right - in the virtual address.
|
||||
The \textit{sign extension} Bits cannot be used for actual information but act as a reservation for future architectural changes.
|
||||
|
||||
\begin{table}
|
||||
\begin{tabular}{l | c | c}
|
||||
Description & Bits in 4 KiB Pages & Bits in 2 MiB Pages \\
|
||||
\hline
|
||||
Sign Extend & 12 & 12 \\
|
||||
Page-Map-Level-4 Offeset & 9 & 9 \\
|
||||
Page-Directory-Pointer Offeset & 9 & 9 \\
|
||||
Page-Directory Offeset & 9 & 9 \\
|
||||
Page-Table Offeset & 9 & - \\
|
||||
Physical Page Offset & 9 & 21 \\
|
||||
\end{tabular}
|
||||
\caption{Paging on \gls{amd64}: Virtual Address Composition 4KiB/2MiB pagesizes}
|
||||
\label{tab:page-transl-vaddr-composition}
|
||||
\end{table}
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=\textwidth]{gfx/amd64-4kb-page-translation-long-mode}
|
||||
\caption{4-Kbyte Page Translation—Long Mode\cite{AMD64Vol2}}
|
||||
\label{fig:4kb-page-transl}
|
||||
\end{figure}
|
||||
|
||||
\cref{fig:4kb-page-transl} shows the detailed virtual address composition for 4 KiB pages, using four levels of page-tables.
|
||||
It uses four sets of 9-Bit indices in the virtual address, one per hierarchy level, followed by the 9 Bit page-internal offset.
|
||||
|
||||
An alternative approach is displayed in \cref{fig:2mb-page-transl}, using 2 MiB sized pages.
|
||||
It uses three sets of 9-Bit indices for the page-tables, and a 21-Bit page-internal offset.
|
||||
Increasing the page-size improves speed and memory-usage and decreases the granularity.
|
||||
In this specific example the hierarchy is reduced by one level of page-tables.
|
||||
This reduces the amount of storage required for the page-tables in overall and causes the lookup algorithm to finish faster.
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=\textwidth]{gfx/amd64-2mb-page-translation-long-mode}
|
||||
\caption{2-Mbyte Page Translation—Long Mode\cite{AMD64Vol2}}
|
||||
\label{fig:2mb-page-transl}
|
||||
\end{figure}
|
||||
|
||||
The other supported page sizes, 4 MiB and 1 GiB, as well as intermixing page sizes through the different levels don't add new insight into the mechanism and don't need to be detailed here.
|
||||
|
||||
% \subsubsection{Top-Level Page Table Self-Reference}
|
||||
% \subsubsection{Caching Lookups}
|
||||
% \subsubsection{Full Example}
|
||||
% * http://taptipalit.blogspot.de/2013/10/theory-recursive-mapping-page.html
|
||||
% * https://www.coresecurity.com/blog/getting-physical-extreme-abuse-of-intel-based-paging-systems-part-2-windows
|
||||
|
||||
\begin{listing}[htb]
|
||||
\tikzset{/minted/basename=callee-c}
|
||||
\begin{minted}[autogobble,linenos,breaklines=true]{rust}
|
||||
\end{minted}
|
||||
\caption{The called function in \gls{Rust}}
|
||||
\label{code::context::examples::func-callee-c}
|
||||
\end{listing}
|
||||
|
||||
\cref{code::context::examples::func-call} shows a snippet snippet of the calling function.
|
||||
It stores the arguments within the registers according to the System V X86\_64 calling convention. %TODO REFERENCE
|
||||
The caller doesn't alter the stack-frame pointer (RBP) or the stack pointer (RSP) registers before call, hence the called function must restore these if it alters them.
|
||||
|
||||
\begin{listing}
|
||||
\begin{minted}[escapeinside=??,highlightlines={},autogobble,linenos,breaklines=true]{rust}
|
||||
\end{minted}
|
||||
\caption{Procedure Call Example: Caller Rust}
|
||||
\label{code::context::examples::func-call}
|
||||
\end{listing}
|
||||
|
||||
\begin{listing}
|
||||
\begin{minted}[escapeinside=??,highlightlines={},autogobble,linenos,breaklines=true]{nasm}
|
||||
\end{minted}
|
||||
\caption{Procedure Call Example: Caller Assembly}
|
||||
\label{code::context::examples::func-call}
|
||||
\end{listing}
|
||||
|
||||
% \balloon{comment}{
|
||||
|
||||
% RDI, RSI, RDX, RCX, R8, R9, XMM0–7
|
||||
|
||||
\begin{table}[ht!]
|
||||
\tikzmark{precallto}
|
||||
\centering
|
||||
\begin{tabular}{ r | >{\columncolor{YellowGreen}}c | l }
|
||||
\multicolumn{1}{r}{RBP offset} & \multicolumn{1}{c}{Content} & \\
|
||||
$\uparrow$ & \cellcolor{white} & \\
|
||||
& \cellcolor{white} \dots \textit{beyond current stack} \dots & \\
|
||||
\hhline{~-~}
|
||||
0 & \textit{Previous RSP} & $\leftarrow$ RBP \\
|
||||
\hhline{~-~}
|
||||
\vdots & \dots~~\textit{local variables}~~\dots & \\
|
||||
\hhline{~-~}
|
||||
-0x30 & 3rd arg & \\
|
||||
\hhline{~|-|~}
|
||||
-0x38 & 2nd arg & \\
|
||||
\hhline{~-~}
|
||||
-0x40 & 1st arg & \\
|
||||
\hhline{~-~}
|
||||
\vdots & \dots~~\textit{local variables}~~\dots & \\
|
||||
\hhline{~-~}
|
||||
-0x60 & rdi & \\
|
||||
\hhline{~-~}
|
||||
& \dots~~\textit{local variables}~~\dots & \\
|
||||
\hhline{~-~}
|
||||
$RBP-RSP$ & \textit{unknown} & $\leftarrow$ RSP \\
|
||||
\hhline{~-~}
|
||||
& \cellcolor{white} & \\
|
||||
$\downarrow$ & \cellcolor{white} & \\
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
|
||||
|
||||
|
||||
\cref{code::context::examples::func-prologue} shows \textit{sum}'s prologue.
|
||||
The corresponding epilogue is displayed in \cref{code::context::examples::func-epilogue}.
|
||||
The comments explain the code line by line, please read them to understand what exactly happens at each instruction.
|
||||
|
||||
\begin{listing}[ht!]
|
||||
\begin{minted}[escapeinside=??,linenos=false,breaklines=true]{nasm}
|
||||
$7490: push ?\tikzmark{prologuestart}? rbp ; save the stack-frame pointer on the stack
|
||||
$7491: mov rbp,rsp ; set the stack-frame base pointer from the stack pointer
|
||||
$7494: sub rsp,0x50 ; allocate 0x50 Bytes for arguments and local variables
|
||||
$7498: mov QWORD PTR [rbp-0x30],rdi ; copy 1st arg onto stack
|
||||
$749c: mov QWORD PTR [rbp-0x28],rsi ; copy 2nd arg onto stack
|
||||
$74a0: mov QWORD PTR [rbp-0x20],rdx ; copy 3rd arg onto stack
|
||||
\end{minted}
|
||||
\caption{Function Prologue with three Arguments}
|
||||
\label{code::context::examples::func-prologue}
|
||||
\end{listing}
|
||||
|
||||
\begin{tikzpicture}[remember picture]
|
||||
\draw[overlay,red,thick,dashed] (pic cs:precallto) circle [radius=7pt] node { \textbf{1} };
|
||||
\draw[overlay,red,thick,dashed] (pic cs:prologuestart) circle [radius=7pt] node { \textbf{1} };
|
||||
\end{tikzpicture}
|
||||
|
||||
\begin{listing}[ht!]
|
||||
\begin{minted}[linenos=true,breaklines=true]{nasm}
|
||||
$74ee: mov rax,QWORD PTR [rbp-0x48] ; store return value in RAX
|
||||
$74f2: add rsp,0x50 ; set stack pointer to where stack-frame pointer was stored
|
||||
$74f6: pop rbp ; restore the stack-frame pointer
|
||||
$74f7: ret ; return to the caller, following the address on the stack
|
||||
\end{minted}
|
||||
\caption{Function Epilogue}
|
||||
\label{code::context::examples::func-epilogue}
|
||||
\end{listing}
|
||||
|
||||
\cref{fig:proc-call-example-mem} displays
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=0.95\textwidth,]{gfx/call-procedure-memory-content.png}
|
||||
\caption{Memory Layout Throughout The Procedure Call Steps}
|
||||
\label{fig:proc-call-example-mem}
|
||||
\end{figure}
|
||||
|
||||
\section{Interrupt Driven Preemptive Context Switches on \glsentrytext{amd64}}
|
||||
\label{rnd::sysprog-conventions::ir-driven-preemptive-cs-amd64}
|
||||
On \gls{amd64}, the \gls{CPU}'s interrupt mechanism does not switch the full context described previously, but only handles the registers that are necessary to successfully jump to the interrupt function: RFLAGS, RSP, RBP, RIP\footnote{Segment registers are neglected}.
|
||||
|
||||
In this scenario, the context is stored on the \gls{stack} of the function that is interrupted.
|
||||
\Cref{fig:amd64-long-mode-interrupt-stac} pictures the \gls{stack} layout on interrupt entry.
|
||||
In order to leverage an interrupt for a context switch, the interrupt function needs to replace these values on the \gls{stack} with values for the new context.
|
||||
CS (Code-Segment) and SS (Stack-Segment) have no effect in \gls{amd64} 64-Bit mode\cite[p.~20]{AMD64Vol1} and can remain unchanged.
|
||||
The \gls{OS} developer needs to know the exact address where on the \gls{stack} this data structure has been pushed by the \gls{CPU}, and must then manipulate these addresses directly.
|
||||
This type of manipulation is inherently dangerous and can not be easily checked by the \gls{compiler}.
|
||||
The function that handles the interrupt must then use the instruction \textit{iretq}\cite[p.~252]{AMD64Vol2}, to make the \gls{CPU} restore the partial context from the \gls{stack} and continue to function pointed to by the RIP.
|
||||
|
||||
|
||||
\begin{figure}
|
||||
\centering
|
||||
\includegraphics[width=0.8\textwidth]{gfx/amd64-long-mode-stack-after-interrupt.png}
|
||||
\caption{Long-Mode Stack After Interrupt\cite[p.~252]{AMD64Vol2}}
|
||||
\label{fig:amd64-long-mode-interrupt-stac}
|
||||
\end{figure}
|
||||
|
||||
For a full context-switch, the other registers that are part of the context need to be handled by the \gls{OS}'s interrupt function.
|
||||
|
||||
|
||||
\chapter{Research Questions}
|
||||
|
||||
Setting up and maintaining the paging-structure, as well as allocating physical memory for the virtual pages is a complex task in the \gls{OS}.
|
||||
Developing this part of the \gls{OS} is error-prone, and is not well-supported by mainstream \glspl{proglang}.
|
||||
|
||||
\subsection{Definition Of Additional Analysis Rules To Extend Safety Checks}
|
||||
\section{Definition Of Additional Analysis Rules To Extend Safety Checks}
|
||||
% TODO: How can Business Logical
|
||||
% Examples:
|
||||
% TLB needs to be reset on Task Change
|
||||
|
@ -36,18 +308,19 @@ Developing this part of the \gls{OS} is error-prone, and is not well-supported b
|
|||
|
||||
\chapter{Porting \glsentrytext{C} Vulnerabilities}
|
||||
\label{rnd::porting-c-vulns}
|
||||
In this chapter, the examples from \autoref{TODO} ported to \gls{Rust} for evaluation.
|
||||
In this chapter, the weakness manifestations from \cref{context::common-mem-safety-mistakes::manifestations} are rewritten in \gls{Rust} to learn to what level they are mitigated just by porting them.
|
||||
|
||||
\chapter{\glsentrytext{LX} Modules Written In \glsentrytext{Rust}}
|
||||
|
||||
\chapter{Existing \glsentrytext{OS}-Development Projects Based On Rust}
|
||||
\label{rnd::existing-os-in-rust}
|
||||
\label{rnd::existing-os-dev-wity-rust}
|
||||
|
||||
\section{Libraries}
|
||||
|
||||
\subsection{Libfringe}
|
||||
% TODO: https://github.com/edef1c/libfringe
|
||||
|
||||
|
||||
\section{Systems}
|
||||
\subsection{intermezzOS}
|
||||
\subsection{Blog OS}
|
||||
|
@ -55,6 +328,7 @@ In this chapter, the examples from \autoref{TODO} ported to \gls{Rust} for evalu
|
|||
\subsection{Tock}
|
||||
|
||||
\chapter{\glsentrytext{imezzos}: Adding Preemptive \glsentrytext{OS}-Level Multitasking}
|
||||
\label{rnd::imezzos-preemptive-multitasking}
|
||||
|
||||
\section{Timed Interrupts For Scheduling and Dispatching}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue