This repository contains openIPE, a microcontroller design based on openMSP430, implementing Texas Instruments' Intellectual Property Encapsulation (IPE) memory isolation feature and featuring a firmware layer that can be used to implement various security-critical features. Aside from the source code of the microcontroller and applications, the repository contains a unit test suite and uses a symbolic execution tool to validate properties of either IPE application or firmware code.
If you extend or improve upon our work, please consider submitting a pull request and cite openIPE as the following:
@inproceedings{bognar25openipe,
title = {{openIPE}: An Extensible Memory Isolation Framework for Microcontrollers},
author = {Bognar, Marton and Van Bulck, Jo},
year = 2025,
booktitle = {10th {IEEE} European Symposium on Security and Privacy (EuroS{\&}P)},
}For a complete introduction to this work, we also strongly encourage reading our EuroS&P'25 paper.
We recommend using Docker Compose for the development environment. You can launch a development container with the following command:
docker compose run --remove-orphans openipeCheck (and if desired, modify) the list of mounted directories in docker-compose.yaml, which will allow you to synchronize files between the container and your local file system.
You can also build the image locally if you'd like to modify the Dockerfile:
docker compose run --build --remove-orphans openipe_localAlternatively, you can follow the steps in the Dockerfile to set up the dependencies on your own machine.
The following sections describe how to run the most important experiments.
All commands are meant to be run inside the container (e.g. via docker compose run openipe).
Run the original regression tests of openMSP430:
cd /openipe/core/sim/rtl_sim/run/ && ./run_allThis will finish with an overview table and a report of the number of successful and unsuccessful tests.
Run the unit tests that validate the security guarantees added by our extensions and the interrupt case study:
cd /openipe/core/sim/rtl_sim/run/ && ./run_ipeBy default this checks that no leakage occurs with all hardware fixes in place.
Re-running with a protection disabled (__OMIT_IPE_FIXES=1 ./run_ipe or __OMIT_SP_SWITCHING=1 ./run_ipe) instead asserts that the expected IPE Exposure vulnerabilities are re-enabled.
During the execution of the case study tests (#24-26), overhead measurements for the interrupt latencies are also provided.
#===================================================================#
# SUMMARY REPORT #
#===================================================================#
+-----------------------------------
| Number of PASSED tests : 26
| Number of SKIPPED tests : 0
| Number of FAILED tests : 0
| Number of ABORTED tests : 0
|----------------------------------
| Number of tests : 26
+----------------------------------Run the framework on the attestation code adapted from VRASED, reporting the number of cycles elapsed in the protected IPE call:
./scripts/run_app.sh ipe-hmacThis builds the Verilator simulator and the ipe-hmac application and runs it on the simulator; other applications under app/ run the same way (e.g. ./scripts/run_app.sh ipe-hello).
The Pandora symbolic execution tool can be run on openIPE binaries. It operates on compiled ELF binaries. For example, first build the hello world IPE application and the bootcode:
make -C /openipe/app/ipe-hello
make -C /openipe/core/sim/verilator build/bootcode.elfThen run Pandora to validate the IPE application binary, or the firmware:
cd /pandora && source ./venv/bin/activate
# validate IPE application:
./pandora.py run -c config-debugging.ini /openipe/app/ipe-hello/ipe-hello.elf
# validate firmware:
./pandora.py run -c config-debugging.ini /openipe/core/sim/verilator/build/bootcode.elfThe Pandora reports will be stored in /pandora/logs/debugging_logs/. If you use docker compose or manually map the volumes, you will be able to access these logs on your host machine and open them in a browser.
The sdk/ directory provides a source-to-source C compilation toolchain that automates the boilerplate required to safely call in and out of the IPE-protected region. compiler.py and linker.py act as drop-in replacements for msp430-elf-gcc and are wired in via Makefile.include:
CC = $(OPENIPE)/compiler.py
LD = $(OPENIPE)/linker.pyAnnotate C code with macros from libipe/ipe_support.h:
| Macro | Purpose |
|---|---|
IPE_ENTRY |
Entry point callable from untrusted code (ecall) |
IPE_FUNC |
Internal IPE function (not directly callable from outside) |
IPE_VAR |
Protected variable (placed in .ipe_vars) |
IPE_CONST |
Protected constant (placed in .ipe_const) |
One compilation unit must also include DECLARE_IPE_STRUCT; to emit the hardware initialization structure.
annotated C sources libraries (libgcc.a, ...)
│ │
▼ │
compiler.py │
(src→src AST transform) │
│ │
▼ │
.o files │
│ │
└──────────┬────────────┘
▼
linker.py
(generate stubs + link)
│
▼
final ELF binary
┌────────────────────────┐
│ IPE-protected region │ ← hardware boundary
├────────────────────────┤
│ untrusted code/data │
└────────────────────────┘
compiler.py performs a source-to-source AST transformation (via pycparser) on each annotated file:
IPE_ENTRY fn()is split: the body moves tofn_internal()inside the IPE region;fn()becomes a generated ecall stub.- Calls from IPE code to untrusted functions are rewritten to
fn_stub()(ocall trampolines). - Calls to compiler helper routines (
libgcc.a) are intercepted at assembly level and rewritten to secure, intra-IPE variants. - Argument register usage (r12–r15) is encoded as a bitmap and embedded as weak ELF symbols (
__ipe_ecall_*,__ipe_ocall_*) for the linker.
linker.py reads those symbols across all object files, instantiates assembly templates to produce the entry-dispatch table and ocall stubs, and links everything with a custom linker script. Different versions of IPE entry stubs (e.g., with or without secure interrupt support) can be specified via a JSON config file.
The runtime stubs implement the ecall/ocall entry points in assembly, with register clearing to prevent leakage across the IPE boundary.
main untrusted stub IPE "enclave"
| | ╔══════════════════════════════╗
│ │ ║ IPE stub IPE app ║
├─ fn ────>│ ║ │ │ ║
│ ├─ ipe_entry ──>─┤ │ ║
│ │ ║ ├─ fn_internal ─>│ ║
│ │ ║ │ │ ... ║
│ │ ║ │<─ ocall_cb_fn ─┤ ║
│ │<─ ocall_stub ──┤ │ ║
│<─ cb_fn ─┤ ║ │ │ ║
... │ │ ║ │ │ ║
├── ret ──>│ ║ │ │ ║
│ ├─ ipe_entry ──>─┤ │ ║
│ │ ║ ├─ ocall ret ───>│ ║
│ │ ║ │ │ ... ║
│ │ ║ │<─── ecall ret ─┤ ║
│ │<─ ecall ret ───┤ │ ║
│<─ ret ───┤ ║ ║
│ │ ╚══════════════════════════════╝
Ecalls (untrusted->IPE) route through the single hardware entry point ipe_entry, which initializes secure registers and stack, before dispatching to fn_internal().
Ocalls (IPE->untrusted) use a generated trampoline ocall_cb_fn() that lives inside the IPE region: it saves and clears secret registers, calls the untrusted cb_fn(), and the untrusted return re-enters IPE via ipe_entry to restore trusted registers before resuming.
The following is a non-exhaustive list of the most important directories and files that are relevant for the memory isolation implementation and the security evaluation.
core/rtl/verilog: contains the source files for the HDL implementation. Newly introduced files or files with notable changes areperiph/ipe_periph.v,omsp_frontend.v,omsp_mem_backbone.v,openMSP430.v.core/sim/rtl_sim/bin/ipe_linker.x: linker script used for IPE support.core/sim/rtl_sim/bin/ipe_macros.s: utility scripts for the software development framework.core/sim/rtl_sim/run/run_ipe: script to run IPE unit tests.sdk/: software development framework (compiler/linker toolchain, libipe).app/ipe-hello: IPE hello world project in C.app/ipe-hmac: software attestation case study.
One of the most important features of openIPE is the extensible firmware.
You can find the firmware implementing the IPE bootcode in core/sim/rtl_sim/src/ipe/bootcode.s43, and the version extended to implement the FW-IRQ secure interrupt scheme in core/sim/rtl_sim/src/ipe/bootcode-fw-irq.s43.
In the IPE unit test file we added additional options to switch between different firmware implementations.
Finally, the openIPE linker script and the assembly macros contain useful options for firmware modifications.
In case of suggestions or questions, please open a pull request or an issue!
openIPE is based on the excellent openMSP430 core and is released under a BSD-3-Clause license.
