opencv / opencv/opencv-python

cv2 import appends a trailing ":" to LD_LIBRARY_PATH — empty entry resolves to CWD and breaks child processes

Open
#1,268 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
5.4k
Forks
1k
Avg merge
22h 17m
Merged PRs (30d)
3

Description

Environment
  • opencv-python: reproduced on 4.11.0.86, 4.12.0.88, 4.13.0.92, 4.14.0.94 and 5.0.0.93 (PyPI manylinux x86_64 wheels; earliest tested 4.11.0.86, Jan 2025)
  • Python 3.13.15 / 3.14.7, Linux x86_64 (glibc; CachyOS/Arch, kernel 6.x)
Minimal reproduction
python -m venv venv && venv/bin/pip install opencv-python==5.0.0.93
venv/bin/python - <<'EOF'
import os, subprocess

print("before import:", repr(os.environ.get("LD_LIBRARY_PATH")))
import cv2
print("cv2", cv2.__version__, "after import:", repr(os.environ.get("LD_LIBRARY_PATH")))
print("child sees:", subprocess.run(["sh", "-c", "printf '%s' \"$LD_LIBRARY_PATH\""],
      capture_output=True, text=True).stdout)
EOF
Observed
before import: None
cv2 5.0.0 after import: '/…/site-packages/cv2/../../lib64:'
child sees: '/…/site-packages/cv2/../../lib64:'

cv2/__init__.py (POSIX branch, cv2/__init__.py:146-147 in current wheels):

# amending of LD_LIBRARY_PATH works for sub-processes only
os.environ['LD_LIBRARY_PATH'] = ':'.join(l_vars['BINARIES_PATHS']) + ':' + os.environ.get('LD_LIBRARY_PATH', '')

Three problems in one line:

  1. Unconditional trailing : — when LD_LIBRARY_PATH was previously unset the
    variable is created with a dangling separator. Per ld.so(8) semantics an empty
    entry resolves to the current working directory of every child process.
  2. Global environment mutation — the comment itself notes this "works for
    sub-processes only", yet the write persists in os.environ for the whole
    process lifetime, so every subprocess/os.system/webbrowser call made by
    the host application inherits the modified path. This is invisible side-channel
    state an importer cannot reasonably expect from import cv2.
  3. The injected directory typically does not exist — with standard manylinux
    wheel layouts the bundled libraries live in opencv_python.libs/ (located via
    $ORIGIN RPATH), and <site-packages>/cv2/../../lib64 is absent in venv,
    project, and standalone-packaged layouts, so the write buys nothing while
    adding the hazard.
Real-world impact

We ship a Nuitka-standalone application whose launcher cds into the release
folder. With cv2 imported, LD_LIBRARY_PATH ends with : → the release folder
(the CWD) enters the child loader path → children that load system OpenSSL
(xdg-open → kde-open, i.e. "open a URL in the default browser") resolve the
bundled, older libssl.so.3/libcrypto.so.3 instead of the system ones and die
with:

kde-open: libssl.so.3: version `OPENSSL_3.2.0' not found (required by /usr/lib/libcurl.so.4)

Any application that imports cv2 and then spawns helper processes from a
directory containing same-named libraries is affected the same way.

Suggested fix
  • Do not append a dangling separator; only write the variable when there is
    something to add and prepend/append with proper joining:

    paths = l_vars['BINARIES_PATHS']
    if paths:
        old = os.environ.get('LD_LIBRARY_PATH')
        os.environ['LD_LIBRARY_PATH'] = ':'.join(paths) + (':' + old if old else '')
    
  • Longer term, prefer not mutating the global environment at all: the bundled
    libraries are already found through $ORIGIN RPATH; if an env-based fallback
    is really needed, consider documenting it and cleaning up after import, or
    exposing an opt-in.

Happy to send a PR to opencv-python/opencv-python (the loader __init__.py)
if you agree with the direction.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Run the minimal reproduction, then inspect the POSIX branch of cv2/init.py around lines 146-147 and trace how BINARIES_PATHS is used. Done means importing cv2 no longer creates an empty LD_LIBRARY_PATH entry or causes unrelated child processes to inherit an unsafe path; add regression coverage if the repository provides a suitable test location.

Written by the indexing model from the issue text.

Assessment

Tech stack
linux, python
Domain
build-system, operating-systems
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.