Fix SIGSEGV in open wrapper on multibyte arguments

Merged
AE
1mo ago
+500-2

Problem

Inside a cmux terminal (CMUX_SOCKET_PATH set), open runs cmux's wrapper script
(Resources/bin/open) instead of /usr/bin/open. Passing an argument containing
multibyte characters — e.g. a Japanese filename like 日本語.pdf — crashes the
bash process executing the wrapper itself with SIGSEGV, rather than the script
simply failing.

Repro conditions (confirmed on real hardware)

  • macOS 15.1 (24B83)
  • GNU bash, version 5.3.9(1)-release (aarch64-apple-darwin24.6.0) from MacPorts
    (/opt/local/bin/bash; the script is #!/usr/bin/env bash, so PATH resolution
    picks this build over Apple's /bin/bash 3.2)
  • Locale: LANG=ja_JP.UTF-8, LC_CTYPE=ja_JP.UTF-8, LC_ALL unset
open 日本語.pdf

reproducibly crashes with Segmentation fault: 11 (exit 139), 10/10 runs.

Crash report excerpt

~/Library/Logs/DiagnosticReports/bash-*.ips:

exception: EXC_BAD_ACCESS (SIGSEGV), KERN_INVALID_ADDRESS at 0x0
termination: SIGNAL / Segmentation fault: 11

remove_pattern
parameter_brace_remove_pattern   (nested — called again below)
getpattern
expand_string_for_pat
parameter_brace_remove_pattern
parameter_brace_expand
param_expand
expand_word_internal
...

This nested parameter_brace_remove_pattern call (pattern-removal expanding its
own pattern operand) matches trim()'s implementation:

trim() {
    local value="$1"
    value="${value#"${value%%[![:space:]]*}"}"
    value="${value%"${value##*[![:space:]]}"}"
    printf '%s' "$value"
}

to_lower_ascii() already guards against a related issue by forcing
LC_ALL=C before doing ASCII-only work:

to_lower_ascii() {
    # Bash 3.2-compatible lowercase conversion.
    LC_ALL=C printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
}

trim() had no such guard, and is called from several places
(normalize_boolean, is_html_extension, normalize_host,
normalize_whitelist_pattern, ...) with arguments that can contain arbitrary
multibyte content (file paths, URLs).

Root cause — broader than trim()

Isolating this on the affected bash build showed the bug is not confined to
trim()
's bracket-class pattern removal. Under the same locale, this bash build's
multibyte-aware glob/pattern matcher (remove_pattern for ${var%pattern} /
${var#pattern}, and xstrmatch for case statements) crashes on any attempted
match against a non-ASCII string — including:

  • The wrapper's very first case "$arg" in -a|-b|...|-*) ... *) ... esac arg-scanning
    loop, independent of trim(), even with plain literal (non-wildcard) alternation
    patterns.
  • normalize_host() / normalize_whitelist_pattern()'s ${value#*://}-style glob
    prefix stripping, when given a raw (non-punycode) Unicode hostname.

Bash's POSIX ERE engine ([[ $x =~ pattern ]], used by has_uri_scheme) does not
crash — this is specific to the glob/pattern-matching code path.

Fix

Rather than scattering local LC_ALL=C through every affected function, force
C-locale (byte-wise) matching once for the whole script, right before it starts
inspecting arguments:

export LC_ALL=C

Every pattern in this script only ever matches ASCII structure (URL schemes, flags,
extensions, whitespace, :// separators), so this never changes what any pattern
matches — it only avoids invoking the buggy multibyte matcher. This is the same
reasoning to_lower_ascii() already applies locally, generalized to the functions
that need it (trim, the arg-scanning case, normalize_host,
normalize_whitelist_pattern) without touching unrelated code.

Testing

  • Added a regression test to tests/test_open_wrapper.py that passes a Japanese
    filename (日本語.pdf) through the wrapper under ja_JP.UTF-8.
    • Runs against /bin/bash as a baseline (does not reproduce there — Apple's
      bundled bash 3.2 doesn't hit this bug).
    • Runs against any alternate bash build found on the machine (e.g.
      /opt/local/bin/bash); on this development machine (MacPorts bash 5.3.9,
      matching the reported build) it reproduces the crash before the fix and passes
      after. If no such build is present, the test documents this and does not fail —
      the crash is bash-build/version-dependent, so CI machines without an affected
      bash won't exercise this path.
  • Verified locally: before the fix, open 日本語.pdf crashes 10/10 runs
    (Segmentation fault: 11) on /opt/local/bin/bash 5.3.9; after the fix, 10/10
    runs succeed and correctly pass the filename through to system open.
  • Existing tests/test_open_wrapper.py suite (whitelist matching, HTML routing,
    IDN/punycode host matching, etc.) still passes unchanged.

Two commits: the first adds the regression test only (red on an affected bash
build), the second adds the fix (green).

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes a SIGSEGV crash in cmux's open wrapper when passed multibyte arguments (e.g. open 日本語.pdf) under a UTF-8 locale on some bash builds (observed with MacPorts bash 5.3.9 on macOS 15).

  • Forces export LC_ALL=C before argument processing to sidestep the buggy multibyte glob matcher; all patterns match ASCII structure, so matches are unchanged.
  • Captures the caller's original LC_ALL (distinguishing unset from empty) before forcing C, and restores it (or unsets it) before dispatching to system open and the cmux CLI, so the forced locale doesn't leak into child processes.
  • Adds regression tests passing a Japanese filename through the wrapper under ja_JP.UTF-8, run against /bin/bash and any alternate bash build found; skipped when none exists.
  • Adds a static guard asserting export LC_ALL=C precedes any top-level case or ${var%pattern}/${var#pattern} construct, covering named and positional parameters, ;/&&/||-chained statements, and comments.
  • Verifies via fake child processes that system open and the cmux CLI observe the caller's original locale.

Summary by CodeRabbit

  • Bug Fixes

    • Improved consistent matching of URLs, flags, file extensions, and paths by using a standard locale during argument processing.
    • Prevented crashes when opening filenames containing multibyte characters under supported Bash environments.
    • Improved behavior across different locale settings.
  • Tests

    • Added regression coverage for non-ASCII filenames with default and alternate Bash installations.
    • Expanded test configuration for custom shell binaries and environment overrides.
    • Added checks verifying locale handling before argument processing.