Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

NuttX Protected Build

The Traditional "Flat" Build

The traditional NuttX build is a "flat" build. By flat, I mean that when you build NuttX, you end up with a single "blob" called nuttx. All of the components of the build reside in the same address space. All components of the build can access all other components of the build.

The "Two Pass" Protected Build

The NuttX protected build, on the other hand, is a "two-pass" build and generates two "blobs": (1) a separately compiled and linked kernel blob called, again, nuttx and separately compiled and linked user blob called in nuttx_user.elf (in the existing build configurations). The user blob is created on pass 1 and the kernel blob is created on pass2.

...

  • nuttx. The pass2 kernel-space ELF file
  • nuttx.hex. The pass2 Intel HEX file (selected in defconfig)
  • System.map. Symbols in the kernel-space ELF file

The Memory Protection Unit

If the MCU supports a Memory Protection Unit (MPU), then the logic within the kernel blob all execute in kernel-mode, i.e., with all privileges. These privileged threads can access all memory, all CPU instructions, and all MCU registers. The logic executing within the user-mode blob, on the other hand, all execute in user-mode with certain restrictions as enforced by the MCU and by the MPU. The MCU may restrict access to certain registers and machine instructions; with the MPU, access to all kernel memory resources are prohibited from the user logic. This includes the kernel blob's FLASH, .bss/.data storage, and the kernel heap memory.

Advantages of the Protected Build

The advantages of such a protected build are (1) security and (2) modularity. Since the kernel resources are protected, it will be much less likely that a misbehaving task will crash the system or that a wild pointer access will corrupt critical memory. This security also provides a safer environment in which to execute 3rd party software and prevents "snooping" into the kernel memory from the hosted applications.

Modularity is assured because there is a strict control of the exposed kernel interfaces. In the flat build, all symbols are exposed and there is no enforcement of a kernel API. With the protected build, on the other hand, all interactions with the kernel from the user application logic must use system calls (or syscalls) to interface with the OS. A system call is necessary to transition from user-mode to kernel-mode; all user-space operating system interfaces are via syscall proxies. Then, while in kernel mode, the kernel system call handler will perform the OS service requested by the application. At the conclusion of system processing, user-privileges are restored and control is return to the user application. Since the only interactions with the kernel can be through support system calls, modularity of the OS is guaranteed.

User-Space Proxies/Kernel-Space Stubs

The same OS interfaces are exposed to the application in both the "flat" build and the protected build. The difference is that in the protected build, the user-code interfaces with a proxy for the OS function. For example, here is what a proxy for the OS getpid() interface:

...

Thus the getpid() proxy is a stand-in for the real OS getpid() interface that executes a system call so the kernel code can perform the real getpid() operation on behalf of the user application.
Proxies are auto-generated for all exported OS interfaces using the CSV file syscall/syscall.csv and the program tools/mksyscalls.
Similarly, on the kernel-side, there are auto-generated stubs that map the system calls back into real OS calls.
These, however, are internal to the OS and the implementation may be architecture-specific.
See the README.txt files in those directories for further information.

Combining Intel HEX Files

One issue that you may face is that the two pass builds creates two FLASH images. Some debuggers that I use will allow me to write each image to FLASH separately. Others will expect to have a single Intel HEX image. In this latter case, you may need to combine the two Intel HEX files into one. Here is how you can do that:

...

Then use the combined.hex file with for FLASH/JTAG tool. If you do this a lot, you will probably want to invest a little time to develop a tool to automate these steps.

Files and Directories

Here is a summary of directories and files used by the STM32F4Discovery protected build:

  • boards/arm/stm32/stm32f4discovery/configs/kostest. This is the kernel mode OS test configuration. The two standard configuration files can be found in this directory: (1) defconfig and (2) Make.defs.
  • boards/arm/stm32/stm32f4discovery/kernel. This is the first past build directory. The Makefile in this directory is invoked to produce the pass1 object (nuttx_user.elf in this case). The second pass object is created by arch/arm/src/Makefile. Also in this directory is the file userspace.c. The user-mode blob contains a header that includes information need by the kernel blob in order to interface with the user-code. That header is defined in by this file.
  • boards/arm/stm32/stm32f4discovery/scripts. Linker scripts for the kernel mode build are found in this directory. This includes (1) memory.ld which hold the common memory map, (2) user-space.ld that is used for linking the pass1 user-mode blob, and (3) kernel-space.ld that is used for linking the pass1 kernel-mode blob.

