merge #5103 into opencontainers/runc:main

Aleksa Sarai (11):
  gha: test both with and without libpathrs
  build: enable libpathrs by default
  ci: build and install libpathrs
  build: enable builds with libpathrs
  deps: update to cyphar.com/go-pathrs@v0.2.4
  README: document libpathrs build tag
  script: seccomp.sh -> build-seccomp.sh
  build: rename /opt/libseccomp cdylib directory
  build: treat armhf as ARMv7
  dockerfile: switch to Debian 13
  integration: output debug information in fd leak test

LGTMs: rata kolyshkin AkihiroSuda
This commit is contained in:
Aleksa Sarai
2026-03-12 18:56:27 +09:00
26 changed files with 343 additions and 129 deletions
+5 -1
View File
@@ -13,7 +13,8 @@ task:
CIRRUS_WORKING_DIR: /home/runc
GO_VER_PREFIX: "1.25."
BATS_VERSION: "v1.12.0"
RPMS: gcc git-core iptables jq glibc-static libseccomp-devel make criu fuse-sshfs container-selinux policycoreutils
LIBPATHRS_VERSION: "0.2.4"
RPMS: gcc git-core iptables jq glibc-static libseccomp-devel make criu fuse-sshfs container-selinux policycoreutils cargo lld wget
# yamllint disable rule:key-duplicates
matrix:
- DISTRO: almalinux-8
@@ -63,6 +64,9 @@ task:
dnf -y install criu
esac
# Install libpathrs.
/home/runc/script/build-libpathrs.sh "$LIBPATHRS_VERSION" /usr
# Install Go.
URL_PREFIX="https://go.dev/dl/"
# Find out the latest minor release URL.
+43 -6
View File
@@ -15,6 +15,7 @@ permissions:
contents: read
env:
LIBPATHRS_VERSION: "0.2.4"
# Don't ignore C warnings. Note that the output of "go env CGO_CFLAGS" by default is "-g -O2", so we keep them.
CGO_CFLAGS: -g -O2 -Werror
@@ -26,6 +27,7 @@ jobs:
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm]
go-version: [1.24.x, 1.25.x, 1.26.x]
libpathrs: ["libpathrs", ""]
rootless: ["rootless", ""]
race: ["-race", ""]
criu: ["", "criu-dev"]
@@ -75,7 +77,19 @@ jobs:
- name: install deps
run: |
sudo apt update
sudo apt -y install libseccomp-dev sshfs uidmap
sudo apt -y install libseccomp-dev sshfs uidmap lld
- name: install libpathrs ${{ env.LIBPATHRS_VERSION }}
if: ${{ matrix.libpathrs != '' }}
run: |
sudo -E PATH="$PATH" ./script/build-libpathrs.sh "$LIBPATHRS_VERSION" /usr
- name: configure custom BUILDTAGS
if: ${{ matrix.libpathrs == '' }}
run: |-
# Strip out libpathrs from the default buildtags.
CUSTOM_BUILDTAGS="$(make -pn | sed -En '/^BUILDTAGS :?=/ { s/.*=// ; s/\blibpathrs\b//p }')"
echo "CUSTOM_BUILDTAGS=$CUSTOM_BUILDTAGS" >>"$GITHUB_ENV"
- name: install CRIU
if: ${{ matrix.criu == '' }}
@@ -107,7 +121,11 @@ jobs:
check-latest: true
- name: build
run: sudo -E PATH="$PATH" make EXTRA_FLAGS="${{ matrix.race }}" all
run: |
sudo -E PATH="$PATH" make \
${CUSTOM_BUILDTAGS:+BUILDTAGS="$CUSTOM_BUILDTAGS"} \
EXTRA_FLAGS="${{ matrix.race }}" \
all
- name: Setup Bats and bats libs
uses: bats-core/bats-action@4.0.0
@@ -126,7 +144,7 @@ jobs:
- name: unit test
if: matrix.rootless != 'rootless'
run: sudo -E PATH="$PATH" -- make TESTFLAGS="${{ matrix.race }}" localunittest
run: sudo -E PATH="$PATH" -- make ${CUSTOM_BUILDTAGS:+BUILDTAGS="$CUSTOM_BUILDTAGS"} TESTFLAGS="${{ matrix.race }}" localunittest
- name: add rootless user
if: matrix.rootless == 'rootless'
@@ -136,7 +154,7 @@ jobs:
- name: integration test (fs driver)
continue-on-error: ${{ matrix.criu != '' }} # Don't let criu-dev errors fail CI.
run: sudo -E PATH="$PATH" script -e -c 'make local${{ matrix.rootless }}integration'
run: sudo -E PATH="$PATH" script -e -c 'make ${CUSTOM_BUILDTAGS:+BUILDTAGS="$CUSTOM_BUILDTAGS"} local${{ matrix.rootless }}integration'
- name: integration test (systemd driver)
continue-on-error: ${{ matrix.criu != '' }} # Don't let criu-dev errors fail CI.
@@ -147,7 +165,7 @@ jobs:
printf "[Service]\nDelegate=yes\n" | sudo tee /etc/systemd/system/user@.service.d/delegate.conf
sudo systemctl daemon-reload
# Run the tests.
sudo -E PATH="$PATH" script -e -c 'make RUNC_USE_SYSTEMD=yes local${{ matrix.rootless }}integration'
sudo -E PATH="$PATH" script -e -c 'make RUNC_USE_SYSTEMD=yes ${CUSTOM_BUILDTAGS:+BUILDTAGS="$CUSTOM_BUILDTAGS"} local${{ matrix.rootless }}integration'
# We need to continue support for 32-bit ARM.
# However, we do not have 32-bit ARM CI, so we use i386 for testing 32bit stuff.
@@ -170,7 +188,23 @@ jobs:
sudo add-apt-repository -y ppa:criu/ppa || sudo add-apt-repository -y ppa:criu/ppa
# apt-add-repository runs apt update so we don't have to.
sudo apt -qy install libseccomp-dev libseccomp-dev:i386 gcc-multilib libgcc-s1:i386 criu
GCC_VERSION="$(gcc -dumpversion)"
sudo apt -qy install \
lld criu \
libseccomp-dev libseccomp-dev:i386 \
libc-dev:i386 libgcc-s1:i386 libgcc-${GCC_VERSION}-dev:i386 gcc-i686-linux-gnu
# When cross-compiling, GCC 13 and earlier will look for a linker that
# is marked for cross-compilation, which the Ubuntu lld package doesn't
# provide. The solution is to create a symlink ourselves. GCC 14 fixed
# this, see <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111605>.
ln -sv "$(which ld.lld)" /usr/local/bin/i686-linux-gnu-ld.lld
- run: rustup target add i686-unknown-linux-gnu
- name: install libpathrs ${{ env.LIBPATHRS_VERSION }}
run: |
sudo -E PATH="$PATH" ./script/build-libpathrs.sh "$LIBPATHRS_VERSION" /usr 386
sudo ldconfig /usr/386/lib
- name: install go
uses: actions/setup-go@v6
@@ -179,6 +213,9 @@ jobs:
check-latest: true
- name: unit test
env:
CC: i686-linux-gnu-gcc
PKG_CONFIG_PATH: /usr/386/lib/pkgconfig
run: sudo -E PATH="$PATH" -- make GOARCH=386 localunittest
fedora:
+9 -3
View File
@@ -8,11 +8,14 @@ on:
- release-*
pull_request:
workflow_dispatch:
env:
GO_VERSION: 1.25
permissions:
contents: read
env:
GO_VERSION: 1.25
LIBPATHRS_VERSION: "0.2.4"
jobs:
keyring:
runs-on: ubuntu-24.04
@@ -85,7 +88,10 @@ jobs:
- name: install deps
run: |
sudo apt update
sudo apt -y install libseccomp-dev
sudo apt -y install libseccomp-dev lld
- name: install libpathrs ${{ env.LIBPATHRS_VERSION }}
run: |
sudo -E PATH="$PATH" ./script/build-libpathrs.sh "$LIBPATHRS_VERSION" /usr
- name: compile with no build tags
run: make BUILDTAGS=""
- name: compile with runc_nocriu build tag
+17
View File
@@ -46,6 +46,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
particularly useful and was completely obsoleted by the changes to
`/proc/self/exe` sealing we introduced in runc [1.2.0][]. (#5141)
### Changed ###
- Our release binaries and default build configuration now use [libpathrs][] by
default, providiung better hardening against certain kinds of attacks. Users
of runc should not see any changes as a result of this, but pacakgers will
need to adjust their packaging accordingly. runc can still be built without
libpathrs (by building without the `libpathrs` build tag), but we currently
plan to make runc 1.6 *require* libpathrs. (#5103)
- Previously we made an attempt to make our `runc.armhf` release binaries work
with ARMv6 (which would allow runc to work on the original Raspberry Pi).
Unfortunately, this has effectively always been broken (because we
cross-compile `libseccomp` within a Debian container and statically link to
it) and so we are now officially matching [the Debian definition of `armhf`][debian-armhf]
(that is, ARMv7). (#5103)
[libpathrs]: https://github.com/cyphar/libpathrs
[debian-armhf]: https://wiki.debian.org/ArmHardFloatPort
## [1.4.0] - 2025-11-27
> 路漫漫其修远兮,吾将上下而求索!
+31 -15
View File
@@ -1,18 +1,22 @@
ARG GO_VERSION=1.25
ARG BATS_VERSION=v1.12.0
ARG LIBSECCOMP_VERSION=2.6.0
ARG LIBPATHRS_VERSION=0.2.4
FROM golang:${GO_VERSION}-bookworm
FROM golang:${GO_VERSION}-trixie
ARG DEBIAN_FRONTEND=noninteractive
ARG CRIU_REPO=https://download.opensuse.org/repositories/devel:/tools:/criu/Debian_12
ARG CRIU_REPO=https://download.opensuse.org/repositories/devel:/tools:/criu/Debian_13
RUN KEYFILE=/usr/share/keyrings/criu-repo-keyring.gpg; \
wget -nv $CRIU_REPO/Release.key -O- | gpg --dearmor > "$KEYFILE" \
&& echo "deb [signed-by=$KEYFILE] $CRIU_REPO/ /" > /etc/apt/sources.list.d/criu.list \
&& dpkg --add-architecture i386 \
&& printf "%s\n" i386 armel armhf arm64 ppc64el s390x riscv64 | xargs -t -n1 -- dpkg --add-architecture \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
cargo \
cargo-auditable \
clang \
criu \
gcc \
gcc-multilib \
@@ -22,6 +26,7 @@ RUN KEYFILE=/usr/share/keyrings/criu-repo-keyring.gpg; \
iptables \
jq \
kmod \
lld \
pkg-config \
python3-minimal \
sshfs \
@@ -29,13 +34,13 @@ RUN KEYFILE=/usr/share/keyrings/criu-repo-keyring.gpg; \
uidmap \
iproute2 \
&& apt-get install -y --no-install-recommends \
libc-dev:i386 libgcc-s1:i386 \
gcc-aarch64-linux-gnu libc-dev-arm64-cross \
gcc-arm-linux-gnueabi libc-dev-armel-cross \
gcc-arm-linux-gnueabihf libc-dev-armhf-cross \
gcc-powerpc64le-linux-gnu libc-dev-ppc64el-cross \
gcc-s390x-linux-gnu libc-dev-s390x-cross \
gcc-riscv64-linux-gnu libc-dev-riscv64-cross \
libc-dev:i386 libgcc-s1:i386 gcc-i686-linux-gnu libstd-rust-dev:i386 \
gcc-aarch64-linux-gnu libc-dev-arm64-cross libstd-rust-dev:arm64 \
gcc-arm-linux-gnueabi libc-dev-armel-cross libstd-rust-dev:armel \
gcc-arm-linux-gnueabihf libc-dev-armhf-cross libstd-rust-dev:armhf \
gcc-powerpc64le-linux-gnu libc-dev-ppc64el-cross libstd-rust-dev:ppc64el \
gcc-s390x-linux-gnu libc-dev-s390x-cross libstd-rust-dev:s390x \
gcc-riscv64-linux-gnu libc-dev-riscv64-cross libstd-rust-dev:riscv64 \
&& apt-get clean \
&& rm -rf /var/cache/apt /var/lib/apt/lists/* /etc/apt/sources.list.d/*.list
@@ -54,14 +59,25 @@ RUN cd /tmp \
&& ./install.sh /usr/local \
&& rm -rf /tmp/bats-core
ARG RELEASE_ARCHES="386 amd64 arm64 armel armhf ppc64le riscv64 s390x"
ENV DYLIB_DIR=/opt/runc-dylibs
# install libseccomp
ARG LIBSECCOMP_VERSION
COPY script/seccomp.sh script/lib.sh /tmp/script/
RUN mkdir -p /opt/libseccomp \
&& /tmp/script/seccomp.sh "$LIBSECCOMP_VERSION" /opt/libseccomp 386 amd64 arm64 armel armhf ppc64le riscv64 s390x
COPY script/build-seccomp.sh script/lib.sh /tmp/script/
RUN mkdir -p $DYLIB_DIR \
&& /tmp/script/build-seccomp.sh "$LIBSECCOMP_VERSION" $DYLIB_DIR $RELEASE_ARCHES
ENV LIBSECCOMP_VERSION=$LIBSECCOMP_VERSION
ENV LD_LIBRARY_PATH=/opt/libseccomp/lib
ENV PKG_CONFIG_PATH=/opt/libseccomp/lib/pkgconfig
# install libpathrs
ARG LIBPATHRS_VERSION
COPY script/build-libpathrs.sh /tmp/script/
RUN mkdir -p $DYLIB_DIR \
&& /tmp/script/build-libpathrs.sh "$LIBPATHRS_VERSION" $DYLIB_DIR $RELEASE_ARCHES
ENV LIBPATHRS_VERSION=$LIBPATHRS_VERSION
ENV LD_LIBRARY_PATH=$DYLIB_DIR/lib
ENV PKG_CONFIG_PATH=$DYLIB_DIR/lib/pkgconfig
# Prevent the "fatal: detected dubious ownership in repository" git complain during build.
RUN git config --global --add safe.directory /go/src/github.com/opencontainers/runc
+1 -1
View File
@@ -12,7 +12,7 @@ GIT_BRANCH_CLEAN := $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g")
RUNC_IMAGE := runc_dev$(if $(GIT_BRANCH_CLEAN),:$(GIT_BRANCH_CLEAN))
PROJECT := github.com/opencontainers/runc
EXTRA_BUILDTAGS :=
BUILDTAGS := seccomp urfave_cli_no_docs
BUILDTAGS := seccomp urfave_cli_no_docs libpathrs
BUILDTAGS += $(EXTRA_BUILDTAGS)
COMMIT := $(shell git describe --dirty --long --always)
+3
View File
@@ -112,6 +112,7 @@ make EXTRA_BUILDTAGS="runc_nocriu"
| Build Tag | Feature | Enabled by Default | Dependencies |
|---------------|---------------------------------------|--------------------|---------------------|
| `seccomp` | Syscall filtering using `libseccomp`. | yes | `libseccomp` |
| `libpathrs` | Use [`libpathrs`][] for path safety. | yes | [`libpathrs`][] |
| `runc_nocriu` | **Disables** runc checkpoint/restore. | no | `criu` |
The following build tags were used earlier, but are now obsoleted:
@@ -120,6 +121,8 @@ The following build tags were used earlier, but are now obsoleted:
- **apparmor** (since runc v1.0.0-rc93 the feature is always enabled)
- **selinux** (since runc v1.0.0-rc93 the feature is always enabled)
[`libpathrs`]: https://github.com/cyphar/libpathrs
### Running the test suite
`runc` currently supports running its test suite via Docker.
+1 -1
View File
@@ -28,7 +28,7 @@ require (
)
require (
cyphar.com/go-pathrs v0.2.1 // indirect
cyphar.com/go-pathrs v0.2.4 // indirect
github.com/cilium/ebpf v0.17.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
+2 -2
View File
@@ -1,5 +1,5 @@
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
cyphar.com/go-pathrs v0.2.4 h1:iD/mge36swa1UFKdINkr1Frkpp6wZsy3YYEildj9cLY=
cyphar.com/go-pathrs v0.2.4/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/checkpoint-restore/go-criu/v7 v7.2.0 h1:qGiWA4App1gGlEfIJ68WR9jbezV9J7yZdjzglezcqKo=
github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo=
+151
View File
@@ -0,0 +1,151 @@
#!/bin/bash
# Copyright (C) 2026 Open Containers Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -xEeuo pipefail
# shellcheck source=./script/lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
PLATFORM="$(get_platform)"
declare -A GOARCH_TO_RUST_TARGET=(
["386"]=i686-unknown-linux-gnu
["amd64"]=x86_64-unknown-linux-gnu
["arm64"]=aarch64-unknown-linux-gnu
["armel"]=armv5te-unknown-linux-gnueabi
["armhf"]=armv7-unknown-linux-gnueabihf
["ppc64le"]=powerpc64le-unknown-linux-gnu
["s390x"]=s390x-unknown-linux-gnu
["riscv64"]=riscv64gc-unknown-linux-gnu
)
declare -A RUST_TARGET_TO_CC=(
["i686-unknown-linux-gnu"]="i686-${PLATFORM}-gcc"
["x86_64-unknown-linux-gnu"]="x86_64-${PLATFORM}-gcc"
["aarch64-unknown-linux-gnu"]="aarch64-${PLATFORM}-gcc"
["armv5te-unknown-linux-gnueabi"]="arm-${PLATFORM}eabi-gcc"
["armv7-unknown-linux-gnueabihf"]="arm-${PLATFORM}eabihf-gcc"
["powerpc64le-unknown-linux-gnu"]="powerpc64le-${PLATFORM}-gcc"
["s390x-unknown-linux-gnu"]="s390x-${PLATFORM}-gcc"
["riscv64gc-unknown-linux-gnu"]="riscv64-${PLATFORM}-gcc"
)
# sha256 checksums for libpathrs release tarballs.
declare -A LIBPATHRS_SHA256=(
["0.2.4"]=45aca68e698b844fae0cf7c459bc441519b0e7b48397caa7d3936cfc68d73f77
)
function generate_cargo_config() {
for rust_target in "${GOARCH_TO_RUST_TARGET[@]}"; do
local target_gcc="${RUST_TARGET_TO_CC[$rust_target]}"
# Based on <https://wiki.debian.org/Rust#Crosscompiling>.
cat <<-EOF
[target.$rust_target]
linker = "$target_gcc"
rustflags = ["-L", "$(rustc --print sysroot)/lib/rustlib/$rust_target/lib"]
EOF
done
}
# Due to libpathrs being MPLv2/LGPLv3 we must include its sources, so
# download, install and build against it.
# Parameters:
# $1 -- libpathrs version to download and build.
# $2 -- destination directory.
# $@ -- additional architectures to cross-compile for (optional)
function build_libpathrs() {
local ver="$1"
shift
local dest="$1"
shift
local go_arches=("$@")
local tar="libpathrs-${ver}.tar.xz"
# Download, check, and extract.
wget "https://github.com/cyphar/libpathrs/releases/download/v${ver}/${tar}"{,.asc}
sha256sum --strict --check - <<<"${LIBPATHRS_SHA256[${ver}]} *${tar}"
local srcdir
srcdir="$(mktemp -d)"
tar xf "$tar" -C "$srcdir"
pushd "$srcdir/libpathrs-$ver" || return
# Use cargo-auditable if available.
if cargo auditable --version &>/dev/null; then
export CARGO="cargo auditable"
fi
local extra_cargo_flags=("--locked")
# If we are being asked to install this in a system library directory
# (i.e., --prefix=/usr or something similar), the correct place to put
# libpathrs.so depends very strongly on the distro we are running on, and
# detecting this in a generic way is quite difficult.
#
# The simplest solution is to use a disto-packaged binary to detect where
# libc.so is installed and use the same import path, so we look at
# /proc/self/maps and parse out the parent directory of the libc.so being
# used.
local native_libdir libdir=
native_libdir="$(awk '$NF ~ /\/libc\>.*\.so/ { print $NF; }' /proc/self/maps |
sort -u | head -n1 | xargs dirname)"
if [[ "$native_libdir" == "$dest/"* ]]; then
libdir="$native_libdir"
fi
# Install native version for Dockerfile builds.
make \
EXTRA_CARGO_FLAGS="${extra_cargo_flags[*]}" \
release
./install.sh \
--prefix="$dest" \
--libdir="$libdir"
cargo clean
local cargo_config
cargo_config="$(mktemp --tmpdir runc-libpathrs-cargo.toml.XXXXXX)"
# shellcheck disable=SC2064 # We want to resolve the path here.
trap "rm -f '$cargo_config'" EXIT
# Only use the cross-compile config when we actually need to cross-compile.
# RedHat-based distros insist on calling their targets "$ARCH-redhat-linux"
# which breaks our above logic, but we don't ever need to cross-compile on
# RedHat distros so we can ignore this.
generate_cargo_config >"$cargo_config"
extra_cargo_flags+=("--config=$cargo_config")
for go_arch in "${go_arches[@]}"; do
local rust_target="${GOARCH_TO_RUST_TARGET[$go_arch]}"
make \
EXTRA_CARGO_FLAGS="${extra_cargo_flags[*]} --target=$rust_target" \
release
./install.sh \
--rust-target="$rust_target" \
--prefix="$dest/$go_arch"
cargo clean
done
# Place the source tarball to $dest/src.
popd || return
mkdir -p "$dest"/src
mv "$tar"{,.asc} "$dest"/src
}
if [ $# -lt 2 ]; then
echo "Usage: $0 <version> <dest-dir> [<extra-arch> ...]" >&2
exit 1
fi
build_libpathrs "$@"
@@ -60,12 +60,12 @@ function build_libseccomp() {
# Place the source tarball to $dest/src.
popd || return
mkdir "$dest"/src
mkdir -p "$dest"/src
mv "$tar"{,.asc} "$dest"/src
}
if [ $# -lt 2 ]; then
echo "Usage: seccomp.sh <version> <dest-dir> [<extra-arch> ...]" >&2
echo "Usage: $0 <version> <dest-dir> [<extra-arch> ...]" >&2
exit 1
fi
+32 -26
View File
@@ -1,41 +1,56 @@
#!/bin/bash
# get_platform computes the platform section of target triples on this OS.
function get_platform() {
# Fedora doesn't have ID_LIKE and only has ID=fedora, so we need to
# construct a fake ID_LIKE to treat AlmaLinux and Fedora the same way.
local ID_LIKE
# shellcheck source=/etc/os-release disable=SC1091 # outside our sources
ID_LIKE="$(
source /etc/os-release
echo "${ID:-} ${ID_LIKE:-}"
)"
local PLATFORM
case "$ID_LIKE" in
*suse*)
PLATFORM=suse-linux
;;
*rhel* | *fedora* | *centos*)
PLATFORM=redhat-linux
;;
*)
PLATFORM=linux-gnu
;;
esac
echo "$PLATFORM"
}
# set_cross_vars sets a few environment variables used for cross-compiling,
# based on the architecture specified in $1.
function set_cross_vars() {
GOARCH="$1" # default, may be overridden below
unset GOARM
PLATFORM=linux-gnu
# openSUSE has a custom PLATFORM
if grep -iq "ID_LIKE=.*suse" /etc/os-release; then
PLATFORM=suse-linux
is_suse=1
fi
PLATFORM="$(get_platform)"
[[ "$PLATFORM" == *suse* ]] && is_suse=1
case $1 in
case "$1" in
386)
# Always use the 64-bit compiler to build the 386 binary, which works
# for the more common cross-build method for x86 (namely, the
# equivalent of dpkg --add-architecture).
local cpu_type
if [ -v is_suse ]; then
# There is no x86_64-suse-linux-gcc, so use the native one.
HOST=
cpu_type=i586
else
HOST=x86_64-${PLATFORM}
cpu_type=i686
fi
HOST=x86_64-${PLATFORM}
CFLAGS="-m32 -march=$cpu_type ${CFLAGS[*]}"
;;
amd64)
if [ -n "${is_suse:-}" ]; then
# There is no x86_64-suse-linux-gcc, so use the native one.
HOST=
else
HOST=x86_64-${PLATFORM}
fi
HOST=x86_64-${PLATFORM}
;;
arm64)
HOST=aarch64-${PLATFORM}
@@ -48,16 +63,7 @@ function set_cross_vars() {
armhf)
HOST=arm-${PLATFORM}eabihf
GOARCH=arm
# "armhf" means ARMv7 for Debian, ARMv6 for Raspbian.
# ARMv6 is chosen here for compatibility.
#
# https://wiki.debian.org/RaspberryPi
#
# > Raspberry Pi OS builds a single image for all of the Raspberry families,
# > so you will get an armhf 32-bit, hard floating-point system, but built
# > for the ARMv6 ISA (with VFP2), unlike Debian's ARMv7 ISA (with VFP3)
# > port.
GOARM=6
GOARM=7
;;
ppc64le)
HOST=powerpc64le-${PLATFORM}
+13 -15
View File
@@ -20,6 +20,7 @@ set -e
# Project-specific options and functions. In *theory* you shouldn't need to
# touch anything else in this script in order to use this elsewhere.
: "${LIBSECCOMP_VERSION:=2.6.0}"
: "${LIBPATHRS_VERSION:=0.2.4}"
project="runc"
root="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")/..")"
@@ -40,14 +41,16 @@ function build_project() {
shift
local arches=("$@")
# Assume that if /opt/libseccomp exists, then we are run
# via Dockerfile, and seccomp is already built.
local seccompdir=/opt/libseccomp temp_dir
if [ ! -d "$seccompdir" ]; then
temp_dir="$(mktemp -d)"
seccompdir="$temp_dir"
# Assume that if /opt/runc-dylibs exists, then we are running via
# Dockerfile, and thus seccomp is already built. Otherwise, build it now.
local dylibdir=/opt/runc-dylibs
if ! [ -d "$dylibdir" ]; then
trap 'rm -rf "$dylibdir"' EXIT
dylibdir="$(mktemp -d)"
# Download and build libseccomp.
"$root/script/seccomp.sh" "$LIBSECCOMP_VERSION" "$seccompdir" "${arches[@]}"
"$root/script/build-seccomp.sh" "$LIBSECCOMP_VERSION" "$dylibdir" "${arches[@]}"
# Download and build libpathrs.
"$root/script/build-libpathrs.sh" "$LIBPATHRS_VERSION" "$dylibdir" "${arches[@]}"
fi
# For reproducible builds, add these to EXTRA_LDFLAGS:
@@ -70,7 +73,7 @@ function build_project() {
CFLAGS="$original_cflags"
set_cross_vars "$arch"
make -C "$root" \
PKG_CONFIG_PATH="$seccompdir/$arch/lib/pkgconfig" \
PKG_CONFIG_PATH="$dylibdir/$arch/lib/pkgconfig" \
"${make_args[@]}"
"$STRIP" "$root/$project"
mv "$root/$project" "$builddir/$project.$arch"
@@ -84,13 +87,8 @@ function build_project() {
exit 1
fi
# Copy libseccomp source tarball.
cp "$seccompdir"/src/* "$builddir"
# Clean up.
if [ -n "$tempdir" ]; then
rm -rf "$tempdir"
fi
# Copy dylib source tarballs.
cp "$dylibdir"/src/* "$builddir"
}
# End of the easy-to-configure portion.
+6
View File
@@ -112,6 +112,9 @@ tmp_seccomp_gpgflags=("--homedir=$tmp_gpgdir" "--no-default-keyring" "--keyring=
gpg "${tmp_seccomp_gpgflags[@]}" --recv-keys 0x47A68FCE37C7D7024FD65E11356CE62C2B524099
gpg "${tmp_seccomp_gpgflags[@]}" --recv-keys 0x7100AADFAE6E6E940D2E0AD655E45A5AE8CA7C8A
tmp_libpathrs_gpgflags=("--homedir=$tmp_gpgdir" "--no-default-keyring" "--keyring=libpathrs.keyring" "--keyserver=keys.openpgp.org")
gpg "${tmp_libpathrs_gpgflags[@]}" --recv-keys 0xC9C370B246B09F6DBCFC744C34401015D1D2D386
gpg "${gpgflags[@]}" --clear-sign <<<"[This is test text used for $project release scripts. $(date --rfc-email)]" |
gpg "${tmp_runc_gpgflags[@]}" --verify || bail "Signing key ${keyid:-DEFAULT} is not in trusted $project.keyring list!"
@@ -156,4 +159,7 @@ gpg "${tmp_runc_gpgflags[@]}" --verify "$project.$hashcmd"
# Verify seccomp tarball.
gpg "${tmp_seccomp_gpgflags[@]}" --verify libseccomp*.asc
# Verify libpathrs tarball.
gpg "${tmp_libpathrs_gpgflags[@]}" --verify libpathrs*.asc
popd
+7 -2
View File
@@ -1,7 +1,7 @@
#!/bin/bash
set -eux -o pipefail
DNF=(dnf -y --setopt=install_weak_deps=False --setopt=tsflags=nodocs --exclude="kernel,kernel-core")
RPMS=(bats git-core glibc-static golang jq libseccomp-devel make)
RPMS=(bats git-core glibc-static golang jq libseccomp-devel cargo lld make wget)
# Work around dnf mirror failures by retrying a few times.
for i in $(seq 0 2); do
sleep "$i"
@@ -16,11 +16,16 @@ fi
dnf clean all
SCRIPTDIR="$(dirname "${BASH_SOURCE[0]}")"
LIBPATHRS_VERSION="0.2.4"
"$SCRIPTDIR"/build-libpathrs.sh "$LIBPATHRS_VERSION" /usr
# To avoid "avc: denied { nosuid_transition }" from SELinux as we run tests on /tmp.
mount -o remount,suid /tmp
# Setup rootless user.
"$(dirname "${BASH_SOURCE[0]}")"/setup_rootless.sh
"$SCRIPTDIR"/setup_rootless.sh
# Delegate cgroup v2 controllers to rootless user via --systemd-cgroup
mkdir -p /etc/systemd/system/user@.service.d
+2
View File
@@ -69,6 +69,8 @@ is_allowed_fdtarget() {
if ! is_allowed_fdtarget "$target"; then
echo "Violation: FD $fd_name -> '$target'"
violation_found=1
else
echo "Permitted: FD $fd_name -> '$target'"
fi
done < <(find "/proc/$pid/fd" -type l -print0)
[ "$violation_found" -eq 0 ]
+1 -1
View File
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: MPL-2.0
#
# libpathrs: safe path resolution on Linux
# Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
# Copyright (C) 2019-2025 SUSE LLC
# Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
+1 -1
View File
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
* Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
+3 -7
View File
@@ -3,8 +3,8 @@
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
* Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -30,11 +30,9 @@ import (
// you can try to use [Root.Open] or [Root.OpenFile].
//
// It is critical that perform all relevant operations through this [Handle]
// (rather than fetching the file descriptor yourself with [Handle.IntoRaw]),
// (rather than fetching the underlying [os.File] yourself with [Handle.IntoFile]),
// because the security properties of libpathrs depend on users doing all
// relevant filesystem operations through libpathrs.
//
// [os.File]: https://pkg.go.dev/os#File
type Handle struct {
inner *os.File
}
@@ -43,7 +41,7 @@ type Handle struct {
// handle will be copied by this method, so the original handle should still be
// freed by the caller.
//
// This is effectively the inverse operation of [Handle.IntoRaw], and is used
// This is effectively the inverse operation of [Handle.IntoFile], and is used
// for "deserialising" pathrs root handles.
func HandleFromFile(file *os.File) (*Handle, error) {
newFile, err := fdutils.DupFile(file)
@@ -92,8 +90,6 @@ func (h *Handle) OpenFile(flags int) (*os.File, error) {
// calling [Handle.Close] will also close any copies of the returned [os.File].
// If you want to get an independent copy, use [Handle.Clone] followed by
// [Handle.IntoFile] on the cloned [Handle].
//
// [os.File]: https://pkg.go.dev/os#File
func (h *Handle) IntoFile() *os.File {
// TODO: Figure out if we really don't want to make a copy.
// TODO: We almost certainly want to clear r.inner here, but we can't do
+1 -1
View File
@@ -3,8 +3,8 @@
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
* Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
+1 -1
View File
@@ -5,8 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
* Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
+3 -3
View File
@@ -3,8 +3,8 @@
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
* Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -100,7 +100,7 @@ func InRootReadlink(rootFd uintptr, path string) (string, error) {
size := 128
for {
linkBuf := make([]byte, size)
n := C.pathrs_inroot_readlink(C.int(rootFd), cPath, C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf)))
n := C.pathrs_inroot_readlink(C.int(rootFd), cPath, C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.size_t(len(linkBuf)))
switch {
case int(n) < C.__PATHRS_MAX_ERR_VALUE:
return "", fetchError(n)
@@ -301,7 +301,7 @@ func ProcReadlinkat(procRootFd int, base ProcBase, path string) (string, error)
linkBuf := make([]byte, size)
n := C.pathrs_proc_readlinkat(
C.int(procRootFd), cBase, cPath,
C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf)))
C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.size_t(len(linkBuf)))
switch {
case int(n) < C.__PATHRS_MAX_ERR_VALUE:
return "", fetchError(n)
+4 -11
View File
@@ -3,8 +3,8 @@
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
* Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -56,16 +56,15 @@ var (
// *before* you call wait(2)or any equivalent method that could reap
// zombies).
func ProcPid(pid int) ProcBase {
if pid < 0 || pid >= 1<<31 {
if pid < 0 || uint64(pid) >= 1<<31 {
panic("invalid ProcBasePid value") // TODO: should this be an error?
}
return ProcBase{inner: libpathrs.ProcPid(uint32(pid))}
pid32 := uint32(pid) //nolint:gosec // G115 false positive <https://github.com/securego/gosec/issues/1212>
return ProcBase{inner: libpathrs.ProcPid(pid32)}
}
// ThreadCloser is a callback that needs to be called when you are done
// operating on an [os.File] fetched using [Handle.OpenThreadSelf].
//
// [os.File]: https://pkg.go.dev/os#File
type ThreadCloser func()
// Handle is a wrapper around an *os.File handle to "/proc", which can be
@@ -181,8 +180,6 @@ func (proc *Handle) OpenRoot(path string, flags int) (*os.File, error) {
// Unlike [Handle.OpenThreadSelf], this method does not involve locking
// the goroutine to the current OS thread and so is simpler to use and
// theoretically has slightly less overhead.
//
// [runtime.LockOSThread]: https://pkg.go.dev/runtime#LockOSThread
func (proc *Handle) OpenSelf(path string, flags int) (*os.File, error) {
file, closer, err := proc.open(ProcSelf, path, flags)
if closer != nil {
@@ -228,10 +225,6 @@ func (proc *Handle) OpenPid(pid int, path string, flags int) (*os.File, error) {
// callback MUST be called AFTER you have finished using the returned
// [os.File]. This callback is completely separate to [os.File.Close], so it
// must be called regardless of how you close the handle.
//
// [runtime.LockOSThread]: https://pkg.go.dev/runtime#LockOSThread
// [os.File]: https://pkg.go.dev/os#File
// [os.File.Close]: https://pkg.go.dev/os#File.Close
func (proc *Handle) OpenThreadSelf(path string, flags int) (*os.File, ThreadCloser, error) {
return proc.open(ProcThreadSelf, path, flags)
}
+2 -28
View File
@@ -3,8 +3,8 @@
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
* Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -54,8 +54,6 @@ func OpenRoot(path string) (*Root, error) {
// still be closed by the caller.
//
// This is effectively the inverse operation of [Root.IntoFile].
//
// [os.File]: https://pkg.go.dev/os#File
func RootFromFile(file *os.File) (*Root, error) {
newFile, err := fdutils.DupFile(file)
if err != nil {
@@ -109,8 +107,6 @@ func (r *Root) ResolveNoFollow(path string) (*Handle, error) {
// ergonomic to use.
//
// This is effectively equivalent to [os.Open].
//
// [os.Open]: https://pkg.go.dev/os#Open
func (r *Root) Open(path string) (*os.File, error) {
return r.OpenFile(path, os.O_RDONLY)
}
@@ -127,8 +123,6 @@ func (r *Root) Open(path string) (*os.File, error) {
//
// This is effectively equivalent to [os.OpenFile], except that os.O_CREAT is
// not supported.
//
// [os.OpenFile]: https://pkg.go.dev/os#OpenFile
func (r *Root) OpenFile(path string, flags int) (*os.File, error) {
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) {
fd, err := libpathrs.InRootOpen(rootFd, path, flags)
@@ -145,8 +139,6 @@ func (r *Root) OpenFile(path string, flags int) (*os.File, error) {
//
// Unlike [os.Create], if the file already exists an error is created rather
// than the file being opened and truncated.
//
// [os.Create]: https://pkg.go.dev/os#Create
func (r *Root) Create(path string, flags int, mode os.FileMode) (*os.File, error) {
unixMode, err := toUnixMode(mode, false)
if err != nil {
@@ -194,8 +186,6 @@ func (r *Root) RemoveFile(path string) error {
// directory tree.
//
// This is effectively equivalent to [os.Remove].
//
// [os.Remove]: https://pkg.go.dev/os#Remove
func (r *Root) Remove(path string) error {
// In order to match os.Remove's implementation we need to also do both
// syscalls unconditionally and adjust the error based on whether
@@ -219,8 +209,6 @@ func (r *Root) Remove(path string) error {
// RemoveAll recursively deletes a path and all of its children.
//
// This is effectively equivalent to [os.RemoveAll].
//
// [os.RemoveAll]: https://pkg.go.dev/os#RemoveAll
func (r *Root) RemoveAll(path string) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootRemoveAll(rootFd, path)
@@ -233,8 +221,6 @@ func (r *Root) RemoveAll(path string) error {
// mode is used for the new directory (the process's umask applies).
//
// This is effectively equivalent to [os.Mkdir].
//
// [os.Mkdir]: https://pkg.go.dev/os#Mkdir
func (r *Root) Mkdir(path string, mode os.FileMode) error {
unixMode, err := toUnixMode(mode, false)
if err != nil {
@@ -253,8 +239,6 @@ func (r *Root) Mkdir(path string, mode os.FileMode) error {
// directories created by this function (the process's umask applies).
//
// This is effectively equivalent to [os.MkdirAll].
//
// [os.MkdirAll]: https://pkg.go.dev/os#MkdirAll
func (r *Root) MkdirAll(path string, mode os.FileMode) (*Handle, error) {
unixMode, err := toUnixMode(mode, false)
if err != nil {
@@ -278,9 +262,7 @@ func (r *Root) MkdirAll(path string, mode os.FileMode) (*Handle, error) {
// directory tree. The provided mode is used for the new directory (the
// process's umask applies).
//
// This is effectively equivalent to [unix.Mknod].
//
// [unix.Mknod]: https://pkg.go.dev/golang.org/x/sys/unix#Mknod
// This is effectively equivalent to [golang.org/x/sys/unix.Mknod].
func (r *Root) Mknod(path string, mode os.FileMode, dev uint64) error {
unixMode, err := toUnixMode(mode, true)
if err != nil {
@@ -298,8 +280,6 @@ func (r *Root) Mknod(path string, mode os.FileMode, dev uint64) error {
// created at path and is a link to target.
//
// This is effectively equivalent to [os.Symlink].
//
// [os.Symlink]: https://pkg.go.dev/os#Symlink
func (r *Root) Symlink(path, target string) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootSymlink(rootFd, path, target)
@@ -314,8 +294,6 @@ func (r *Root) Symlink(path, target string) error {
// host).
//
// This is effectively equivalent to [os.Link].
//
// [os.Link]: https://pkg.go.dev/os#Link
func (r *Root) Hardlink(path, target string) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootHardlink(rootFd, path, target)
@@ -327,8 +305,6 @@ func (r *Root) Hardlink(path, target string) error {
// Readlink returns the target of a symlink with a [Root]'s directory tree.
//
// This is effectively equivalent to [os.Readlink].
//
// [os.Readlink]: https://pkg.go.dev/os#Readlink
func (r *Root) Readlink(path string) (string, error) {
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (string, error) {
return libpathrs.InRootReadlink(rootFd, path)
@@ -345,8 +321,6 @@ func (r *Root) Readlink(path string) (string, error) {
// calling [Root.Close] will also close any copies of the returned [os.File].
// If you want to get an independent copy, use [Root.Clone] followed by
// [Root.IntoFile] on the cloned [Root].
//
// [os.File]: https://pkg.go.dev/os#File
func (r *Root) IntoFile() *os.File {
// TODO: Figure out if we really don't want to make a copy.
// TODO: We almost certainly want to clear r.inner here, but we can't do
+1 -1
View File
@@ -3,8 +3,8 @@
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
* Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
+1 -1
View File
@@ -1,4 +1,4 @@
# cyphar.com/go-pathrs v0.2.1
# cyphar.com/go-pathrs v0.2.4
## explicit; go 1.18
cyphar.com/go-pathrs
cyphar.com/go-pathrs/internal/fdutils