Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

subprocess with env=os.environ doesn't preserve environment variables when calling a 32bit process on Windows 8.1 #68681

Closed
The-Compiler mannequin opened this issue Jun 23, 2015 · 10 comments
Labels
OS-windows stdlib Python modules in the Lib dir

Comments

@The-Compiler
Copy link
Mannequin

The-Compiler mannequin commented Jun 23, 2015

BPO 24493
Nosy @pfmoore, @vstinner, @tjguk, @bitdancer, @zware, @eryksun, @zooba, @The-Compiler

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = <Date 2021-02-24.18:37:41.504>
created_at = <Date 2015-06-23.20:37:07.531>
labels = ['library', 'OS-windows']
title = "subprocess with env=os.environ doesn't preserve environment variables when calling a 32bit process on Windows 8.1"
updated_at = <Date 2021-02-24.18:37:41.504>
user = 'https://github.com/The-Compiler'

bugs.python.org fields:

activity = <Date 2021-02-24.18:37:41.504>
actor = 'eryksun'
assignee = 'none'
closed = True
closed_date = <Date 2021-02-24.18:37:41.504>
closer = 'eryksun'
components = ['Library (Lib)', 'Windows']
creation = <Date 2015-06-23.20:37:07.531>
creator = 'The Compiler'
dependencies = []
files = []
hgrepos = []
issue_num = 24493
keywords = []
message_count = 10.0
messages = ['245700', '245703', '245706', '245717', '245722', '245724', '250829', '250843', '250844', '250848']
nosy_count = 9.0
nosy_names = ['paul.moore', 'astrand', 'vstinner', 'tim.golden', 'r.david.murray', 'zach.ware', 'eryksun', 'steve.dower', 'The Compiler']
pr_nums = []
priority = 'normal'
resolution = 'third party'
stage = 'resolved'
status = 'closed'
superseder = None
type = None
url = 'https://bugs.python.org/issue24493'
versions = ['Python 3.4']

@The-Compiler
Copy link
Mannequin Author

The-Compiler mannequin commented Jun 23, 2015

Trying to call a 32bit Python via subprocess from a 64bit one works as expected:

    Python 3.4.2 (v3.4.2:ab2c023a9432, Oct  6 2014, 22:16:31) [MSC v.1600 64 bit (AMD64)] on win32
    [...]
    >>> subprocess.check_call([r'C:\Python34_x32\python.exe', '-c', 'print("Hello World")'])
    Hello World

However, when passing the env parameter, even just as env=os.environ, things break:

    >>> subprocess.check_call([r'C:\Python34_x32\python.exe', '-c', 'print("Hello World")'], env=os.environ)
    Fatal Python error: Failed to initialize Windows random API (CryptoGen)

Starting a 64bit Python from the 64bit one works fine:

    >>> subprocess.check_call([r'C:\Python34\python.exe', '-c', 'print("Hello World")'], env=os.environ)
    Hello World

According to bpo-20614 the "Fatal Python error" happens when SYSTEMROOT is not set, but that's definitely set in the original environment.

Looking at the code, it just passes env to Windows' CreateProcess[1] - I guess this does *something* different in this scenario when None/NULL is given as compared to the os.environ dict?

This breaks virtualenv with --python for me:

C:\Users\florian\tmp>C:\Python34\python.exe -m virtualenv --python=C:\Python34_x32\python.exe venv3
Running virtualenv with interpreter C:\Python34_x32\python.exe
Fatal Python error: Failed to initialize Windows random API (CryptoGen)

[1] https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx

@The-Compiler The-Compiler mannequin added stdlib Python modules in the Lib dir OS-windows labels Jun 23, 2015
@eryksun
Copy link
Contributor

eryksun commented Jun 24, 2015

I can't reproduce this in Windows 7 or 10 using Python 3.4. What gets printed for the following?

import os
import subprocess

cmd32 = os.path.join(os.environ['SYSTEMROOT'], 'SysWOW64', 'cmd.exe')
subprocess.call('{} /c set SYSTEMROOT'.format(cmd32), env=os.environ)

@The-Compiler
Copy link
Mannequin Author

The-Compiler mannequin commented Jun 24, 2015

That gives me "Environment variable SYSTEMROOT not defined" which would explain the "Fatal Python error" I'm seeing.

@The-Compiler
Copy link
Mannequin Author

The-Compiler mannequin commented Jun 24, 2015

Sorry I missed this - I can reproduce this on Windows 8.1, but not on Windows 7. I hope I'll be able to try another Windows 8.1 machine today.

