php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Doc Bug #79704 proc_open failure with Windows
Submitted: 2020-06-16 02:30 UTC Modified: 2020-06-17 07:55 UTC
From: svnpenn at gmail dot com Assigned: cmb (profile)
Status: Closed Package: Program Execution
PHP Version: 7.4.7 OS: Windows 10
Private report: No CVE-ID: None
 [2020-06-16 02:30 UTC] svnpenn at gmail dot com
Description:
------------
proc_open failure with Windows

If I run this Python script:

    import subprocess
    a = [
       r'C:\Windows\System32\msiexec.exe',
       r'TargetDir=C:\TargetDir',
       '/qb',
       '/a',
       'a.msi'
    ]
    subprocess.run(a)

Everything is fine, but if I run this PHP script:

    <?php
    declare(strict_types = 1);
    $a1 = [
       'C:\\Windows\\System32\\msiexec.exe',
       'TargetDir=C:\\TargetDir',
       '/qb',
       '/a',
       'a.msi'
    ];
    $a2 = [];
    $a3 = [];
    proc_open($a1, $a2, $a3);

I just get the usage page for Windows Installer. The documentation and tests
show that you can use an array:

- https://github.com/php/php-src/blob/master/ext/standard/tests/general_functions/proc_open_array.phpt
- https://www.php.net/manual/en/function.proc-open.php

but it seems PHP is doing something funny with the parsing.


Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2020-06-16 02:46 UTC] requinix@php.net
-Status: Open +Status: Not a bug
 [2020-06-16 02:46 UTC] requinix@php.net
proc_open() with the $cmd as an array will quote all the arguments.
Try running the resultant command yourself:

C:\>"C:\Windows\System32\msiexec.exe" "TargetDir=C:\TargetDir" "/qb" "/a" "a.msi"

What you need to do is

$a = "C:\\Windows\\System32\\msiexec.exe TargetDir=C:\\TargetDir /qb /a a.msi";

The important thing is that you do not quote the TargetDir option or the flags. If the filename is variable then you may quote it (using escapeshellarg) - this works because msiexec specifically supports it.
 [2020-06-16 08:21 UTC] cmb@php.net
-Status: Not a bug +Status: Re-Opened -Type: Bug +Type: Documentation Problem -Package: *General Issues +Package: Program Execution -Assigned To: +Assigned To: cmb
 [2020-06-16 08:21 UTC] cmb@php.net
Apparently, msiexec.exe does not use the supplied argv[1], but
rather GetCommandLine() doing its own parsing of the arguments.
Other commands may do this as well, so we should at least document
this issue.

[1] <https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=vs-2019#parsing-c-command-line-arguments>
 [2020-06-16 23:20 UTC] svnpenn at gmail dot com
> proc_open() with the $cmd as an array will quote all the arguments.

With a POSIX shell, this is fine. As with non special characters, a quoted
string is the same as a non quoted string. So with a POSIX shell, these would
be the same:

    /a
    '/a'

but the Windows shell is not POSIX. And quoting should only be used when it is
needed. A command like this will succeed:

    msiexec.exe TargetDir=C:\TargetDir /qb /a a.msi

but add quotes and it fails:

    msiexec.exe TargetDir=C:\TargetDir /qb "/a" a.msi

So it seems strange that "proc_open" assumes this is the correct thing to do for
all platforms. As detailed in the original post, other languages dont do that.
To make it worse, "escapeshellarg" is broken on Windows as well:

> On Windows, escapeshellarg() instead replaces percent signs, exclamation marks
> (delayed variable substitution) and double quotes with spaces and adds double
> quotes around the string. 

https://php.net/function.escapeshellarg

From a security standpoint that might be fine, but makes it impossible to run
some valid external commands from PHP on Windows platform. As a workaround, I
wrote this function:

    function f_escape_sh($s_in) {
       $s_out = preg_replace('/"/', '""', $s_in);
       if ($s_out != $s_in) {
          return '"' . $s_out . '"';
       }
       $n_mat = preg_match('/[ &>^-]/', $s_in);
       if ($n_mat == 1) {
          return '"' . $s_out . '"';
       }
       return $s_out;
    }
 [2020-06-17 07:55 UTC] cmb@php.net
If $cmd is an array, the bypass_shell option is implicitly set, so
there is no shell (COMSPEC) involved.  Thus, shell escaping does
not apply.  Instead, the arguments are escaped in conformance to
how parsing C++ command line arguments is documented[1].  This
works fine, if the executed command actually uses the runtime
supplied argv[]; msiexec.exe unfortunately does not.

Admittedly, the escaping applied to the $cmd array elements, as
well as that of escapeshellarg() does more than necessary, but
that can't be changed for BC reasons (at least not for stable PHP
branches).

[1] <https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=vs-2019#parsing-c-command-line-arguments>
 [2020-06-18 08:54 UTC] phpdocbot@php.net
Automatic comment on behalf of cmb
Revision: http://git.php.net/?p=doc/en.git;a=commit;h=24143af06cb3df496e671ae710af6b54398b2f74
Log: Fix #79704: proc_open failure with Windows
 [2020-06-18 08:54 UTC] phpdocbot@php.net
-Status: Re-Opened +Status: Closed
 [2020-06-19 20:25 UTC] phpdocbot@php.net
Automatic comment on behalf of mumumu
Revision: http://git.php.net/?p=doc/ja.git;a=commit;h=606f91ee4b20daa8f3b22d7d7c8c6340673a1702
Log: Fix #79704: proc_open failure with Windows
 [2020-12-30 11:59 UTC] nikic@php.net
Automatic comment on behalf of mumumu
Revision: http://git.php.net/?p=doc/ja.git;a=commit;h=25567314762e0bf4b31f4f7f9c9de5df05e4fa8d
Log: Fix #79704: proc_open failure with Windows
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun Dec 22 05:01:30 2024 UTC