From a1fddcd61ad6307c0840f14324016ee488c49a7c Mon Sep 17 00:00:00 2001 From: Gianluca Renzi Date: Tue, 13 Oct 2026 23:53:26 +0200 Subject: [PATCH 1/4] portable_option(): no overflow of portable_dir; readlink() leaves room for the nul "%s.%s" of a path of up to PATH_MAX - 1 characters could overflow portable_dir: snprintf() now fails cleanly on a path that is too long. Found by gcc's -Wformat-overflow (an error with -Werror) when building for ppc64 with gcc. The three readlink() calls into fullpath could fill all of it, and the terminating nul then went past its end: they now read at most sizeof(fullpath) - 1 bytes. --- src/runtime/runtime.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/runtime/runtime.c b/src/runtime/runtime.c index a073f60..bc8b0fe 100644 --- a/src/runtime/runtime.c +++ b/src/runtime/runtime.c @@ -724,14 +724,17 @@ void portable_option(const char* arg, const char* appimage_path, const char* nam char portable_dir[PATH_MAX]; char fullpath[PATH_MAX]; - ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath)); + ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath) - 1); if (length < 0) { fprintf(stderr, "Error getting realpath for %s\n", appimage_path); exit(EXIT_FAILURE); } fullpath[length] = '\0'; - sprintf(portable_dir, "%s.%s", fullpath, name); + if (snprintf(portable_dir, sizeof(portable_dir), "%s.%s", fullpath, name) >= (int) sizeof(portable_dir)) { + fprintf(stderr, "Path of the portable %s directory too long: %s.%s\n", name, fullpath, name); + exit(EXIT_FAILURE); + } if (!mkdir(portable_dir, S_IRWXU)) fprintf(stderr, "Portable %s directory created at %s\n", name, portable_dir); else @@ -1511,7 +1514,7 @@ int main(int argc, char* argv[]) { if (arg && strcmp(arg, "appimage-help") == 0) { char fullpath[PATH_MAX]; - ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath)); + ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath) - 1); if (length < 0) { fprintf(stderr, "Error getting realpath for %s\n", appimage_path); exit(EXIT_EXECERROR); @@ -1561,7 +1564,7 @@ int main(int argc, char* argv[]) { if (getenv("TARGET_APPIMAGE") == NULL) { // If we are operating on this file itself - ssize_t len = readlink(appimage_path, fullpath, sizeof(fullpath)); + ssize_t len = readlink(appimage_path, fullpath, sizeof(fullpath) - 1); if (len < 0) { perror("Failed to obtain absolute path"); exit(EXIT_EXECERROR); From 0cdccba321dd08f27a6eebe4d428bd8e5626158f Mon Sep 17 00:00:00 2001 From: Gianluca Renzi Date: Wed, 14 Oct 2026 01:30:31 +0200 Subject: [PATCH 2/4] Add riscv64 and ppc64le Build the runtime for 64-bit RISC-V and for little endian 64-bit PowerPC as for the other architectures, in Alpine containers (Alpine has riscv64 and ppc64le images): create-build-container.sh maps the architectures to the Docker platforms and build-runtime.sh names the binaries runtime-riscv64 and runtime-ppc64le. GitHub has no RISC-V and POWER runners, so the CI sets up QEMU (setup-qemu-action) for these jobs and runs the containers emulated. --- .github/workflows/build.yaml | 11 +++++++++++ BUILD.md | 2 ++ scripts/build-runtime.sh | 4 ++++ scripts/docker/create-build-container.sh | 6 ++++++ 4 files changed, 23 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2a7c4c9..7787d3f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -34,6 +34,12 @@ jobs: runner: ubuntu-24.04-arm - appimage_arch: aarch64 runner: ubuntu-24.04-arm + - appimage_arch: riscv64 + runner: ubuntu-24.04 + qemu: true + - appimage_arch: ppc64le + runner: ubuntu-24.04 + qemu: true runs-on: ${{ matrix.runner }} @@ -41,6 +47,11 @@ jobs: - name: Checkout uses: actions/checkout@v7 + # GitHub has no RISC-V and POWER runners: the build container runs emulated + - name: Set up QEMU + if: ${{ matrix.qemu }} + uses: docker/setup-qemu-action@v4 + - name: Get git hash run: | echo -n "https://github.com/${GITHUB_REPOSITORY}/commit/" > src/runtime/version diff --git a/BUILD.md b/BUILD.md index fbee62b..27ae352 100644 --- a/BUILD.md +++ b/BUILD.md @@ -46,6 +46,8 @@ The build process has been automated completely. As a user, you just need to run The resulting AppImages will end up in your current working directory. +`` is one of `x86_64`, `i686`, `armhf`, `aarch64`, `riscv64` and `ppc64le`. There are no GitHub runners for the last two: the CI runs their Alpine containers emulated by QEMU (see [Emulate foreign architectures](#emulate-foreign-architectures)), which is much slower. + ### Interactive environment diff --git a/scripts/build-runtime.sh b/scripts/build-runtime.sh index 3ce3b91..9401ba3 100755 --- a/scripts/build-runtime.sh +++ b/scripts/build-runtime.sh @@ -42,6 +42,10 @@ elif [[ "$runtime" =~ EABI5 ]]; then architecture=armhf elif [[ "$runtime" =~ x86-64 ]]; then architecture=x86_64 +elif [[ "$runtime" =~ RISC-V ]]; then + architecture=riscv64 +elif [[ "$runtime" =~ "64-bit LSB" ]] && [[ "$runtime" =~ PowerPC ]]; then + architecture=ppc64le else echo "Unsupported architecture: ${runtime#* }" exit 2 diff --git a/scripts/docker/create-build-container.sh b/scripts/docker/create-build-container.sh index 8991688..2d75101 100755 --- a/scripts/docker/create-build-container.sh +++ b/scripts/docker/create-build-container.sh @@ -23,6 +23,12 @@ case "${ARCH}" in aarch64) docker_platform=linux/arm64/v8 ;; + riscv64) + docker_platform=linux/riscv64 + ;; + ppc64le) + docker_platform=linux/ppc64le + ;; *) echo "Unsupported architecture: $ARCH" exit 3 From f9c002a5999e921fe442dfa12b2d616fe925b00e Mon Sep 17 00:00:00 2001 From: Gianluca Renzi Date: Wed, 14 Oct 2026 01:30:43 +0200 Subject: [PATCH 3/4] Add ppc64 (big endian), built in Debian ports with glibc Alpine Linux has no big endian ppc64, so the runtime is built in a Debian ports (sid) system made with debootstrap and run by QEMU (the qemu-user-static of Ubuntu: the binfmt image of setup-qemu-action has no big endian ppc64): glibc instead of musl, gcc instead of clang, no static PIE. libfuse and squashfuse come from source as for the other architectures; zstd, zlib and, if its static library is there, mimalloc from Debian (scripts/debian-ports/build-in-debian.sh). meson installs libfuse into /usr/lib64, which pkg-config and gcc of Debian (multiarch) do not search, so the script adds it; udev.pc, which the build of libfuse reads, comes from systemd-dev, as the maintainer scripts of systemd crash under QEMU. The Makefile gets STATIC_PIE and MIMALLOC, which can be emptied on the command line, and build-runtime.sh passes RUNTIME_MAKE_ARGS to make and names the binary runtime-ppc64 (ELF 64-bit MSB PowerPC, ELFv1 ABI as Debian ppc64). --- .github/workflows/build.yaml | 54 +++++++++++++++++++++++++ BUILD.md | 12 ++++++ scripts/build-runtime.sh | 5 ++- scripts/debian-ports/build-in-debian.sh | 38 +++++++++++++++++ src/runtime/Makefile | 7 +++- 5 files changed, 113 insertions(+), 3 deletions(-) create mode 100755 scripts/debian-ports/build-in-debian.sh diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7787d3f..e1c265f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -113,11 +113,65 @@ jobs: run: | scripts/chroot/chroot_build.sh + # Architectures without Alpine Linux (big endian ppc64): built in Debian ports + # with glibc (scripts/debian-ports/build-in-debian.sh), emulated by QEMU + build-debian-ports: + strategy: + fail-fast: false + matrix: + include: + - appimage_arch: ppc64 + debian_arch: ppc64 + platform: linux/ppc64 + + runs-on: ubuntu-24.04 + timeout-minutes: 240 + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Get git hash + run: | + echo -n "https://github.com/${GITHUB_REPOSITORY}/commit/" > src/runtime/version + git rev-parse --short HEAD | xargs >> src/runtime/version + + # debian-ports has no Docker images: the root file system is made with + # debootstrap (its second stage runs emulated) and imported. The binfmt + # image of setup-qemu-action has no big endian ppc64: qemu-user-static of Ubuntu + - name: Make the Debian ports image + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends debootstrap qemu-user-static + # the keyring of Ubuntu is older than the key that signs sid today + KEYRING=debian-ports-archive-keyring_2026.02.04_all.deb + curl -fsSLO https://deb.debian.org/debian/pool/main/d/debian-ports-archive-keyring/$KEYRING + dpkg-deb -x $KEYRING keyring + # the keyring goes into the system too, for its apt-get update + sudo debootstrap --arch=${{ matrix.debian_arch }} --variant=minbase \ + --include=debian-ports-archive-keyring \ + --keyring=$PWD/keyring/usr/share/keyrings/debian-ports-archive-keyring.gpg \ + sid rootfs http://ftp.ports.debian.org/debian-ports + sudo tar -C rootfs -c . | docker import --platform ${{ matrix.platform }} - debian-ports + sudo rm -rf rootfs keyring $KEYRING + + - name: Build + run: | + docker run --rm --platform ${{ matrix.platform }} -v "$PWD":/src -w /src debian-ports \ + bash scripts/debian-ports/build-in-debian.sh + sudo chown -R "$(id -u):$(id -g)" out + + - uses: actions/upload-artifact@v7 + with: + name: artifacts ${{ matrix.appimage_arch }} + path: out/* + upload: name: Create release and upload artifacts runs-on: ubuntu-latest needs: - build + - build-debian-ports steps: - name: Download artifacts uses: actions/download-artifact@v8 diff --git a/BUILD.md b/BUILD.md index 27ae352..57448b2 100644 --- a/BUILD.md +++ b/BUILD.md @@ -49,6 +49,18 @@ The resulting AppImages will end up in your current working directory. `` is one of `x86_64`, `i686`, `armhf`, `aarch64`, `riscv64` and `ppc64le`. There are no GitHub runners for the last two: the CI runs their Alpine containers emulated by QEMU (see [Emulate foreign architectures](#emulate-foreign-architectures)), which is much slower. +### Architectures without Alpine Linux: big endian ppc64 + +Alpine Linux has no big endian 64-bit PowerPC, so `ppc64` is built in a Debian ports system (the `sid` release, the only one with this architecture), with glibc and gcc instead of musl and clang, and without static PIE (`make STATIC_PIE=`). libfuse and squashfuse are built from source as for the other architectures (`scripts/common/install-dependencies.sh`); zstd, zlib and, when Debian has its static library, mimalloc come from Debian: + +```sh +# in a Debian ports (sid) ppc64 system, as root, in the repository: +> bash scripts/debian-ports/build-in-debian.sh +``` + +The runtime ends up in `out/runtime-ppc64`. Debian ports has no Docker images: the CI makes the system with `debootstrap` and imports it into Docker (job `build-debian-ports` of `.github/workflows/build.yaml`), and runs it with the `qemu-user-static` of Ubuntu, as the binfmt image of setup-qemu-action has no big endian ppc64. + + ### Interactive environment You can spawn a development container locally using the following command: diff --git a/scripts/build-runtime.sh b/scripts/build-runtime.sh index 9401ba3..63ed9ad 100755 --- a/scripts/build-runtime.sh +++ b/scripts/build-runtime.sh @@ -17,7 +17,8 @@ cp -R src "$build_dir"/ pushd "$build_dir" pushd src/runtime/ -make -j"$(nproc)" runtime +# RUNTIME_MAKE_ARGS: extra make variables, e.g. CC=gcc STATIC_PIE= for glibc builds +make -j"$(nproc)" runtime ${RUNTIME_MAKE_ARGS:-} file runtime @@ -44,6 +45,8 @@ elif [[ "$runtime" =~ x86-64 ]]; then architecture=x86_64 elif [[ "$runtime" =~ RISC-V ]]; then architecture=riscv64 +elif [[ "$runtime" =~ "64-bit MSB" ]] && [[ "$runtime" =~ PowerPC ]]; then + architecture=ppc64 elif [[ "$runtime" =~ "64-bit LSB" ]] && [[ "$runtime" =~ PowerPC ]]; then architecture=ppc64le else diff --git a/scripts/debian-ports/build-in-debian.sh b/scripts/debian-ports/build-in-debian.sh new file mode 100755 index 0000000..14acd41 --- /dev/null +++ b/scripts/debian-ports/build-in-debian.sh @@ -0,0 +1,38 @@ +#! /bin/bash + +# Builds the runtime in a Debian (ports) system, for the architectures that +# have no Alpine Linux, e.g. big endian ppc64: glibc instead of musl, gcc +# instead of clang, and no static PIE (glibc has none on every architecture). +# libfuse and squashfuse come from source as for the other architectures +# (scripts/common/install-dependencies.sh), zstd and zlib from Debian. +# +# (systemd-dev: the udev.pc that the build of libfuse reads, without systemd itself, +# whose maintainer scripts crash under QEMU) +# +# Usage (as root, in the repository, e.g. in a container): +# bash scripts/debian-ports/build-in-debian.sh +# The runtime is written into out/. + +set -euxo pipefail + +export DEBIAN_FRONTEND=noninteractive +apt-get update +apt-get install -y --no-install-recommends \ + bash ca-certificates wget file patch xz-utils make gcc libc6-dev pkg-config \ + meson ninja-build autoconf automake libtool \ + libzstd-dev zlib1g-dev libmimalloc-dev systemd-dev + +# meson installs libfuse into /usr/lib64, which pkg-config and gcc of Debian +# (multiarch: /usr/lib/) do not search +export PKG_CONFIG_PATH=/usr/lib64/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH} +export LIBRARY_PATH=/usr/lib64${LIBRARY_PATH:+:$LIBRARY_PATH} + +bash scripts/common/install-dependencies.sh + +# mimalloc only when Debian has its static library +make_args="CC=gcc STATIC_PIE=" +if ! find /usr/lib -name libmimalloc.a | grep -q .; then + make_args="$make_args MIMALLOC=" +fi + +RUNTIME_MAKE_ARGS="$make_args" bash scripts/build-runtime.sh diff --git a/src/runtime/Makefile b/src/runtime/Makefile index 9fd4165..9323fe6 100644 --- a/src/runtime/Makefile +++ b/src/runtime/Makefile @@ -1,7 +1,10 @@ GIT_COMMIT := $(shell cat version) CC = clang -CFLAGS = -std=gnu99 -Os -D_FILE_OFFSET_BITS=64 -DGIT_COMMIT=\"$(GIT_COMMIT)\" -T data_sections.ld -ffunction-sections -fdata-sections -Wl,--gc-sections -static -Wall -Werror -static-pie -LIBS = -lsquashfuse -lsquashfuse_ll -lzstd -lz -lfuse3 -lmimalloc +# both can be emptied on the command line, e.g. for glibc targets without static PIE (make STATIC_PIE=) +STATIC_PIE = -static-pie +MIMALLOC = -lmimalloc +CFLAGS = -std=gnu99 -Os -D_FILE_OFFSET_BITS=64 -DGIT_COMMIT=\"$(GIT_COMMIT)\" -T data_sections.ld -ffunction-sections -fdata-sections -Wl,--gc-sections -static -Wall -Werror $(STATIC_PIE) +LIBS = -lsquashfuse -lsquashfuse_ll -lzstd -lz -lfuse3 $(MIMALLOC) all: runtime From 9ddbab1df277c47ed47783b45b2e810f425225da Mon Sep 17 00:00:00 2001 From: Gianluca Renzi Date: Wed, 14 Oct 2026 10:32:41 +0200 Subject: [PATCH 4/4] ppc64 job: keyring from the package index, signed runtime, release not gated Points of the review: - the keyring of Debian ports was fetched by a fixed file name from the pool, which keeps only the current version; its name now comes from the package index (dists/sid/main/binary-all/Packages.xz); - the ppc64 runtime is signed as the others (the Sign step of the build job, skipped without the secret); - the upload job no longer fails with build-debian-ports: when the emulated Debian ports build fails, the release goes out with the other architectures. --- .github/workflows/build.yaml | 25 ++++++++++++++++++++++--- BUILD.md | 1 + 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e1c265f..b996e9d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -143,9 +143,14 @@ jobs: run: | sudo apt-get update sudo apt-get install -y --no-install-recommends debootstrap qemu-user-static - # the keyring of Ubuntu is older than the key that signs sid today - KEYRING=debian-ports-archive-keyring_2026.02.04_all.deb - curl -fsSLO https://deb.debian.org/debian/pool/main/d/debian-ports-archive-keyring/$KEYRING + # the keyring of Ubuntu is older than the key that signs sid today. The pool of + # Debian keeps only the current version of the package: its file name comes + # from the package index, so that a new upload of the keyring breaks nothing + POOL=$(curl -fsSL https://deb.debian.org/debian/dists/sid/main/binary-all/Packages.xz | xz -d \ + | awk '/^Package: debian-ports-archive-keyring$/ { f = 1 } f && /^Filename:/ { print $2; f = 0 }') + test -n "$POOL" + KEYRING=$(basename "$POOL") + curl -fsSLO "https://deb.debian.org/debian/$POOL" dpkg-deb -x $KEYRING keyring # the keyring goes into the system too, for its apt-get update sudo debootstrap --arch=${{ matrix.debian_arch }} --variant=minbase \ @@ -161,6 +166,16 @@ jobs: bash scripts/debian-ports/build-in-debian.sh sudo chown -R "$(id -u):$(id -g)" out + - name: Sign + env: + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + # as in the build job: skipped without the secret (pull requests from forks) + if: ${{ env.SIGNING_KEY != '' }} + run: | + find out + scripts/sign.sh out/runtime-* + cp signing-pubkey.asc out/ + - uses: actions/upload-artifact@v7 with: name: artifacts ${{ matrix.appimage_arch }} @@ -172,6 +187,10 @@ jobs: needs: - build - build-debian-ports + # Debian ports is unstable by nature and its job runs emulated for hours: when + # it fails, the release still goes out with the other architectures (the + # artifacts of a failed job are not there to upload) + if: ${{ !cancelled() && needs.build.result == 'success' }} steps: - name: Download artifacts uses: actions/download-artifact@v8 diff --git a/BUILD.md b/BUILD.md index 57448b2..67b58ff 100644 --- a/BUILD.md +++ b/BUILD.md @@ -59,6 +59,7 @@ Alpine Linux has no big endian 64-bit PowerPC, so `ppc64` is built in a Debian p ``` The runtime ends up in `out/runtime-ppc64`. Debian ports has no Docker images: the CI makes the system with `debootstrap` and imports it into Docker (job `build-debian-ports` of `.github/workflows/build.yaml`), and runs it with the `qemu-user-static` of Ubuntu, as the binfmt image of setup-qemu-action has no big endian ppc64. +The keyring of Debian ports is the current one of the Debian package index (the pool keeps only the latest version), the runtime is signed as the others, and the release does not wait for this job: when the emulated Debian ports build fails, the other architectures are published without `runtime-ppc64`. ### Interactive environment