Skip to content

fix(update): sanitise env before spawning installer#899

Open
edenfunf wants to merge 1 commit intomicrosoft:mainfrom
edenfunf:fix/update-subprocess-env-894
Open

fix(update): sanitise env before spawning installer#899
edenfunf wants to merge 1 commit intomicrosoft:mainfrom
edenfunf:fix/update-subprocess-env-894

Conversation

@edenfunf
Copy link
Copy Markdown
Contributor

Description

apm update inherits the PyInstaller bootloader's LD_LIBRARY_PATH when spawning the platform installer. The shell -- and the curl / tar / sudo calls install.sh makes -- then dlopens libssl.so.3 / libcrypto.so.3 from the bundle's _internal/ directory instead of the system ones. When the bundled libs are ABI-incompatible with what the system libcurl needs, curl aborts with OPENSSL_3.2.0 not found on the very first release fetch, blocking the upgrade path for every user on an affected distro (Debian trixie arm64 dev-containers, Fedora 43, and similar).

Fixes #894

Fix

New helper apm_cli.utils.subprocess_env.external_process_env centralises PyInstaller env sanitisation:

  • Restores LD_LIBRARY_PATH / DYLD_LIBRARY_PATH / DYLD_FRAMEWORK_PATH from the <NAME>_ORIG snapshots that PyInstaller's bootloader saves at launch.
  • Drops the variable entirely when no _ORIG snapshot exists (no pre-launch value to restore).
  • Strips the _ORIG keys from the returned env so PyInstaller internals do not leak to the child.
  • No-op outside a frozen build and on Windows.

apm update now calls subprocess.run with env=external_process_env() so the installer runs against the user's pre-launch environment. Restoring from _ORIG rather than blindly popping preserves legitimate user exports (CUDA, Nix, custom toolchains).

Complements #466's build-side exclude: that fix stopped new binaries from shipping the offending libs; this fix stops them from being inherited by spawned children even when they are present in older binaries or in any future bundle that re-introduces a similar dependency.

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

Unit coverage

  • tests/unit/test_subprocess_env.py -- 11 tests locking in the helper contract: no-op when not frozen, _ORIG restoration, drop when no _ORIG, DYLD variants, immutability of input mapping and os.environ, base-mapping precedence.
  • tests/unit/test_update_command.py -- two regression guards asserting the installer is always spawned with an explicit env= kwarg (Unix and Windows paths).

Full unit suite: 5308 passed.

End-to-end reproduction on WSL Ubuntu 22.04

  1. Placed deliberately broken libssl.so.3 / libcrypto.so.3 in a fake _internal/ dir.
  2. With LD_LIBRARY_PATH pointed at that dir, curl https://api.github.com/... failed with error while loading shared libraries: libssl.so.3: file too short -- the same dlopen-failure class as [BUG] apm update fails #894.
  3. With sys.frozen=True plus the real external_process_env() applied to the subprocess env, the same curl call returned http_code=200, while the main process still saw the polluted LD_LIBRARY_PATH -- proving the helper does not mutate the live environment.
  4. Outside a frozen build the helper is a no-op and curl still fails -- confirming dev-environment behaviour is untouched.

Note on upgrade path from pre-0.9.3 binaries

This fix takes effect from the next release onwards. Users already on an affected binary (0.8.5 or earlier in an environment whose system libcurl needs OPENSSL_3.2.0+) cannot apm update their way out, because their running binary lacks the fix. install.sh already points such users at the pip fallback (pip install --user apm-cli), which is a one-time escape. From 0.9.3+ onwards, apm update is immune to this bug class.

apm update inherits the PyInstaller bootloader's LD_LIBRARY_PATH when
spawning the platform installer. The shell -- and the curl / tar / sudo
calls install.sh makes -- then dlopens libssl.so.3 / libcrypto.so.3 from
the bundle's _internal/ directory instead of the system ones. When the
bundled libs are ABI-incompatible with what the system libcurl needs,
curl aborts with "OPENSSL_3.2.0 not found" on the very first release
fetch, blocking the upgrade path for every user on an affected distro
(Debian trixie arm64 dev-containers, Fedora 43, and similar).

Centralise PyInstaller env sanitisation in a new helper,
apm_cli.utils.subprocess_env.external_process_env, which restores
LD_LIBRARY_PATH / DYLD_LIBRARY_PATH / DYLD_FRAMEWORK_PATH from the
<NAME>_ORIG snapshots that PyInstaller's bootloader saves at launch, or
drops them entirely when no snapshot exists. The _ORIG keys are stripped
from the returned env so PyInstaller internals do not leak to the child.
Outside a frozen build and on Windows the helper is a no-op.

apm update now calls subprocess.run with env=external_process_env() so
the installer runs against the user's pre-launch environment. Restoring
from _ORIG rather than blindly popping preserves legitimate user
exports (CUDA, Nix, custom toolchains).

Complements microsoft#466's build-side exclude: that fix stopped new binaries
from shipping the offending libs; this fix stops them from being
inherited by spawned children even when they are present in older
binaries or in any future bundle that re-introduces a similar
dependency.

Closes microsoft#894
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] apm update fails

1 participant