About
The purpose of this set of mini exercises is to demonstrate why it makes sense to insert extra fields into your structure that serves the only purpose to check that the structure was not corrupted.
The provided MCC (memory-corruption-checker) structure is slightly more advanced
than just adding uint32_t magic
field as it can capture buffer overruns and
similar issues too.
The source code is available in the examples repository.
You can easily compile and run the examples with the following commands in
the mcc
subdirectory.
make
make run
Basic Usage
Look into ok.c
file that uses example_t
structure.
Note that we call mcc_init
once the structure is initialized.
Each operation on the structure is preceded by mcc_assert
that checks that the structure is not corrupted and each modification
must be followed by mcc_update
.
This obviously clutters the code but such checks would be typically hidden in functions encapsulating access to the members so the end-user would not need to care about them that much.
And when your program starts corrupting memory, having few extra lines would be the least of your problems anyway ;-).
How it Works
You probably noticed that make run
launches each program twice: in
release and development mode.
Our MCC implementation allows you to decide (at compile time) whether to turn
on the checks or disable them.
When they are disabled, all calls to MCC-related functions are effectively
no-ops and even the structure is not modified
(i.e. no magic fields are added at all).
How many bytes were added by MCC to the structure?
Hint.Solution.The MCC Structures
Let’s have a look how the MCC structures are used.
Look into example.h
to see how example_t
structure is flanked by
mcc_header_t
and mcc_footer_t
.
These structures contains checksums of the bytes between them and thus
are able to detect that the structure was changed outside expected places.
The structures also contains the so-called redzone - a rather big byte
array that serves single purpose - catch buffer overflows without corrupting
surrounding memory.
When the program is compiled in release mode, the mcc_header_t
and mcc_footer_t
are actually empty structures, thus there is no overhead at all.
The API
The MCC API is rather simple.
You extend your structure with mcc_header_t mcc_header
and mcc_footer_t mcc_footer
and when new object is created, you call mcc_init
.
mcc_init
is actually a macro that expects that your members are named mcc_header
and mcc_footer
.
If you need different names, you need to pass explicitly pointers
to respective mcc_header_t
and mcc_footer_t
fields in the *_hf
functions.
But we recommend to stay with the defaults to make your code more readable.
Whenever you modify the object, you are supposed to call mcc_update
to ensure
the checksum is recomputed from actual data.
This also checks that the redzone is intact.
There is also mcc_assert
that checks that the checksum corresponds with the
actual values and it also checks the redzone.
Notice that both mcc_update
and mcc_assert
are macros. Why is it better to
sacrifice type safety here?
Memory Corruption Detection Examples
The provided examples demonstrates following issues that can be detected by MCC.
Missed update means we modified the object without modifying the checksums. While this could be omission in the source code (this is one rather big disadvantage as we need to update the checksums quite often) it can also denote that we passed an invalid pointer somewhere and modified the structure inadvertently.
Overruns demonstrates situation where our structure contains a buffer (e.g. a thread name) and our code does not check its size properly. Note that the first example demonstrates a typical situation where we overflow into footer. The second example demonstrates what happens when we not only overflow “our” structure but smash the following one too.
Using MCC in Your Kernels
We do not enforce usage of MCC in your kernel but consider using some kind of memory corruption checks to ensure your structures are valid. That is, use at least a single magic number (uint32_t magic
) that is different
for each kernel object (heap header, threads, …) to check that the object
is not corrupted.