|
| 1 | +#!/usr/bin/env bash |
| 2 | +# requires: |
| 3 | +# mcpp#291 — a plain binary must NOT be handed the private glibc payload on |
| 4 | +# LD_LIBRARY_PATH. |
| 5 | +# |
| 6 | +# That variable is inherited by the entire process subtree. When the target is |
| 7 | +# something like a course provider that shells out (popen("mcpp test ...")), |
| 8 | +# /bin/sh is a HOST binary: its PT_INTERP is baked in, so it loads the HOST |
| 9 | +# ld.so while this variable hands it the PAYLOAD libc.so.6. glibc's libc and |
| 10 | +# ld.so are version-locked to each other via GLIBC_PRIVATE, so on any host |
| 11 | +# whose glibc differs from the payload's the shell dies of SIGSEGV inside the |
| 12 | +# dynamic linker — before main, with empty stdout and no diagnostic. |
| 13 | +# |
| 14 | +# The payload dir belongs on LD_LIBRARY_PATH only when the build actually has |
| 15 | +# a dlopen()-reachable dependency library (whose own DT_NEEDED closure cannot |
| 16 | +# see the executable's RUNPATH). A project with no such dependency must get |
| 17 | +# nothing. |
| 18 | +# |
| 19 | +# ASSERTS ON THE EMITTED ENVIRONMENT, not on whether a shell crashes: the crash |
| 20 | +# needs host glibc != payload glibc. On a matching host (the common CI case) |
| 21 | +# a crash-based test passes for the wrong reason and would never have caught |
| 22 | +# this in the first place. |
| 23 | +set -e |
| 24 | + |
| 25 | +TMP=$(mktemp -d) |
| 26 | +trap "rm -rf $TMP" EXIT |
| 27 | + |
| 28 | +cd "$TMP" |
| 29 | +mkdir -p pkg/src |
| 30 | +cd pkg |
| 31 | +cat > mcpp.toml <<'EOF' |
| 32 | +[package] |
| 33 | +name = "envprobe" |
| 34 | +version = "0.1.0" |
| 35 | +standard = "c++23" |
| 36 | +EOF |
| 37 | + |
| 38 | +# Print the loader path variable exactly as the run target receives it. |
| 39 | +cat > src/main.cpp <<'EOF' |
| 40 | +#include <cstdio> |
| 41 | +#include <cstdlib> |
| 42 | +int main() { |
| 43 | + const char* p = std::getenv("LD_LIBRARY_PATH"); |
| 44 | + std::printf("LDLP=[%s]\n", p ? p : ""); |
| 45 | + return 0; |
| 46 | +} |
| 47 | +EOF |
| 48 | + |
| 49 | +out=$("$MCPP" run 2>&1) || { echo "FAIL: mcpp run failed"; echo "$out"; exit 1; } |
| 50 | + |
| 51 | +line=$(printf '%s\n' "$out" | grep -oE 'LDLP=\[[^]]*\]' | head -1) |
| 52 | +[ -n "$line" ] || { echo "FAIL: probe never printed LDLP"; echo "$out"; exit 1; } |
| 53 | +echo "observed: $line" |
| 54 | + |
| 55 | +# The specific poison: the private glibc payload store directory. |
| 56 | +if printf '%s' "$line" | grep -q 'xim-x-glibc'; then |
| 57 | + echo "FAIL: the run target was handed the private glibc payload on LD_LIBRARY_PATH." |
| 58 | + echo " $line" |
| 59 | + echo " A binary with no dlopen-reachable dependency must not get it —" |
| 60 | + echo " it propagates to every descendant process, including host shells." |
| 61 | + exit 1 |
| 62 | +fi |
| 63 | + |
| 64 | +# ── The other half: when a dlopen-reachable dependency library DOES exist, |
| 65 | +# the payload dir must still be there. Without this, a later change could drop |
| 66 | +# the entry entirely and the negative assertion above would happily pass. |
| 67 | +GLIBC_STORE=$(ls -d "$HOME"/.mcpp/registry/data/xpkgs/xim-x-glibc/*/ 2>/dev/null | head -1) |
| 68 | +if [ -z "$GLIBC_STORE" ]; then |
| 69 | + echo "SKIP (positive half): no private glibc payload installed" |
| 70 | + echo OK |
| 71 | + exit 0 |
| 72 | +fi |
| 73 | + |
| 74 | +cd "$TMP" |
| 75 | +mkdir -p pkg2/src pkg2/runtime |
| 76 | +cd pkg2 |
| 77 | +cat > mcpp.toml <<'EOF' |
| 78 | +[package] |
| 79 | +name = "envprobe2" |
| 80 | +version = "0.1.0" |
| 81 | +standard = "c++23" |
| 82 | +
|
| 83 | +[runtime] |
| 84 | +library_dirs = ["runtime"] |
| 85 | +EOF |
| 86 | +cp ../pkg/src/main.cpp src/main.cpp |
| 87 | + |
| 88 | +out2=$("$MCPP" run 2>&1) || { echo "FAIL: mcpp run failed (positive half)"; echo "$out2"; exit 1; } |
| 89 | +line2=$(printf '%s\n' "$out2" | grep -oE 'LDLP=\[[^]]*\]' | head -1) |
| 90 | +echo "observed (with [runtime] library_dirs): $line2" |
| 91 | + |
| 92 | +printf '%s' "$line2" | grep -q 'xim-x-glibc' || { |
| 93 | + echo "FAIL: a build WITH a dlopen-reachable dependency library dir lost the" |
| 94 | + echo " private glibc payload from LD_LIBRARY_PATH. dlopen'd libraries do" |
| 95 | + echo " not consult the executable's RUNPATH, so they need it here." |
| 96 | + echo " $line2" |
| 97 | + exit 1; } |
| 98 | + |
| 99 | +echo OK |
0 commit comments