SYSTEMROOT is definitely set in the original environment:

    >>> os.environ['SYSTEMROOT']
    'C:\\Windows'
    >>> subprocess.call('{} /c set SYSTEMROOT'.format(cmd32), env=os.environ)
    Environment variable SYSTEMROOT not defined
    1
    >>> subprocess.call('{} /c set SYSTEMROOT'.format(cmd32))
    SystemRoot=C:\Windows
    0

It seems only a minimal set of environment variables are set in the spawned process:

    >>> subprocess.call('{} /c set'.format(cmd32), env=os.environ)
    COMSPEC=C:\Windows\SysWOW64\cmd.exe
    PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC
    PROMPT=$P$G

@The-Compiler The-Compiler mannequin changed the title subprocess with env=os.environ fails with "fatal python error" when calling 32-bit python from 64-bit one on Windows subprocess with env=os.environ doesn't preserve environment variables when calling a 32bit process on Windows 8.1 Jun 24, 2015
@eryksun
Copy link
Contributor

eryksun commented Jun 24, 2015

It seems only a minimal set of environment variables are set

Apparently the initial environment is empty. The values you see are defaults set by cmd.exe when it starts. It also sets the 'hidden' variable "=C:" to the current directory on the C: drive, which you can see via set "".

As a workaround, try running the command using shell=True (i.e. cmd /c). This should let you modify the environment passed to 64-bit cmd.exe, which will be inherited by the grandchild process that it creates.

@The-Compiler
Copy link
Mannequin Author

The-Compiler mannequin commented Jun 24, 2015

I've now updated to Python 3.4.3 and it's broken there as well.

I tried on two other Windows 8.1 machines (with Python 3.4.3 and 3.4.1 respectively), and I can't reproduce there either...

It works with shell=True indeed.

I wonder why this is only broken on that one machine... It's a virtual machine which only has Firefox, Python, PyQt and a few Python packages installed - otherwise it's vanilla.

@The-Compiler
Copy link
Mannequin Author

The-Compiler mannequin commented Sep 16, 2015

I just ran into this again - when trying to run git via subprocess with "env" set, I got:

    \# Start the process
    try:
        hp, ht, pid, tid = \_winapi.CreateProcess(executable, args,
                                 \# no special security
                                 None, None,
                                 int(not close_fds),
                                 creationflags,
                                 env,
                                 cwd,
                               startupinfo)

E FileNotFoundError: [WinError 2] The system cannot find the file specified

This only seems to happen when starting my Python process in cmd.exe, not when it's started via my buildbot (CI).

Again, when passing shell=True everything worked - except when passing cwd as well, then it's broken again.

@bitdancer
Copy link
Member

Sounds like there's something in the cwd that is making the difference (this being Windows which looks for things in the cwd always).

@The-Compiler
Copy link
Mannequin Author

The-Compiler mannequin commented Sep 16, 2015

Sounds like there's something in the cwd that is making the difference (this being Windows which looks for things in the cwd always).

The cwd is an empty temporary directory. And that still wouldn't explain why passing env=os.environ breaks it as well, and why it only breaks in cmd.exe, and only when launching a 32bit process ;)

@eryksun
Copy link
Contributor

eryksun commented Sep 16, 2015

The issue as I understand it is that, on this particular Windows 8.1 system, passing a non-NULL lpEnvironment to CreateProcess works when starting a native 64-bit executable, but fails when starting a 32-bit executable via the WOW64 system. The child process instead gets an empty environment block.

As an additional check, run the following command in 64-bit cmd.exe:

start "" /I "%SystemRoot%\SysWOW64\cmd.exe"

The /I option of the start command passes the shell's original environment to CreateProcess, so it should exhibit the same empty-environment problem when starting 32-bit cmd.exe. In this case you'll get cmd's default environment, which includes COMSPEC, PATHEXT, and PROMPT.

Since inheriting the current environment works in all cases and passing a custom environment works for 64-bit executables, the workaround that I suggested is to use shell=True to pass your custom environment to the shell. The 32-bit executable thus inherits the custom environment from the shell. If using shell=True is a security concern, then you can replace it with a Python script that executes and waits for the child process.

when passing shell=True everything worked - except
when passing cwd as well, then it's broken again.

Since the shell executes the file, a relative path is resolved against the shell's working directory. If you set the latter via the cwd parameter, then pass the file's path as either relative to the cwd path or as a fully qualified path.

@eryksun eryksun closed this as completed Feb 24, 2021
@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OS-windows stdlib Python modules in the Lib dir
Projects
None yet
Development

No branches or pull requests

2 participants