SoC
  • Home
  • Arm
  • Arm Cortex M0/M0+
  • Arm Cortex M4
  • Arm Cortex M3
  • Contact
Reading: Avoiding Memory Corruption Issues in Embedded Systems
SUBSCRIBE
SoCSoC
Font ResizerAa
  • Home
  • Arm
  • Arm Cortex M0/M0+
  • Arm Cortex M4
Search
  • Home
  • Arm
  • Arm Cortex M0/M0+
  • Arm Cortex M4
Have an existing account? Sign In
Follow US
  • Looking for Something?
  • Privacy Policy
  • About Us
  • Sitemap
  • Contact Us
© S-O-C.ORG, All Rights Reserved.
Arm

Avoiding Memory Corruption Issues in Embedded Systems

Scott Allen
Last updated: September 20, 2023 1:19 pm
Scott Allen 8 Min Read
Share
SHARE

Memory corruption is a common issue that can plague embedded systems developers. These problems arise when memory is accessed incorrectly or outside of its allocated bounds, leading to unexpected behavior and crashes. Preventing memory corruption requires strategies like proper memory management, input validation, and testing.

Contents
Use a Memory Protection UnitValidate InputsUse Memory Safe LanguagesAllocate Memory CorrectlyAvoid Dangling PointersEnforce Memory Access RulesUse Memory Safe FunctionsIsolate ComponentsHandle Errors GracefullyAdopt a Security MindsetPerform Extensive TestingUse Memory Protection HardwareHandle Memory ExhaustionConclusion

Use a Memory Protection Unit

A memory protection unit (MPU) is hardware that can prevent illegal memory accesses. The MPU divides memory into regions and sets access permissions for each region. Attempts to access restricted areas will trigger an exception rather than corrupt memory. For example, stack and heap regions can be set as read/write while code memory is read-only.

MPUs are available on many ARM Cortex-M and Cortex-R processor cores. The MPU registers must be properly configured at startup for each region’s base address, size, and access permissions. Setting this up takes some work but prevents a whole class of memory corruption issues.

Validate Inputs

One primary source of memory corruption is invalid data from external inputs. This includes inputs from sources like data buses, communication interfaces, user interaction, removable media, and sensors. All inputs should be validated before usage:

  • Check for buffer overflows by validating length and null terminators.
  • Validate the range of numeric values.
  • Check for invalid encodings or characters.
  • Verify checksums, magic values, protocol framing, and parity bits.
  • Handle errors gracefully and safely.

Runtime memory checking tools like Valgrind can help identify buffer overflows during testing. But input validation should be used even with these tools to stop problems at the source.

Use Memory Safe Languages

Memory safe languages automatically prevent buffer overflows, invalid accesses, and other memory issues. For C and C++, functions like strncpy and snprintf are safer alternatives to unsafe functions like strcpy.

Managed languages like Java and C# bound array access and garbage collection prevents use-after-free bugs. Rust’s borrow checker ensures memory safety at compile time. Programming in these languages eliminates entire classes of memory corruption vulnerabilities.

Allocate Memory Correctly

Dynamic memory allocation is prone to issues if not used properly:

  • Use calloc() to zero initialize allocated memory.
  • Check malloc() return values to catch out-of-memory errors.
  • Avoid memory leaks by freeing allocations when done.
  • Use language features like smart pointers in C++.
  • Beware of integer overflows when calculating buffer sizes.

Memory pools are safer and faster than repeated malloc/free calls for temporary buffers. Pool memory in fixed size blocks and enforce limits.

Avoid Dangling Pointers

Dangling pointers reference freed memory and can cause crashes or leaks if dereferenced. These arise from:

  • Freeing memory while pointers still reference it.
  • Returning pointers to stack-allocated local variables.
  • Attempting to access deleted objects in languages like C++.

Use tools like AddressSanitizer to check code for these at runtime. Avoid dangling pointers by carefully managing object lifetimes and pointer scopes.

Enforce Memory Access Rules

Set memory access rules in your code:

  • Initialize variables before usage.
  • Do not read uninitialized memory.
  • Do not write to read-only memory like string literals.
  • Access arrays only within allocated bounds.
  • Do not use freed memory.

The compiler can warn about many invalid memory accesses if the right flags are enabled (e.g. -Werror in GCC). Fix any warnings to avoid bugs.

Use Memory Safe Functions

Replace unsafe C standard library functions with safer versions:

| Unsafe | Safer Alternative |
|-|-|
| strcpy | strcpy_s, strncpy |
| gets | fgets |
| printf | snprintf |
| scanf | fscanf |

These check lengths, restrict reads, enforce null termination, and prevent buffer overflows.

Isolate Components

Isolate software components through techniques like virtual memory, privilege rings, and memory domains. This prevents bugs in one component from corrupting other unrelated components.

Virtual memory uses an MMU to provide separate address spaces for processes. Cortex-A and Cortex-R cores have MMUs.

ARM TrustZone divides hardware into secure and non-secure worlds. Security-critical software runs in the secure world, isolated from the rest.

Handle Errors Gracefully

Bugs will inevitably occur, so make software robust against memory corruptions:

  • Enable stack protection, if available, to catch stack overflows.
  • Handle exceptions and abort gracefully rather than try to continue execution.
  • Safely restart components that may have been compromised.
  • Halt unsafe operations before damage occurs.

Adopt a Security Mindset

Think adversarially – assume all external data and inputs are malicious until validated. Avoid trusting anything outside your code’s control. Identify high-risk areas like parsers, protocol handlers, external inputs, allocated memory, integer operations, array accesses, and pointer usage. Apply secure design principles like fail-safe defaults, least privilege, and reducing the attack surface.

Perform Extensive Testing

Thorough testing is required to weed out memory corruption issues:

  • Run static analysis tools to detect problems at compile time.
  • Use sanitizers like AddressSanitizer during testing to find runtime issues.
  • Perform fuzz testing by sending invalid random inputs at interfaces.
  • Monitor memory usage during long-running tests for leaks.
  • Test on real hardware to detect issues not seen in simulators.
  • Stress test border conditions like out of memory, max array sizes, etc.

Fix any identified issues before release. Ongoing testing will be required as new features are added.

Use Memory Protection Hardware

Dedicated memory protection units offer runtime monitoring of memory accesses:

  • ARM CoreSight MPU monitors up to 16 memory regions.
  • TrustZone Address Space Controller isolates secure memory.
  • External memory protection units can detect invalid accesses.

This hardware can catch errors missed during testing and prevent exploitation. But software errors must still be fixed at the source for a robust solution.

Handle Memory Exhaustion

Out of memory conditions can lead to corruption as allocation fails but code still assumes success. Avoid this by:

  • Checking for failed memory allocations.
  • Only allocating what is needed, minimizing waste.
  • Freeing memory promptly when no longer needed.
  • Prioritizing critical tasks when memory runs low.

Monitor free memory levels during runtime and take appropriate action if it becomes too low.

Conclusion

Memory corruption undermines reliability and security if left unchecked. But with the right tools, design practices, and testing, these issues can be eliminated in embedded systems even on bare metal. Combining memory protection hardware with robust software will result in an embedded device resilient against memory related errors and vulnerabilities.

Newsletter Form (#3)

More ARM insights right in your inbox

 


Share This Article
Facebook Twitter Email Copy Link Print
Previous Article Configuring Memory and Caches for Arm Cortex-R4
Next Article Configuring Interrupts and Exception Handling on Cortex-M1
Leave a comment Leave a comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

2k Followers Like
3k Followers Follow
10.1k Followers Pin
- Sponsored-
Ad image

You Might Also Like

What is arm Cortex-M23?

The ARM Cortex-M23 is a 32-bit processor core designed for…

5 Min Read

Does cortex M series have MMU?

The short answer is yes, the Cortex-M series of ARM…

7 Min Read

Memory Options and Tradeoffs in ARM Cortex-M

ARM Cortex-M microcontrollers offer a variety of memory options to…

12 Min Read

bkpt Instruction in ARM

The bkpt instruction in ARM stands for breakpoint. It allows…

6 Min Read
SoCSoC
  • Looking for Something?
  • Privacy Policy
  • About Us
  • Sitemap
  • Contact Us
Welcome Back!

Sign in to your account