Files
threadx/scripts/check_module_mmu_a7.sh
T
Frédéric Desbiens a9bd72de21 Merge commit from fork
* Fixed the Cortex-A7 module data check to validate whole ranges

The Cortex-A7 module port received a module instance, a start address and a
byte size from the common Module Manager, then discarded the instance and the
size and asked the MMU to translate the start address alone, for reading only.
A range was therefore accepted whenever its first byte happened to be readable
by the module, so privileged dispatch code could read or write past the end of
the module's mapping, or use read-only module code as a write destination.

The check now takes the size and an access intent. The module's own data region
is answered from the manager's records, which name it exactly and cost no
translations; everything else is answered by translating every page the range
touches with the requested unprivileged access. Empty ranges, ranges whose last
byte would wrap, and ranges that leave the recorded data region partway through
are all refused, and the walk is bounded by TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES
so one module request cannot impose unbounded work on the kernel. Translation is
only believed while the requesting module's own context is loaded.

The outside direction gets its own check rather than negating the inside one:
a range that reaches into the module only partway is inside neither answer, and
negating a whole-range check would have let it pass as a kernel object.

Other ports keep their single data check through backward-compatible fallbacks
in the common header; their preprocessed dispatch output is byte-identical.

Regression coverage runs on the host by standing a simulated page map behind the
one architecture primitive, and reaches 100% line, branch and call coverage of
the new logic. Twenty-four of its expectations fail against the previous
implementation.

Assisted-by: Claude Code (Opus 5)

* Drove the Cortex-A7 range check through a live MMU

The host test for this check stands a simulated page map behind the port's CP15
primitive, so it never executes an address translation. That leaves the part
most easily got wrong unexercised: an encoding naming the wrong operation, or a
PAR fault bit read the wrong way round, passes it without complaint.

This adds a bare-metal image that builds a short-descriptor translation table,
enables the MMU, and drives the real check through the real translations on a
real Cortex-A7 translation regime, plus a script that builds and runs it under
an emulator. Sections are mapped for unprivileged read/write, unprivileged read
only, and privileged only, with an unmapped section behind the read-only one so
that a range can be made to leave its mapping partway through.

That last case is the one the replaced check accepted, and the test asserts
both answers against the same live MMU: the current check rejects the range,
and translating only its first address accepts it.

It also confirms what the simulated map could only assume, that ATS1CUW denies
a write to a read-only mapping while ATS1CUR allows the read. The write intent
is the reason the port asks for two translations rather than one.

The script skips with a notice when the cross toolchain or the emulator is
absent, so a machine without them does not fail the build.

Assisted-by: Claude Code (Opus 5)
2026-09-28 11:31:41 -04:00

121 lines
4.0 KiB
Bash
Executable File

#!/bin/bash
##############################################################################
# Copyright (c) 2026 Eclipse ThreadX contributors
#
# This program and the accompanying materials are made available under the
# terms of the MIT License which is available at
# https://opensource.org/licenses/MIT.
#
# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5).
# The AI-generated portions may be considered public domain (CC0-1.0)
# and not subject to the project's licence. The human contributor has
# reviewed and verified that the code is correct.
#
# SPDX-License-Identifier: MIT and CC0-1.0
##############################################################################
# Runs the Cortex-A7 module range check against a live MMU.
#
# The host test for that check stands a simulated page map behind the port's
# CP15 primitive, so it never executes an address translation. An encoding that
# named the wrong operation, or a PAR fault bit read the wrong way round, would
# pass it. This check builds a bare-metal image that turns the MMU on and drives
# the real check through the real translations, then runs it under an emulator.
#
# scripts/check_module_mmu_a7.sh
#
# Environment:
# CROSS_CC arm-none-eabi C compiler; defaults to arm-none-eabi-gcc.
# QEMU Arm system emulator; defaults to qemu-system-arm.
#
# Exit status is 0 when the image runs and every expectation holds, 1 when an
# expectation fails, and 0 with a notice when the tools are absent, so that a
# machine without a cross toolchain does not fail the build.
set -u
CC="${CROSS_CC:-arm-none-eabi-gcc}"
QEMU_BIN="${QEMU:-qemu-system-arm}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
SRC="$ROOT/test/tx/module_manager/threadx_module_manager_cortex_a7_mmu_test.c"
PORT="$ROOT/ports_module/cortex_a7/gnu/module_manager/src/txm_module_manager_inside_data_check.c"
for tool in "$CC" "$QEMU_BIN"; do
if ! command -v "$tool" >/dev/null 2>&1 && [ ! -x "$tool" ]; then
echo "Skipping: $tool not found."
echo " Needs an arm-none-eabi toolchain and qemu-system-arm."
exit 0
fi
done
# The port supplies the two architecture primitives from its register setup
# file, which cannot be compiled here because it also builds the module page
# tables. They are provided by the image itself, copied from that file, so what
# runs is the same instruction sequence the port issues.
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
cat > "$work/primitives.c" <<'PRIMITIVES'
#define TX_SOURCE_CODE
#include "tx_api.h"
#include "txm_module.h"
UINT _txm_module_manager_address_probe(ULONG address, UINT write_request)
{
ULONG translation;
if (write_request == ((UINT) TXM_MODULE_MANAGER_ACCESS_WRITE))
{
__asm volatile ("MCR p15, 0, %0, c7, c8, 3" : : "r"(address) : );
}
else
{
__asm volatile ("MCR p15, 0, %0, c7, c8, 2" : : "r"(address) : );
}
__asm volatile ("ISB");
__asm volatile ("MRC p15, 0, %0, c7, c4, 0" : "=r"(translation) : : );
if (translation & TXM_ADDRESS_TRANSLATION_FAULT_BIT)
{
return(TX_FALSE);
}
return(TX_TRUE);
}
ULONG _txm_module_manager_current_asid_get(VOID)
{
ULONG contextidr;
__asm volatile ("MRC p15, 0, %0, c13, c0, 1" : "=r"(contextidr) : : );
return(contextidr & TXM_CONTEXTIDR_ASID_MASK);
}
PRIMITIVES
"$CC" -mcpu=cortex-a7 -marm -O1 -std=gnu99 -Wall -Wextra -Werror \
--specs=rdimon.specs \
-I"$ROOT/ports_module/cortex_a7/gnu/inc" \
-I"$ROOT/common/inc" \
-I"$ROOT/common_modules/inc" \
-I"$ROOT/common_modules/module_manager/inc" \
-o "$work/mmu_test.elf" "$SRC" "$PORT" "$work/primitives.c" || {
echo "FAIL: the Cortex-A7 MMU test image did not build."
exit 1
}
output="$("$QEMU_BIN" -M realview-pb-a8 -cpu cortex-a7 -m 128 -nographic \
-semihosting -kernel "$work/mmu_test.elf" 2>/dev/null)"
status=$?
echo "$output"
if [ $status -ne 0 ] || ! echo "$output" | grep -q "SUCCESS!"; then
echo "FAIL: the Cortex-A7 MMU test did not report success."
exit 1
fi
exit 0