Skip to content

examples: Containment tests for a protected build and a kernel build - #3721

Open
casaroli wants to merge 2 commits into
apache:masterfrom
casaroli:fork-ostest-pffault
Open

casaroli wants to merge 2 commits into
apache:masterfrom
casaroli:fork-ostest-pffault

Conversation

@casaroli

@casaroli casaroli commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Two test programs for a protected build and a kernel build.

examples/pffault touches one kernel address from a user process. It is small and direct, and it shows the fault and the signal.

examples/sandbox is the general test. Every target carries the outcome it expects, so the test fails a build that refuses everything as well as one that permits everything.

target address expected
self its own data must succeed
kernel kernel memory must fault
periph a peripheral register must fault
unmapped an address with no mapping must fault

The self target is the control. Without it a build that refuses every access passes every other check.

The offending process allocates memory, writes to all of it, and opens a file before it makes the access. It still holds both when it dies. The test reads /proc/meminfo before, while the offender lives, and after it is reaped, and it reads /proc/<pid>/group/fd while the offender lives. A count that never rises is reported as a failure, because "the same before and after" says nothing if the memory was never seen.

The offender is a process and not a task. A kernel build does not give task_create() to user code, so posix_spawn() is used.

Impact

Two new example programs, pffault and sandbox. No existing configuration in this repository selects them. The separate fork() and vfork() tests this branch used to carry are upstream already, through #3685.

Testing

Board: ESP32-S3-DevKitC with an ESP32-S3-WROOM-2 N32R8V, 32 MB octal flash and 8 MB PSRAM.

Configuration: esp32s3-devkit:kernel_oct, a BUILD_KERNEL image.

sandbox: /proc/meminfo reads
      total       used       free    maxused    maxfree  nused  nfree name
     378616      28336     350280      28704     345576    105      4 Kmem
    4194304    1441792    2752512               2752512               Page

sandbox: target self -- this process's own data, expecting success
sandbox: PASS - the allowed access completed
sandbox: memory 1441792 -> 2162688 -> 1441792
sandbox: PASS - 3 descriptor(s) open, none after

sandbox: target kernel -- kernel memory at 0x3fc98000, expecting a fault
pms_violation_isr: SIGSEGV (PMS) task /system/bin/sandbox
sandbox: PASS - the offending process was terminated

sandbox: target periph -- a peripheral register at 0x600c5000, expecting a fault
pms_violation_isr: SIGSEGV (PMS) task /system/bin/sandbox
sandbox: PASS - the offending process was terminated

sandbox: target unmapped -- an address with no mapping at 0x3d800000, expecting a fault
pms_violation_isr: SIGSEGV (MMU entry) task /system/bin/sandbox
sandbox: PASS - the offending process was terminated

sandbox: CONTAINED - 4 target(s), every check passed

The run above was made three times, which is twelve process deaths. The memory returns to the same value each time, and the descriptors are closed each time.

ostest runs to the end on the same image.

vfork_test: Child 41 ran and exited before the parent resumed
fork_test: Child running independently (child)
fork_test: Parent and child had independent memory
ostest_main: Exiting with status 0

tools/checkpatch.sh -c -u -m -g reports no errors.

Comment thread examples/sandbox/Kconfig
A test that deliberately tries to escape the kernel/user boundary of
a protected or kernel build, and checks that the attempt is contained:
the offending task is terminated and everything else keeps running.
the offending process is terminated and everything else keeps running.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's merge the last two patch into one

@casaroli casaroli Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

…space.

A small user program that reads or writes one address, by default
0x3fc98000, the base of the ESP32-S3 kernel DRAM region.  A user process
must not reach it, so the access must fault and only the process must die.
"pffault w" makes the access a store, and a second argument names another
address.

With CONFIG_ESP32S3_PAGEFAULT and CONFIG_ESP32S3_PAGEFAULT_SELFTEST on the
kernel side, "pffault r 0x80000000" exercises the recoverable-fault restart
path, and "pffault r 0x0" the report path.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
…lds.

A test that a user process which makes a forbidden access is stopped, and
that nothing else is.  It spawns this program again as a separate process
to make the access, because a kernel build does not give user code
task_create(), and checks that the offender died, that the caller still
runs and that a canary thread kept counting.  The canary is what tells
"the offender was contained" from "the whole system stopped".

Every target has the outcome it expects:

  self      the process's own data       must succeed
  kernel    kernel memory                must fault
  periph    a peripheral register        must fault
  unmapped  an address with no mapping   must fault

"self" is the control.  Without it a build that refuses every access
passes every other check.  An MMU keeps processes apart but does not stop
one reaching a peripheral, and an unmapped access is refused by another
mechanism again, so neither is covered by the kernel target.

The offender allocates memory and opens a file before the access.  The
test reads /proc/meminfo and /proc/<pid>/group/fd while it lives and after
it is reaped, and fails if the counts never rose, since "the same before
and after" says nothing if the resources were never seen.

The addresses come from Kconfig, because a user process cannot see kernel
symbols.  A protected build derives the kernel target from
CONFIG_NUTTX_USERSPACE when none is set.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants