From 5a4d6937a4d8e8dd5a837647bb4dcb3052c914cd Mon Sep 17 00:00:00 2001 From: Chris Kearney <303316+chriskearney@users.noreply.github.com> Date: Sun, 25 Oct 2026 04:05:04 -0700 Subject: [PATCH] mesa-m3: refuse unresolved Bash append overrides Bash accepts NAME+=VALUE in POSIX mode, but the scanner only recognizes NAME=VALUE. A late uwsm export such as LIBGL_ALWAYS_SOFTWARE+=1 therefore passes the system-provider check before selecting software rendering. Report tracked append assignments as unknown in system-provider mode, keeping the software recovery path. Leave the default legacy scanner unchanged. Exercise Bash's actual result and the complete handoff under both shells, and bump the package release with its updated checksum. Generated-by: LLM Signed-off-by: Chris Kearney <303316+chriskearney@users.noreply.github.com> --- tools/aurora-sep/recipes/mesa-m3/PKGBUILD | 4 +-- .../recipes/mesa-m3/mesa-m3-user-setup | 18 ++++++++--- .../recipes/mesa-m3/tests/test-session-env.sh | 3 +- .../mesa-m3/tests/test-system-provider.py | 32 +++++++++++++++++++ 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/tools/aurora-sep/recipes/mesa-m3/PKGBUILD b/tools/aurora-sep/recipes/mesa-m3/PKGBUILD index bfae006a487842..2c80497680e0af 100644 --- a/tools/aurora-sep/recipes/mesa-m3/PKGBUILD +++ b/tools/aurora-sep/recipes/mesa-m3/PKGBUILD @@ -10,7 +10,7 @@ # No profile selection substitutes another Mac's firmware or calibration. pkgname=mesa-m3 pkgver=26.1.4.m3.2 -pkgrel=7 +pkgrel=8 _commit=7ff89a4da387bd561b78efd5f09c98ca8ae18538 pkgdesc="Mesa in /opt/mesa-m3 for the M3 Pro (G15S) and M3 (G15G) GPUs; used by uwsm sessions on T6030, and on T8122 when opted in" arch=('aarch64') @@ -91,7 +91,7 @@ sha256sums=('7ed24969985dce91f089d34dbe1c55cbcb8c95765e3e5bc0f268d27fbead2222' '644cf4a5ab57b7badec75843e2899b68d41490c4851d6885227180d6090c25ea' '9a26a232fd050b6207bd0d385035723f72ffd2b6b72a25d6ac92ae2f99ea5b66' '34f8bb3fa505718dab176b6283dde0d4de9cae4c3928922bca18e6052434bca7' - '093a9029dbc6cb43e1c2997baa9c591f29df86d6b242f7f36dbf32add29ba103' + '04fae9bf32a09a574b6ed9ab9d7afb97340df252b9269ed789303e8561a61a21' '96c3101c15c73d26cd3086bc8ac4e8eb35d42300d495ca0ffd2bd03960ef3332' '9c7c3050d3ec15fce80a6e59d099567b396f1844a43695aa4377e02ac696f566' '2a15cfdbc160b78e5a3a8e99d5898d40a54db28592f1a4ce04a600990a731f87' diff --git a/tools/aurora-sep/recipes/mesa-m3/mesa-m3-user-setup b/tools/aurora-sep/recipes/mesa-m3/mesa-m3-user-setup index f06c5d9f6312d9..7291099ff7cb18 100755 --- a/tools/aurora-sep/recipes/mesa-m3/mesa-m3-user-setup +++ b/tools/aurora-sep/recipes/mesa-m3/mesa-m3-user-setup @@ -26,7 +26,8 @@ see the comments there. an LD_LIBRARY_PATH entry then also counts when it holds only libEGL, libGLESv2, libGL, libGLX, libglapi or libgbm; entries split on ":" and ";", and a relative or empty - entry makes the result "unknown" + entry makes the result "unknown"; Bash += assignments + to tracked variables are unknown too Nothing is sourced or evaluated. Files are read as text and split into words the way a POSIX shell would (quotes, backslashes, comments, ";"), with no @@ -189,9 +190,11 @@ def shell_words(line): return words -def shell_assignments(line): +def shell_assignments(line, *, append_unknown=False): """The NAME=VALUE words of one line of shell, environment.d or - /etc/environment text. None when the line cannot be split.""" + /etc/environment text. None when the line cannot be split. With + append_unknown, a Bash NAME+=VALUE word is emitted with a None value, + since its resulting value depends on earlier assignments.""" words = shell_words(line) if words is None: return None @@ -200,6 +203,13 @@ def shell_assignments(line): m = re.match(r'([A-Za-z_][A-Za-z0-9_]*)=(.*)$', w, re.S) if m: out.append((m.group(1), m.group(2))) + elif append_unknown: + # Arch's sh is Bash, which accepts += even in POSIX mode. Its + # value depends on earlier assignments; do not silently ignore + # a late provider override when admitting system Mesa. + m = re.match(r'([A-Za-z_][A-Za-z0-9_]*)\+=(.*)$', w, re.S) + if m: + out.append((m.group(1), None)) return out @@ -523,7 +533,7 @@ class Finder: elif is_hypr: found = hyprlang_assignments(line) else: - found = shell_assignments(line) + found = shell_assignments(line, append_unknown=self.o['system_provider']) if found is None: if any(re.search(rf'(? "$HR$T/home/.config/uwsm/env" diff --git a/tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py b/tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py index f66a74c4dfffad..e528270b7c9444 100644 --- a/tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py +++ b/tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py @@ -29,6 +29,38 @@ def run_detector(self, environment=None, system=True): **(environment or {})}, capture_output=True, text=True, timeout=10) + def test_bash_append_cannot_override_system_admission_silently(self): + folder = self.root / "home/tester/.config/uwsm" + folder.mkdir() + config = folder / "env" + cases = (("LIBGL_ALWAYS_SOFTWARE", "1"), + ("LD_LIBRARY_PATH", "/opt/custom/lib")) + for name, value in cases: + with self.subTest(variable=name): + config.write_text("export " + name + "+=" + value + "\n") + # The target's sh is Bash. This is a real assignment in + # POSIX mode, although the legacy parser ignores it. + shell = subprocess.run( + ["bash", "--posix", "-c", + 'unset LIBGL_ALWAYS_SOFTWARE LD_LIBRARY_PATH; . "$1"; ' + 'printf "%s" "${!2}"', "bash", str(config), name], + env={"PATH": "/usr/bin:/bin"}, capture_output=True, + text=True, timeout=10) + self.assertEqual(shell.returncode, 0, shell.stderr) + self.assertEqual(shell.stdout, value) + result = self.run_detector() + self.assertEqual(result.returncode, 3, result.stdout) + self.assertIn(name, result.stdout) + self.assertIn("user_setup=unknown", result.stdout) + # Keep the default legacy decision unchanged. + self.assertEqual(self.run_detector(system=False).returncode, 0) + + def test_unrelated_bash_append_is_not_a_mesa_choice(self): + folder = self.root / "home/tester/.config/uwsm" + folder.mkdir() + (folder / "env").write_text("export MY_TOOLS_PATH+=/tools\n") + self.assertEqual(self.run_detector().returncode, 0) + def test_clean_setup(self): self.assertEqual(self.run_detector().returncode, 0)