From 6603100c7e44be8247c130b517c704d6a13a2c55 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 9 Dec 2025 00:57:26 -0500 Subject: [PATCH] Add a CI for `increase_default_stack_size.sh` This runs whenever the script is modified, or weekly to ensure the CI doesn't inadvertently decay (due to using the latest packages for a variety of shells). This runs with `sh` (presumably `dash`), `ksh`, `bash`, `dash` (explicitly), `zsh`, `ash` (Busybox), `hush` (Busybox), `mksh`, `yash`, and `brush`. While none of these guarantee this script is POSIX-compliant, as a fully and explicitly-only POSIX-compliant environment is not constructed, this does reasonably test the script itself to be POSIX-compliant. The tools called have been reviewed for being used to the POSIX standard (although not audited to that degree). The script itself is modified with the following changes for compliance with POSIX: 1) `hexdump` is replaced with `od` (`od` suggested by @PlasmaPower) 2) `printf \xFF` replaced with octal escapes, as `\x` is not part of POSIX 3) `head -c` is replaced with `cut`, as the `-c` option is not standardized under POSIX (despite it being present for `tail`). This was identified by @PlasmaPower. As we used `head -c-2` to truncate the last two characters of a string, we now use `wc -c` for a `strlen` to enable the necessary arithmetic to calculate what two bytes in from the end of the string is. This entire effort can be argued pointless, as we could simply run `monerod` on Debian. This script is useful, the journey down the rabbithole of POSIX compliance fascinating, and the methodology applicable to other potential futures though (whether running binaries on Alpine or testing other `sh` scripts for their portability). As part of this effort overall, our CI was extended with `shellcheck` for all `sh` scripts in-tree, including all of our existing `sh` scripts. That there is an actual, direct benefit past this specific effort. --- .github/actions/build-dependencies/action.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/stack_size.yml | 93 +++++++++++++++++++ orchestration/increase_default_stack_size.sh | 21 +++-- 4 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/stack_size.yml diff --git a/.github/actions/build-dependencies/action.yml b/.github/actions/build-dependencies/action.yml index 695b7e4e..679167d5 100644 --- a/.github/actions/build-dependencies/action.yml +++ b/.github/actions/build-dependencies/action.yml @@ -62,7 +62,7 @@ runs: docker system prune -a --volumes sudo apt remove -y *docker* # Install uidmap which will be required for the explicitly installed Docker - sudo apt install uidmap + sudo apt install -y uidmap if: runner.os == 'Linux' - name: Update system dependencies diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 12ae78d6..6a4fdc33 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -208,7 +208,7 @@ jobs: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0 - name: shellcheck run: | - sudo apt install shellcheck + sudo apt install -y shellcheck find . -iname "*.sh" | while read -r script; do shellcheck --enable=all --shell=sh --severity=info $script done diff --git a/.github/workflows/stack_size.yml b/.github/workflows/stack_size.yml new file mode 100644 index 00000000..031d3f63 --- /dev/null +++ b/.github/workflows/stack_size.yml @@ -0,0 +1,93 @@ +name: Check Update Default Stack Size + +on: + push: + paths: + - "orchestration/increase_default_stack_size.sh" + pull_request: + paths: + - "orchestration/increase_default_stack_size.sh" + workflow_dispatch: + # Also run weekly to ensure this doesn't inadvertently decay + schedule: + - cron: "0 0 * * 1" + +jobs: + stack_size: + strategy: + matrix: + os: [ubuntu-latest, ubuntu-24.04, ubuntu-22.04] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0 + + - name: Download Monero + uses: ./.github/actions/monero + + - name: Verify expected behavior + shell: bash + run: | + cp /usr/bin/monerod monerod + + STACK=$((8 * 1024 * 1024)) + + cp monerod monerod-chelf + git clone https://github.com/Gottox/chelf + cd chelf + git checkout b2994186cea7b7d61a588fd06c1cc1ae75bcc21a + make + ./chelf -s $STACK ../monerod-chelf + cd .. + + cp monerod monerod-muslstack + GOBIN=$(pwd) go install github.com/yaegashi/muslstack@d19cc5866abce3ca59dfc1666df7cc97097d0933 + ./muslstack -s $STACK ./monerod-muslstack + + sudo apt update -y + sudo apt install -y ksh bash dash zsh busybox mksh yash + sudo ln -s $(which busybox) /usr/bin/ash + sudo ln -s $(which busybox) /usr/bin/hush + cargo install brush-shell + for shell in sh ksh bash dash zsh ash hush mksh yash brush; do + cp monerod monerod-idss-$shell + ln -s $(which $shell) sh + ./sh ./orchestration/increase_default_stack_size.sh monerod-idss-$shell + rm ./sh + done + + sha256() { + sha256sum "$1" | cut -d' ' -f1 + } + CHELF=$(sha256 monerod-chelf) + find . -iname "monerod-*" | while read -r bin; do + BIN=$(sha256 "$bin") + if [ ! "$CHELF" = "$BIN" ]; then + echo "Different artifact between monerod-chelf ($CHELF) and $bin ($BIN)" + exit 1 + fi + done + + read_stack() { + STACK_INFO=$(readelf "$1" -l | grep STACK -A1) + MEMSZ=$(echo "$STACK_INFO" | tail -n1 | sed -E s/^[[:space:]]*//g | cut -f2 -d' ') + printf "%i" $((MEMSZ)) + } + INITIAL_STACK=$(read_stack monerod) + if [ "$INITIAL_STACK" -ne "0" ]; then + echo "Initial \`PT_GNU_STACK\` wasn't 0" + exit 2 + fi + + UPDATED_STACK=$(read_stack monerod-chelf) + if [ "$UPDATED_STACK" -ne "$STACK" ]; then + echo "Updated \`PT_GNU_STACK\` ($UPDATED_STACK) wasn't 8 MB ($STACK)" + exit 3 + fi + + # Only one byte should be different due to the bit pattern of 8 MB + BYTES_DIFFERENT=$(cmp -l monerod monerod-chelf | wc -l || true) + if [ "$BYTES_DIFFERENT" -ne 1 ]; then + echo "More than one byte was different between the two binaries" + exit 4 + fi diff --git a/orchestration/increase_default_stack_size.sh b/orchestration/increase_default_stack_size.sh index 9cc2b2a7..b989808c 100755 --- a/orchestration/increase_default_stack_size.sh +++ b/orchestration/increase_default_stack_size.sh @@ -26,23 +26,28 @@ hex() { read_bytes() { dd bs=1 skip="$1" count="$2" if="$ELF" 2> /dev/null | hex } +hex_to_octal() { + printf "ibase=16; obase=8; %s\n" "$1" | bc +} write_bytes() { POS=$1 BYTES=$2 while [ ! "$BYTES" = "" ]; do - NEXT=$(printf "%s" "$BYTES" | head -c2) + NEXT=$(printf "%s" "$BYTES" | cut -c-2) # Advance to the third byte, as in, after the first two bytes BYTES=$(printf "%s" "$BYTES" | tail -c+3) + NEXT=$(hex_to_octal "$NEXT") # shellcheck disable=SC2059 - printf "\x$NEXT" | dd conv=notrunc bs=1 seek="$POS" of="$ELF" 2> /dev/null + printf \\"$NEXT" | dd conv=notrunc bs=1 seek="$POS" of="$ELF" 2> /dev/null POS=$((POS + 1)) done } # Magic MAGIC=$(read_bytes 0 4) -EXPECTED_MAGIC=$(printf "\x7ELF" | hex) +# shellcheck disable=SC2059 +EXPECTED_MAGIC=$(printf \\"$(hex_to_octal 7F)"ELF | hex) if [ ! "$MAGIC" = "$EXPECTED_MAGIC" ]; then echo "Not ELF" exit 2 @@ -101,9 +106,13 @@ swap_native_endian() { return fi - while [ ! "$BYTES" = "" ]; do + while :; do printf "%s" "$BYTES" | tail -c2 - BYTES=$(printf "%s" "$BYTES" | head -c-2) + NEW_LENGTH=$(( $(printf "%s" "$BYTES" | wc -c) - 2 )) + if [ "$NEW_LENGTH" -eq 0 ]; then + break + fi + BYTES=$(printf "%s" "$BYTES" | cut -c-$NEW_LENGTH) done } @@ -144,7 +153,7 @@ while [ "$NEXT_PROGRAM_HEADER" -ne -1 ]; do NEXT_PROGRAM_HEADER=$(( NEXT_PROGRAM_HEADER - 1 )) PROGRAM_HEADER=$(read_program_header "$THIS_PROGRAM_HEADER") - HEADER_TYPE=$(printf "%s" "$PROGRAM_HEADER" | head -c8) + HEADER_TYPE=$(printf "%s" "$PROGRAM_HEADER" | cut -c-8) HEADER_TYPE=$(swap_native_endian "$HEADER_TYPE") # `PT_GNU_STACK` # https://github.com/torvalds/linux/blob/c2f2b01b74be8b40a2173372bcd770723f87e7b2/include/uapi/linux/elf.h#L41