◐ Shell
clean mode source ↗

New subclass payload layout by youknowone · Pull Request #6319 · RustPython/RustPython

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "ssl.rs" -type f 2>/dev/null | head -5

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# First, find the ssl.rs file
find . -path "*/stdlib/src/ssl.rs" -type f

Repository: RustPython/RustPython

Length of output: 91


🏁 Script executed:

# Search for ssl.rs in the repository
fd -t f "ssl.rs" | head -10

Repository: RustPython/RustPython

Length of output: 118


🏁 Script executed:

# Read the ssl.rs file around lines 1870-1881 with context
sed -n '1850,1900p' ./crates/stdlib/src/ssl.rs | cat -n

Repository: RustPython/RustPython

Length of output: 2713


🏁 Script executed:

# Get more context - read from line 1850 to 1900 to see the function signature
head -1900 ./crates/stdlib/src/ssl.rs | tail -60 | cat -n

Repository: RustPython/RustPython

Length of output: 3016


🏁 Script executed:

# Let's check the file size first
wc -l ./crates/stdlib/src/ssl.rs

Repository: RustPython/RustPython

Length of output: 96


🏁 Script executed:

# Search for how other modules use new_os_subtype_error and errno
rg -A5 "new_os_subtype_error" --type rs | head -80

Repository: RustPython/RustPython

Length of output: 91


🏁 Script executed:

# Search for FileNotFoundError usage patterns
rg -B3 -A3 "file_not_found_error" --type rs | head -100

Repository: RustPython/RustPython

Length of output: 91


🏁 Script executed:

# Check how CPython's ssl module handles file not found errors
web_search

Repository: RustPython/RustPython

Length of output: 115


🏁 Script executed:

# Search for how other modules use new_os_subtype_error
rg "new_os_subtype_error" -A5 | head -100

Repository: RustPython/RustPython

Length of output: 5611


🏁 Script executed:

# Search for FileNotFoundError patterns in the codebase
rg "file_not_found_error" -B3 -A3 | head -100

Repository: RustPython/RustPython

Length of output: 7049


🏁 Script executed:

# Check if there's any errno constant definition or usage patterns
rg "errno.*2\|ENOENT" --max-count 20

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Check how std::fs errors are handled elsewhere
rg "std::fs::" | grep -i "error\|map_err" | head -20

Repository: RustPython/RustPython

Length of output: 483


🏁 Script executed:

# Check for errno/ENOENT constants
rg "ENOENT|errors::" crates/vm/src/exceptions.rs | head -20

Repository: RustPython/RustPython

Length of output: 1148


🏁 Script executed:

# Look at the full socket.rs pattern for errno extraction
rg -B5 -A5 "error_num\(\)" crates/stdlib/src/socket.rs | head -40

Repository: RustPython/RustPython

Length of output: 966


🏁 Script executed:

# Check the error mapping in exceptions.rs more carefully
rg -B2 -A2 "ENOENT" crates/vm/src/exceptions.rs

Repository: RustPython/RustPython

Length of output: 364


🌐 Web query:

CPython ssl.load_dh_params FileNotFoundError errno Windows POSIX behavior

💡 Result:

Short answer

  • ssl.SSLContext.load_dh_params(dhfile) opens the file with the C runtime and calls OpenSSL's PEM_read_DHparams; if fopen/_Py_fopen_obj fails Python exposes the OS error (FileNotFoundError / OSError) with errno. [1][2]
  • On POSIX this typically behaves as you expect (fopen fails → errno = ENOENT → FileNotFoundError). On Windows the same underlying fopen can also fail for other reasons (path encoding/mbcs issues, wide/ANSI conversion, or Windows-specific fopen behavior), so you can also get FileNotFoundError or other OSError variants even when a path that looks valid to Python strings is used. This is a known class of platform differences for ssl file-loading APIs. [2][3]

Why this happens (brief technical outline)

  • CPython's _ssl implementation calls _Py_fopen_obj(filepath, "rb") and then PEM_read_DHparams on the FILE*. If _Py_fopen_obj returns NULL, CPython inspects errno and raises an OSError (FileNotFoundError if errno==ENOENT). If PEM_read_DHparams returns NULL with errno==0, a different SSL error is raised. (Code and reproduction notes shown in community / source snippets). [2]
  • Differences in how Windows converts Python filename objects to the C fopen call (MBCS/ANSI vs wide APIs used by Python internals, and what OpenSSL or the C runtime expects) produce platform-dependent failures; similar issues have been reported and fixed historically for other ssl file-loading functions (e.g. load_verify_locations). [3][4]

What to do

  • Verify the file exists and is readable from Python (os.path.exists, open(...,'rb')). If that succeeds but load_dh_params still fails, the problem is likely an encoding/ABI/path-form issue on Windows (non-ASCII characters, reserved names, long paths). Use a plain ASCII path or a path exposed via a file descriptor opened in Python and re-create the file in a known-good location. [1][2]
  • If you need a robust workaround on Windows for non-encodable paths, open the file yourself in Python (open(path,'rb')) to confirm accessibility; report/attach a minimal reproducer to the CPython issue tracker if fopen succeeds but load_dh_params still fails. See linked reports about related Windows path/encoding behavior in the ssl module. [3][4]

Sources

  • CPython ssl docs (SSLContext.load_dh_params). [1]
  • Community excerpt showing the _ssl.c implementation calling _Py_fopen_obj and handling errno / PEM_read_DHparams. [2]
  • Python bug tracker / issue about Windows path/encoding differences for ssl.load_verify_locations (same class of problem). [3]
  • Python ssl module documentation note about platform-dependent behavior (OpenSSL/OS differences). [4]

References
[1] Python ssl docs — SSLContext.load_dh_params. (docs.python.org)
[2] Community discussion / snippet showing _ssl__SSLContext_load_dh_params implementation and errno handling. (community.letsencrypt.org)
[3] Python issue: SSLContext.load_verify_locations Windows encoding/path behavior. (bugs.python.org)
[4] Python ssl module notes on platform/OpenSSL differences. (docs.python.org)

Citations:


🏁 Script executed:

# Check the stat module mentioned in learnings for platform-specific handling pattern
rg -B5 -A5 "SF_SUPPORTED\|SF_SYNTHETIC" crates/stdlib/src/stat.rs | head -50

Repository: RustPython/RustPython

Length of output: 130


🏁 Script executed:

# Check if there's a utility function to convert std::io::Error to proper errno
rg -B3 -A3 "e.kind\(\)" crates/stdlib/src/ | head -60

Repository: RustPython/RustPython

Length of output: 3925


🏁 Script executed:

# Check how into_pyexception() works
rg -B3 -A5 "into_pyexception" crates/stdlib/src/ssl.rs | head -60

Repository: RustPython/RustPython

Length of output: 1890


🏁 Script executed:

# Check the trait definition or implementation
rg "into_pyexception" crates/ | grep -E "(fn|impl|trait)" | head -20

Repository: RustPython/RustPython

Length of output: 1166


🏁 Script executed:

# Look at the specific load_dh_params code more carefully
sed -n '1840,1895p' ./crates/stdlib/src/ssl.rs | cat -n

Repository: RustPython/RustPython

Length of output: 2880


🏁 Script executed:

# Check the into_pyexception implementation for std::io::Error
rg -B5 -A15 "impl.*into_pyexception.*std::io::Error|fn into_pyexception.*io::Error" crates/

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Check os.rs implementation
sed -n '1,100p' crates/vm/src/stdlib/os.rs | grep -A20 "into_pyexception"

Repository: RustPython/RustPython

Length of output: 982


🏁 Script executed:

# Find the exact implementation in os.rs
rg -B2 -A10 "fn into_pyexception" crates/vm/src/stdlib/os.rs

Repository: RustPython/RustPython

Length of output: 668


🏁 Script executed:

# Find the io::Error into_pyexception implementation
rg -B5 -A15 "impl.*IntoPyException.*for.*io::Error" crates/

Repository: RustPython/RustPython

Length of output: 1183


🏁 Script executed:

# Check the convert/to_pyobject.rs file for io::Error
rg -B5 -A20 "io::Error" crates/vm/src/convert/to_pyobject.rs | head -80

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Find to_pyexception implementation
rg -B5 -A20 "fn to_pyexception" crates/vm/src/stdlib/io.rs | head -100

Repository: RustPython/RustPython

Length of output: 1160


🏁 Script executed:

# Check the full to_pyexception implementation to see how it maps errno to exception types
rg -B5 -A50 "fn to_pyexception" crates/vm/src/stdlib/io.rs | tail -80

Repository: RustPython/RustPython

Length of output: 2135


Refactor to eliminate the existence check and use proper error handling. Instead of checking Path::exists() and hardcoding errno=2, try to read the file directly and match on std::io::ErrorKind to convert errors properly via into_pyexception(). This approach: