Safety RAM: Protecting variables with one’s complement
Firstly please read the Safety RAM: Protecting memory section with checksum blog post if you haven’t as certain aspects won’t be re-explained.
The problem with previous CRC based method was that there shall be no parallelism or interruption during executing of critical section by others, possibly non-safe, processes. So to deal with this limitation we can try to check consistency of the memory every time we read and protect it every time we write. Nice and simple. Obviously checking and protecting the whole safety memory region using checksum every time we read or write would be a massive overkill. Instead we could try to store the same variable in 2 distinct places in memory and check if those are matching when we are reading. But storing the exactly the same value is not smart enough. It would not help us much in likely cases when e.g. the entirely memory blocks are erased. We would not detect any errors if storing the same variable twice in this case. Luckily we can use one’s complement for this purpose. Basically we swap 0 and 1 when storing the second complement value. Please also remind yourself that these mechanism are intended against random faults, not malicious cyber attack.
Now let’s get our hands dirty. All the source code shown here (and more) can be found on GitHub here.
The logical first step is to define the 2 memory regions as shown on this figure:
This we can achieve by this linker script shown below. Note that unlike the checksum protection we don’t need leave any space for the checksum as there is no checksum. The price for that is that we’ll need twice as memory.
data_safram.ld – Linker script – define custom memory section |
|
Of course one can take this further and place these section take into account the hardware architecture so e.g. these end up on different chips.
Now we need to check and protect the variables on every single write and read performed.
During reading we basically perform following operation to check the value:
In similar way, we write twice to protect it:
In our example memory section 1 is “.data_safram” and memory section 2 is “.data_safram_cplmnt”.
But wait a minute, so I need to do that every time I store or read a value – that’s painful and for sure buggy if one needs to write that manually every single time. So let’s simplify it by using read/write C macros:
safram_cplmnt.h – Write and read macros for safety variables |
|
The one’s complement function is defined as:
safram_cplmnt.h – One’s complement macro |
|
And as as this setup relies on specific names of complementary variables, we can also define C macros to help with the definition:
safram_cplmnt.h – Definition macros for safety variables |
|
Note that the example defines these macros only for arrays of UINT8 but similar macros can be created for any other basic type. The only catch could be with floating values where we need to convert their bit representation to unsigned integer when storing and checking the complement.
Then we can use the macros in our main program.
th_safram_cplmnt.c – Safety variables with complements |
|
And then we can use the same memory corruption program as in here to emulate memory corruption. The critical section is still there but limited in size. The critical moment happens when we check consistency and return the safety variable value as this operation is not atomic in this implementation (e.g. our values still could get corrupted after the memory check is evaluated and thus wrong value is returned without detection). Yet the probability of this happening is much lower as variables are protected and checked as we go.
Process 1 | Process 2 |
th_safram_cplmnt | crc/th_safram_corruption |
|
|
The conclusions are very similar to the one’s for checksum protection really. You can see for yourself (well, at least on openSUSE Leap 15) that the process 1 is restarted whenever we corrupt the memory from process 2. But as said before we did NOT really achieve any ASIL rated memory protection and separation (spatial freedom from interference if you want) compliant with ISO26262. Besides others (e.g. no coding practices required by ISO26262 – Part 6 were followed) the process restart mechanism is not safe. This of course brings a question what part of Linux kernel we would need to make safe in order to be able to rely on this. But that’s beyond the scope of this blog post (and I don’t know really at the moment).
To sum up this dual-storage of one’s complement approach comes handy if we need to protect global static variables on applications where our process can be preempted by or has other less safety critical software components running in parallel. This is the main advantage really. The disadvantage is that we need twice as much as memory for our safety variables and the critical section did not disappear completely, it is still there, but limited in size. Please stay tuned if you wonder about what protection mechanism are implemented in Linux’s memory management.
And as said, all the code is here: https://github.com/llansky3/safram in the “cplmnt” folder.
Memory corruption problems
And please allow me one correction to the last Safety RAM: Protecting memory section with checksum blog post that explains why it was not possible to corrupt the memory on Ubuntu 21, Debian 11 and possible others.
Firstly and the most importantly this was a bug in the my memory corruption program. The start address should have 64 bits and not just 32 bits. Sorry and thanks to my colleagues for pointing this out.. This is fixed now by:
crc/th_safram_corruption.c |
|
This demonstrated itself especially on Ubuntu and Debian where address space layout randomization (ASLR) is enabled and gcc builds with PIE enabled by default. This is not the case in openSUSE Leap 15 and Tumbleweed and thus the addresses land in lower addresses that can be accommodated by 32bit pointer.
But also you may have noticed the “ptrace restrictions are effectively disabled” line in the program output and related part of the code:
crc/th_safram_crc.c |
|
So now let’s explore what happens if we define the PTRACE_NOT_ALLOWED preprocessor macro. In this particular case this can be done by making everything with:
|
You may observer (as I do) that the memory corruption still works fine. This is the case on openSUSE Leap 15 or Tumbleweed, but in other distributions you may get following error when attempting to corrupt the memory:
|
So what stops the memory corruption in these cases? The answer is Yama – Linux Security Module. The “ptrace_scope” is by default it is set 1 = restricted there. The openSUSE Leap 15 or Tumbleweed doesn’t have this module enabled by default. That’s why there is no issue in corrupting memory there. Of course if you are running the corruption program with “sudo” then you will not run into any of these problems.
You can check if you have this module in your distribution and the active ptrace scope by:
|
This page describes how to disable that if you are interested. By the way similar restrictions can be also achieved using AppArmor or SELinux.
Related Articles
Oct 01st, 2024
How to enhance VM Security with AMD SEV-SNP on SLES 15-SP6
Feb 21st, 2023
No comments yet