From d8dfdcc8d195e9f56fb3fe13e6d050b975e3734a Mon Sep 17 00:00:00 2001 From: Yakun Xu Date: Thu, 23 Jul 2026 04:42:28 +0800 Subject: [PATCH] [script] isolate test environment using unprivileged unshare namespace (#13364) This change introduces script/_maybe_isolate to run test scripts inside an isolated network and mount namespace using unshare -nmr. Key changes: - Add script/_maybe_isolate to create a Linux unprivileged user/mount namespace, enable the loopback interface (lo), mount tmpfs on /run, and define a mock sudo function to execute commands seamlessly within the namespace. - Source script/_maybe_isolate in script/check-infra-if-index-changed and script/check-posix-pty when executing checks. - Update GitHub Actions POSIX workflow (.github/workflows/posix.yml) to set kernel.apparmor_restrict_unprivileged_userns=0, enabling unprivileged user namespaces on Ubuntu runners. --- .github/workflows/posix.yml | 2 + script/_maybe_isolate | 82 +++++++++++++++++++++++++++++ script/check-infra-if-index-changed | 2 + script/check-posix-pty | 6 ++- script/make-pretty | 2 +- 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 script/_maybe_isolate diff --git a/.github/workflows/posix.yml b/.github/workflows/posix.yml index 23e368784..c552ee174 100644 --- a/.github/workflows/posix.yml +++ b/.github/workflows/posix.yml @@ -161,6 +161,7 @@ jobs: run: | sudo apt-get update sudo apt-get --no-install-recommends install -y expect lcov libreadline-dev net-tools ninja-build + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 sudo bash script/install_socat cd /tmp wget https://github.com/obgm/libcoap/archive/bsd-licensed.tar.gz @@ -207,6 +208,7 @@ jobs: run: | sudo apt-get update sudo apt-get --no-install-recommends install -y net-tools ninja-build + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Build run: | script/check-infra-if-index-changed build diff --git a/script/_maybe_isolate b/script/_maybe_isolate new file mode 100644 index 000000000..b73a8d447 --- /dev/null +++ b/script/_maybe_isolate @@ -0,0 +1,82 @@ +#!/bin/bash +# Copyright (c) 2026, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then + echo "Error: This script must be sourced, not executed directly." >&2 + exit 1 +fi + +if [[ -n ${OT_ISOLATED_REEXEC:-} ]]; then + ip link set lo up + + if [[ -d /run ]]; then + mount -t tmpfs tmpfs /run + fi + + if [[ -d /var/run && ! -L /var/run ]]; then + mount -t tmpfs tmpfs /var/run + fi + + sudo() + { + while [[ $# -gt 0 ]]; do + case "$1" in + -E) + shift + ;; + --) + shift + break + ;; + -*) + echo "Error: unsupported mock sudo option: $1" >&2 + return 1 + ;; + *) + break + ;; + esac + done + + "$@" + } + + export -f sudo + return 0 +elif [[ $EUID -eq 0 ]]; then + # Isolation is not necessary + return 0 +elif ! unshare -nmr --kill-child true >/dev/null 2>&1; then + # Isolation is not supported or restricted (e.g., non-Linux or unprivileged userns disabled) + return 0 +fi + +# Unprivileged Linux: Re-exec inside unshare +export OT_ISOLATED_REEXEC=1 + +CALLER_SCRIPT="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]:-$0}" +exec unshare -nmr --kill-child bash "$CALLER_SCRIPT" "$@" diff --git a/script/check-infra-if-index-changed b/script/check-infra-if-index-changed index d72ff7879..51f7ce2fc 100755 --- a/script/check-infra-if-index-changed +++ b/script/check-infra-if-index-changed @@ -159,6 +159,8 @@ main() do_build ;; check) + # shellcheck source=script/_maybe_isolate + source script/_maybe_isolate do_check ;; *) diff --git a/script/check-posix-pty b/script/check-posix-pty index bb9ad2f52..88f7c556d 100755 --- a/script/check-posix-pty +++ b/script/check-posix-pty @@ -108,7 +108,7 @@ do_check() # macOS cannot explicitly set network interface name NETIF_NAME=$(grep -o 'Thread interface: .\+' "${OT_OUTPUT}" | cut -d: -f2 | tr -d ' \r\n') OT_CTL_PATH="$PWD/build/posix/src/posix/ot-ctl" - if [[ ${OT_DAEMON_ALLOW_ALL} == 1 ]]; then + if [[ ${OT_DAEMON_ALLOW_ALL} == 1 ]] || [[ -n ${OT_ISOLATED_REEXEC:-} ]]; then OT_CTL=("${OT_CTL_PATH}") else OT_CTL=(sudo "${OT_CTL_PATH}") @@ -217,7 +217,7 @@ EOF sudo killall -9 expect || true sudo killall -9 ot-ctl || true NETIF_INDEX=$(ip link show "${NETIF_NAME}" | cut -f 1 -d ":" | head -n 1) - sudo PATH="$(dirname "${OT_CTL_PATH}"):${PATH}" \ + sudo env PATH="$(dirname "${OT_CTL_PATH}"):${PATH}" \ python3 "$PWD/tests/scripts/misc/test_multicast_join.py" "${NETIF_INDEX}" "${NETIF_NAME}" \ || die 'multicast group join failed' fi @@ -256,6 +256,8 @@ main() do_build ;; check) + # shellcheck source=script/_maybe_isolate + source script/_maybe_isolate do_check ;; *) diff --git a/script/make-pretty b/script/make-pretty index cf20bb47b..e0ece4e22 100755 --- a/script/make-pretty +++ b/script/make-pretty @@ -250,7 +250,7 @@ do_shell_check() | xargs -n10 -P"$OT_BUILD_JOBS" shfmt -i 4 -bn -ci -fn -s -d git ls-files | xargs shfmt -f | grep -v -E "^($(echo "${OT_EXCLUDE_DIRS[@]}" | tr ' ' '|'))" \ - | xargs -n10 -P"$OT_BUILD_JOBS" shellcheck + | xargs -n10 -P"$OT_BUILD_JOBS" shellcheck -x } do_check()