Exercises related to Robust Design Patterns - Part 2
Stack
Leveraging stack Overflow
Exercice 1
In an interesting article of Arstechnica - How security flaws work: The buffer overflow the following is stated:
The problem of buffer flows is more serious: they often lead to code execution. This happens because those overflowed buffers won’t just overwrite data. They can also overwrite the other important thing kept on the stack—those return addresses. The return address controls which instructions the processor will execute when it’s finished with the current function; it’s meant to be some location within the calling function, but if it gets overwritten in a buffer overflow, it could point anywhere. If attackers can control the buffer overflow, they can control the return address; if they can control the return address, they can choose what code the processor executes next.
In the same article, the Morris Worm ( The buffer overflow - Blame C ) is explained. Have a go at this and explain:
- what was the main attack vector used?
- what leveraging means was part of the attack?
- what is the intrinsic challenge with buffer handling?
Solution
- The
fingerd
program listened for network connections on port 79 and usinggets()
. The function would spawn thefinger
program, passing it theusername
if there was one. The finger program was the one that did the real work of listing users or providing information about any specific user.fingerd
was simply responsible for listening to the network and startingfinger
appropriately - but was the one the attacker leveraged to generate an overflow. - The fact that no buffer size was controlled - nor boundary set - allowed
attackers to easily generate a buffer overflow. In fact,
gets()
is a function that will read from standard input until the person at the keyboard presses return - no matter whether the entered data fits the buffer - Generally, C has a fundamental weakness: buffers do not know their own
size, and the language never validates the reads and writes performed
on buffers, allowing them to overflow. This needs to be understood - and
taken into account - by the developer. In addition, even though some of
C’s string handling functions do take a parameter for the buffer size,
they can do so in a way that still leads to errors and overflows. For
instance, C offers a pair of siblings to
strcat()
andstrcpy()
calledstrncat()
andstrncpy()
. The extran
in their names denotes that they take a size parameter, of sorts. Butn
is not, as many naive C programmers believe, the size of the buffer being written to; it is the number of characters from the source to copy.
Overflow checking
Exercice 2
- The following code is what ARM Keil uses for checking whether a stack overflow
has occurred. Can you explain what it does?
/// Check current running Thread Stack. /// \param[in] thread running thread. /// \return true - success, false - failure. bool_t osRtxThreadStackCheck (const os_thread_t *thread) { if ((thread->sp <= (uint32_t)thread->stack_mem) || (*((uint32_t *)thread->stack_mem) != osRtxStackMagicWord)) { return FALSE; } return TRUE; }
- What happens when a stack overflow is detected? Does the system have the possibility of intervening? Hint: check out Configure RTX v5
Solution
- The explanation is illustrated here:
/// Check current running Thread Stack. /// \param[in] thread running thread. /// \return true - success, false - failure. bool_t osRtxThreadStackCheck (const os_thread_t *thread) { // Check if the stack pointer (sp) is less than or equal to the base // address of the stack memory. If this is true, it means that the thread // has overflowed its stack. // If the stack pointer is within the bounds of the stack memory, then check // the first word of the stack memory. This word is expected to contain a // predefined magic value (osRtxStackMagicWord), which is written to the // stack when the thread is created. If the magic value is not present, // it means that the stack has been corrupted in some way if ((thread->sp <= (uint32_t)thread->stack_mem) || (*((uint32_t *)thread->stack_mem) != osRtxStackMagicWord)) { return FALSE; } return TRUE; }
- Yes, the link Stack Overflow Checking
explains that
If a stack overflow is detected, the function
osRtxErrorNotify
with error codeosRtxErrorStackOverflow
is called. By default, this function is implemented as an endless loop and will practically stop code execution
Exercice 3
You have now set stack sizes for your applications - without too much thoughts about sizes. While a Dynamic Stack Analysis (with the help of watermarking) may help you figuring out how much stack your own tasks require, the main stack (MSP) is trickier. Read Keil’s Application Note 316, Analysis of Stack Usage (page 5 onwards) carefully and respond to the questions below:
- What is the primary difference between Static and Dynamic Stack Analysis?
- What is that one may miss while using Static Stack Analysis?
- What is the risk (in the result interpretation) of Dynamic Stack Analysis?
Solution
- Static Analysis only requires a compiler to run it, while the Dynamic Analysis requires a running system to be able to run
- Linker call graph report does not contain the additional memory space that is required for “thread context switching”, “automatic register stacking” nor “function pointers” (same applies to assembly code).
- The analysis is dynamic, meaning that the results of your test run depend on the branches of your code that were executed. You may be seeing an “incorrect” result because your test did not go through all branches of your code. In addition, for the main stack (MSP), the stack usage depends on timing of interrupt events. As the worst-case scenario will rarely occur, it is hard to capture the absolute maximum.
Memory
Memory Pools
Memory fragmentation is a challenge as we saw during the lecture. Luckily, ARM Keil does have a response to it in the form of Memory Pools.
Exercice 4
Read the Theory of Operations about Memory Pools and provide an answer to following questions:
- What is the prerequisite to make Memory Pools work?
- What are the 3 types of Memory Management Systems available in ARM Keil?
- What is the advantage of Object-specific Memory Pools?
- What should one pay attention to in case of Statically allocated memory objects?
Solution
- It shall be enabled in System Configuration
- They are:
- Global Memory Pool
- Object-specific Memory Pools
- Static Object Memory
- They are fully time deterministic, which means that object creation and destruction takes always the same fixed amount of time.
- The alignment is dependent on the memory type (all 4-byte aligned except for Thread Stack, which requires a 8-byte)
ARM Keil Objects and User Defined Structures
As stated in the previous exercice, ARM Keil supports Memory Pools for different object types. Obviously, this can be used for ARM Keil objects, while the same be done for structures defined by a developer.
Exercice 5
The document Memory Pool Management provides details about its way of working. Very interesting is the usage example that gives a concrete example how to use it. Study it carefully and respond the following questions:
- Can the different functions be called from an ISR?
- What is necessary so that your structure is served by a Memory Pool?
- Can I use this for sharing data among threads?
Solution
- No, not all of them (e.g.
osMemoryPoolNew
cannot be called fromISR
) - The following is needed:
- Maximum number of instances
- Size of structure
- Note: pay attention that calls cannot happen before
osKernelInitialize()
- Yes, you can. It is even explicitly indicated
Using memory pools for exchanging data, you can share more complex objects between threads if compared to a Message Queue.
However, attention shall be paid that no concurrent access to the data occurs.