Alignment, Regions, and Subregions

There are some important comments in the memory.ld file that are worth duplicating here:

...

"For the same reasons, the maximum size of the SRAM mapping is limited to 4KB. Both of these alignment limitations could be reduced by using multiple MPU regions to map the FLASH/SDRAM range or perhaps with some clever use of subregions."

Memory Management

At present, there are two options for memory management in the NuttX protected build:

Single User Heap

By default, there is only a single user-space heap and heap allocator that is shared by both kernel- and user-modes. PROs: Simple and makes good use of the heap memory space, CONs: Awkward architecture and no security for kernel-mode allocations.

Dual, Partitioned Heaps

Two configuration options can change this behavior:

...

NOTE: There are security issues with calling into the user space allocators in kernel mode. That is a security hole that could be exploit to gain control of the system! Instead, the kernel code should switch to user mode before entering the memory allocator stubs (perhaps via a trap). The memory allocator stubs should then trap to return to kernel mode (as does the signal handler now).

The Traditional Approach

A more traditional approach would use something like the interface sbrk(). The sbrk() function adds memory to the heap space allocation of the calling process. In this case, there would still be kernel- and user-mode instances of the memory allocators. Each would sbrk() as necessary to extend their heap; the pages allocated for the kernel-mode allocator would be protected but the pages allocated for the user-mode allocator would not. PROs: Meets all of the needs. CONs: Complex. Memory losses due to quantization.

...

Many MCUs will have Memory Protection Units (MPUs) that can support the security features (only). However these lower end MPUs may not support sufficient mapping capability to support this traditional approach. The ARMv7-M MPU, for example, only supports eight protection regions to manage all FLASH and SRAM and so this approach would not be technically feasible for th ARMv7-M family (Cortex-M3/4).

Comparing the "Flat" Build Configuration with the Protected Build Configuration

Compare, for example the configuration boards/arm/stm32/stm32f4discovery/configs/ostest and the configuration boards/arm/stm32/stm32f4discovery/configs/kostest. These two configurations are identical except that one builds a "flat" version of OS test and the other builds a kernel version of the OS test. See the file boards/arm/stm32/stm32f4discovery/README.txt for more details about those configurations.

...

  • CONFIG_SYS_RESERVED=8. The user application logic interfaces with the kernel blob using system calls. The architecture-specific logic may need to reserved a few system calls for its own internal use. The ARMv7-M architectures all require 8 reserved system calls.
  • CONFIG_SYS_NNEST=2. System calls may be nested. The system must retain information about each nested system call and this setting is used to set aside resources for nested system calls. In the current architecture, a maximum nesting level of two is all that is needed.
  • CONFIG_ARMV7M_MPU=y. This settings enables support for the ARMv7-M Memory Protection Unit (MPU). The MPU is used to prohibit user-mode access to kernel resources.
  • CONFIG_ARMV7M_MPU_NREGIONS=8. The ARMv7-M MPU supports 8 protection regions.

Size Expansion

The protected build will, or course, result in a FLASH image that is larger than that of the corresponding "flat" build. How much larger? I don't have the numbers in hand, but you can build boards/arm/stm32/stm32f4discovery/configs/nsh and boards/arm/stm32/stm32f4discovery/configs/kostest and compare the resulting binaries for yourself using the size command.

...

  • The syscall layer is included in the protected build but not the flat build.
  • The kernel-size _syscal_l stubs will cause all enabled OS code to be drawn into the build. In the flat build, only those OS interfaces actually called by the application will be included in the final objects.
  • The dual memory allocators will increase size.
  • Code duplication. Some code, such as the C library, will be duplicated in both the kernel- and user-blobs, and
  • Alignment. The alignments required by the MPU logic will leave relatively large regions of FLASH (and perhaps RAM) is not usable.

Performance Issues

The only performance differences using the protected build should result as a consequence of the sycalls used to interact with the OS vs. the direct C calls as used in the flat build. If your performance is highly dependent upon high rate OS calls, then this could be an issue for you. But, in the typical application, OS calls do not often figure into the critical performance paths.

...