Skip to content

eu-strip (elfutils) corrupts binaries after in-place note appending #76

Description

@sigmaSd

sui's in-place ELF note appending (introduced in 9c63027) adds a .note.sui SHT_NOTE section header with SHF_ALLOC. This section lands at a file offset / vaddr range that's discontiguous from the original allocated sections. elfutils eu-strip (used by flatpak's SDK and many Linux distributions) cannot reconstruct valid PT_LOAD entries from this layout and writes all-NULL program headers, producing a binary that segfaults on exec.

Reproduction

cat > hello.c << 'EOF'
#include <stdio.h>
int main(void) { printf("hello\n"); return 0; }
EOF
gcc -o hello hello.c
echo -n "payload" > /tmp/data
git clone --depth=1 https://github.com/littledivy/sui.git
env -u CARGO_TARGET_DIR cargo build --manifest-path sui/Cargo.toml -q
./sui/target/debug/sui testdata hello /tmp/data hello.sui
chmod +x hello.sui
cp hello.sui hello.stripped
eu-strip -s hello.stripped
./hello.stripped      # SEGFAULT (exit 139)

Full POC script:

#!/usr/bin/env bash
# POC: eu-strip corrupts ELF binaries modified by sui
# Requirements: gcc, cargo, eu-strip, git
# Reproduces the deno compile segfault on flatpak (exit 139)

set -euo pipefail
DIR=$(mktemp -d)
cd "$DIR"

echo "=== 1. Build a test binary ==="
cat > hello.c << 'EOF'
#include <stdio.h>
int main(void) { printf("hello\n"); return 0; }
EOF
gcc -o hello hello.c
echo "Before: $(file hello)"
./hello && echo "exit 0"

echo ""
echo "=== 2. Build sui ==="
git clone --depth=1 https://github.com/littledivy/sui.git 2>/dev/null
env -u CARGO_TARGET_DIR cargo build --manifest-path sui/Cargo.toml -q 2>&1
SUI=./sui/target/debug/sui

echo ""
echo "=== 3. Modify with sui ==="
echo -n "test payload" > /tmp/sui-data
$SUI testdata hello /tmp/sui-data hello.sui
chmod +x hello.sui
echo "After sui: $(file hello.sui)"
./hello.sui && echo "exit 0"

echo ""
echo "=== 4. Strip with eu-strip (elfutils) ==="
cp hello.sui hello.stripped
eu-strip -s hello.stripped 2>&1
echo "After eu-strip: $(file hello.stripped)"
r=0
./hello.stripped || r=$?
echo "exit $r"

echo ""
if [ "$r" -eq 139 ]; then
    echo "SEGFAULT! eu-strip zeroed all program headers."
    readelf -l hello.stripped 2>/dev/null | head -12
    echo ""
    echo "For comparison, binutils strip works fine:"
    cp hello.sui hello.ok
    strip -s hello.ok
    ./hello.ok && echo "binutils strip: exit 0"
fi

rm -rf "$DIR" /tmp/sui-data

Expected

eu-strip produces a working stripped binary (same as strip -s from binutils, which handles it correctly).

Actual

eu-strip zeros out all program headers — all 15 entries become PT_NULL:

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  NULL           0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000     0x0
  NULL           0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000     0x0
  NULL           0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000     0x0
  ...

The binary is reported as "statically linked" (because PT_DYNAMIC is gone) and segfaults immediately.

Root cause

The .note.sui section is placed via new PT_LOAD / PT_NOTE entries at the end of the file (file offset 0x4348, vaddr 0x404348), while all original allocated sections live in the 0x0000–0x2000 file range (vaddr 0x400000–0x403000). eu-strip tries to reconstruct PT_LOAD segments from the section headers and cannot satisfy the ELF congruence (p_offset % align == p_vaddr % align) across this discontiguity. It falls back to writing null entries.

binutils strip avoids this because it preserves the original program headers verbatim rather than reconstructing them from sections.

Fix suggestion

Remove the SHT_NOTE section header addition for .note.sui. The PT_NOTE program header alone is sufficient for runtime discovery via dl_iterate_phdr. A fully stripped binary already takes this path (see comment at line 1332), and eu-strip preserves PT_NOTE entries even without matching sections.

Alternatively, place the relocated .shstrtab and section header table inside the new PT_LOAD so the section-to-segment coverage is contiguous — but this is more invasive.

Environment

  • eu-strip (elfutils) 0.193 / 0.195
  • strip (binutils) 2.46 — unaffected
  • Tested on Fedora 44, also inside org.gnome.Platform//50 flatpak SDK

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions