From f25ccf07c60688697fea87d320c27b33f875cd1b Mon Sep 17 00:00:00 2001 From: vasilito Date: Tue, 4 Aug 2026 20:58:50 +0300 Subject: [PATCH] icu: assemble the data blob with --noexecstack; gate continuation comments ICU emits its data as generated assembly (icudt75l_dat.S). Without -Wa,--noexecstack the assembler produces an object with no .note.GNU-stack section, and modern binutils warns: ld: warning: icudt75l_dat.o: missing .note.GNU-stack section implies executable stack Harmless alone -- except KDE's ECM links with -Wl,--fatal-warnings, so every KDE consumer of static ICU fails outright (first hit: plasma-workspace applets/digital-clock, collect2: error: ld returned 1). Fixed at the source rather than suppressed downstream, which would have to be repeated per consumer. Also gates the '#'-inside-a-backslash-continuation trap, which terminates the continuation and silently drops every remaining argument. It has now bitten three times, most recently while writing THIS commit: the noexecstack rationale was first placed between two continued configure flags, which would have dropped the rest of ICU's configure line. Moved above the invocation. The check scans the RAW file, not the parsed TOML. In a multi-line basic string a trailing backslash is itself a TOML line-continuation escape, so the newline is gone before the value is handed over and the parsed string has no trailing backslashes at all. The first version scanned the parsed value and silently found nothing -- caught by a self-test, not by review, which is the same class of invisible failure the gate exists to prevent. --- local/recipes/libs/icu/recipe.toml | 19 ++++++++++++-- local/scripts/check-recipe-escapes.py | 36 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/local/recipes/libs/icu/recipe.toml b/local/recipes/libs/icu/recipe.toml index bcedc60367..e873b98c52 100644 --- a/local/recipes/libs/icu/recipe.toml +++ b/local/recipes/libs/icu/recipe.toml @@ -44,6 +44,21 @@ LDFLAGS="${TARGET_LDFLAGS}" ln -sf mh-linux "${ICU_SRC}/config/mh-unknown" +# -Wa,--noexecstack is REQUIRED, not hygiene. +# +# ICU emits its data blob as generated assembly (icudt75l_dat.S) and the +# assembler, absent this flag, produces an object with no .note.GNU-stack +# section. Modern binutils then warns: +# ld: warning: icudt75l_dat.o: missing .note.GNU-stack section implies +# executable stack +# which is harmless on its own -- except KDE's ECM links with +# -Wl,--fatal-warnings, so every KDE consumer of static ICU fails outright: +# collect2: error: ld returned 1 exit status +# (first hit: plasma-workspace applets/digital-clock). +# +# Fixed at the source rather than by suppressing the warning downstream: the +# object genuinely should declare a non-executable stack, and silencing it per +# consumer would have to be repeated for every KDE package that links ICU. "${ICU_SRC}/configure" \ --host="${GNU_TARGET}" \ --prefix=/usr \ @@ -56,8 +71,8 @@ ln -sf mh-linux "${ICU_SRC}/config/mh-unknown" --enable-static \ --disable-shared \ --with-data-packaging=static \ - CFLAGS="-DU_HAVE_CHAR16_T=0 -Wno-deprecated-declarations -fPIC" \ - CXXFLAGS="-DU_HAVE_CHAR16_T=0 -Wno-deprecated-declarations -fPIC" \ + CFLAGS="-DU_HAVE_CHAR16_T=0 -Wno-deprecated-declarations -fPIC -Wa,--noexecstack" \ + CXXFLAGS="-DU_HAVE_CHAR16_T=0 -Wno-deprecated-declarations -fPIC -Wa,--noexecstack" \ icu_cv_host_frag=mh-linux make -j"${COOKBOOK_MAKE_JOBS}" diff --git a/local/scripts/check-recipe-escapes.py b/local/scripts/check-recipe-escapes.py index 4f5c9570c4..2cdff23ec2 100755 --- a/local/scripts/check-recipe-escapes.py +++ b/local/scripts/check-recipe-escapes.py @@ -70,6 +70,35 @@ def walk(node, path, findings): findings.append((path, lineno, advice, shown[:100])) +def find_continuation_comments(raw_text): + r"""Comment lines inside a backslash-continued shell command. + + A '#' line terminates a `\`-continuation, so EVERY remaining argument is + silently dropped -- no shell error, no build error, just a command that + quietly ran with fewer flags than it appears to. In kwin this dropped + KWIN_BUILD_SCREENLOCKER, TABBOX, GLOBALSHORTCUTS and RUNNERS, so cmake took + its own defaults and configure later aborted on a REQUIRED package the + recipe had explicitly disabled. + + Scans the RAW FILE, not the parsed TOML value. In a TOML multi-line basic + string a trailing backslash is itself a line-continuation escape: TOML + removes the newline before the value is ever handed over, so the parsed + string contains no trailing backslashes and this can only be detected in + the source text. (My first version scanned the parsed value and silently + found nothing -- caught by a self-test, not by review.) + """ + findings = [] + prev_cont = False + for lineno, line in enumerate(raw_text.splitlines(), 1): + stripped = line.strip() + if prev_cont and stripped.startswith("#"): + findings.append((lineno, stripped[:88])) + # A line ending in an ODD number of backslashes continues. + trailing = len(line.rstrip()) - len(line.rstrip().rstrip("\\")) + prev_cont = (trailing % 2) == 1 + return findings + + def main() -> int: root = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".") findings_total = 0 @@ -90,6 +119,13 @@ def main() -> int: findings = [] walk(data, "", findings) + + for lineno, shown in find_continuation_comments(recipe.read_text(errors="replace")): + findings_total += 1 + print(f"{recipe}:{lineno}") + print(" comment inside a backslash-continued command -- every") + print(" remaining argument on that command is silently dropped") + print(f" {shown}") for path, lineno, advice, shown in findings: findings_total += 1 print(f"{recipe}{path} (line {lineno} of the string)")