Sunday, 17 May 2020

A pre-requiste for the stuff to come

In this series of blog posts, we will try to implement thread-stack protection for RTEMS. But first,

What is thread-stack protection?

Well, the name says it all 😉. Threads, in any OS, are the atomic unit of CPU utilization that has a program-counter, register sets, and a stack that can be thought of as an address space that is private to a particular thread.  It is this private address space that we want to protect from accidental or malicious data read/writes. Which leads us to another question - 

How do we protect thread stacks?


This is a two-part answer, there is the concept, the abstract idea, of thread-stack protection, and the actual implementation - 
  • It is important to understand as to how can a thread's stack be corrupted. There are multiple ways to corrupt a thread's stack(this is an interesting and informative read of one of the cases),  our focus will be on providing protection against the case when a second thread somehow gains access to address space of our target thread. The idea is to somehow have a setting where for the executing thread the address space of all the other thread either becomes 'invisible' or if it somehow tries to access the address space the OS throws an error. Now how do we implement such a mechanism?
  • Enter the MMU (Memory management unit), as the name suggests(I know I am getting repetitive 😛) is a piece of hardware present on most of the modern processors that can take care of some of the more complex ways of accessing memories for us. This brings us very nicely to our next question -
How do we implement thread stack protection using MMU?


We will be taking the example of the ARMv7-A processor for this demonstration, this processor is on some of the famous boards (Raspberrypi, BeagleBones, etc.) added to that is the fact that RTEMS already has a very mature MMU support for this processor.

For this blog post, I will omit some of the complex details(That will be covered in the next post).
Right now we have to understand some important concepts related to memory operation:-
       
  1. Memory access permissions:- One of the best features of an MMU is the fact that we can decide what kind of operation can be done on a certain memory region, for example, we can have set a 2Kb memory region to permit only read operations and the immediate next 2Kb to support read and write operation and the next after that with no-access. We just have to set a permutation of two bits for the memory region of our choice in an already specified register in the MMU to specify the type of operation that can be done on that memory region, anything other than that and you will find your OS throwing exceptions at you, and probably for a good cause!! But another question arises it's all well and good that we can specify access permission to memory regions, but how do we select these memory regions!?         
  2. Virtual Address- Virtual addresses are the memory addresses that the CPU generates, these addresses are then translated by the MMU to the actual physical address of the memory on our system. For this project, we will have 1:1 VA-PA translation i.e. we will set our address translation scheme in such a way that the physical address and the virtual address are the same. Wait, we can control address translation?                                                                                       
  3.  Pages and Page Table:-  Suppose we have the case we took in point 1, with small pieces of memory requiring different types of access permissions. If we were to set memory access bits for read-only operations it will apply to the complete address space and we won't have any read-write memory region left! So what do we do?                                                                                        A simple solution is to change memory permission for the entire address space at each context switch. But this will lead to a lot of problems, for starters we will be losing a lot of memory to read-only operations for the sake of 2KiB worth of memory and then there is the problem of the .txt,.bss and other memory regions that need to have separate access permissions for every context.                                                                                                                                 A more efficient solution is to divide the address space into regions of our requirement and set the memory access permission for these regions independent of other regions, this way we won't be wasting memory and can have separate regions with different access permission for each context. These regions are more commonly known as pages. But the question still remains,  how does the MMU address these pages and how do we set memory permission for a particular page. Now this will require a long explanation and a simple image can do it all pretty concisely-       

Page Table organization and address translation
                                                                                          



It will be simpler if we don't focus on the address bits and think about the concept involved.
Basically, each page table contains certain entries known as page entries that contain a part of the physical address or the address of the next page table, in both cases, it is the base address, as well as access permission bits. The virtual address value is used for indexing by adding it with the page table entry and we get the physical address. For example, suppose we have 0x223 as the page table entry, the last two bits are 0b11 and are used for access permission, so we are left with 0x220 now we combine it with a part of the virtual address let us say 0x15 making the value 0x22015 and voila! We have the actual physical address.                                                                             
 In this manner,  by setting up page tables and page table entries we can set memory permission for selected regions of memory. Now, this is a very crude example with a lot of details missing and not everything may just be clear yet, it will be in the next post when we go into the gory details of the implementation and try to isolate two blocks of memory.

For now,  try to piece everything together and wait for the next post!!

No comments:

Post a Comment