[go: up one dir, main page]

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

proc_open(): Argument #2 ($descriptor_spec) must only contain arrays and streams [Descriptor item must be either an array or a File-Handle] #12655

Closed
martin-rueegg opened this issue Nov 12, 2023 · 2 comments

Comments

@martin-rueegg
Copy link

Description

The following code:

<?php

$descriptor_spec = [
    0=> [ "pipe", "r" ],  // stdin is a pipe that the child will read from
    1=> [ "pipe", "w" ],  // stdout is a pipe that the child will write to
    2=> [ "pipe", "w" ],  // stderr is a file to write to
];

// NOTE: the array value is iterated by reference!
foreach ( $descriptor_spec as $fd => &$d )
{
    // don't do anything, just the fact that we used "&$d" will sink the ship!
    ;
    // my use case was validation of the array and adjusting some default values
}

$proc = proc_open( "/bin/true", $descriptor_spec, $pipes );
echo $proc === false ? "FAILED\n" : "SUCCEEDED\n";

Resulted in this output (v 7.4):

PHP Stack trace:
PHP   1. {main}() /tmp/scratch.php:0
PHP   2. proc_open($command = '/bin/true', $descriptorspec = [0 => [0 => 'pipe', 1 => 'r'], 1 => [0 => 'pipe', 1 => 'w'], 2 => [0 => 'pipe', 1 => 'w']], $pipes = NULL) /tmp/scratch.php:17
FAILED

Resulted in this output (v 8.x):

PHP Fatal error:  Uncaught ValueError: proc_open(): Argument #2 ($descriptor_spec) must only contain arrays and streams in /tmp/scratch.php:17
Stack trace:
#0 /tmp/scratch.php(17): proc_open()
#1 {main}
  thrown in /tmp/scratch.php on line 17

Process finished with exit code 255

But I expected this output instead:

  •  SUCCEEDED
    
  • no error at all, since descriptor_spec is a perfectly valid array containing valid arrays, as the output of v7.4 confirms
  • all there was, I iterated over the array by reference!

If you leave away the "by-reference-amphersand", the code will succeed!

Since 3v4l.org does not support proc_open, I've created a test code which I'll post in the first comment.

PHP Version

PHP 7.4.33 / 8.1.25 / 8.2.12

Operating System

Ubuntu 22.04.3 LTS

@martin-rueegg
Copy link
Author

Code to test different php versions.

Just adjust the constant in the first line of code!

<?php

const PHP_BINARIES = [ '/usr/bin/php7.4', '/usr/bin/php8.1', '/usr/bin/php8.2' ];

printf( "\n\n%s (%s): '%s'\n", PHP_BINARY, PHP_VERSION, implode( "' '", $argv ) );

$results = [];

if ( $argc === 1 )
{
    foreach ( PHP_BINARIES as $binary )
    {
        $fatal                 = false;
        $summary               = false;
        $last                  = null;
        $results[ $binary ] [] = shell_exec( "$binary --version" );

        $handle = popen(
            sprintf(
                "'%s' '%s' 1 2>&1",
                $binary,
                __FILE__
            ),
            'r'
        );

        while ( $line = fgets( $handle, 500 ) )
        {
            if ( ! ( $summary || $fatal )
                && (
                    false
                    || ( substr( $line, 0, 9 ) === "SUCCESS: " )
                    || ( substr( $line, 0, 8 ) === "FAILED: " )
                    || ( $summary = substr( $line, 0, 7 ) === "SUMMARY" )
                ) )
            {
                if ( ! $summary )
                {
                    $results[ $binary ][] = $line;
                }
            }

            echo $line;
            $last = $line;
        }

        $results[ $binary ][] = sprintf( "rc=%s\n", pclose( $handle ) );
    }

    foreach ( $results as $binary => $result )
    {
        echo "\n\nRESULT for $binary;\n";

        foreach ( $result as $line )
        {
            echo "$line";
        }
    }
    exit();
}

const FD_I = 0;        # stdin
const FD_O = 1;        # stdout
const FD_E = 2;        # stderr

const DESCRIPTORS
= [
    FD_I => [ "pipe", "r" ], // stdin is a pipe that the child will read from
    FD_O => [ "pipe", "w" ], // stdout is a pipe that the child will write to
    FD_E => [ "pipe", "w" ], // stderr is a file to write to
];

function out( ?string $string )
{

    static $stdout = STDOUT;
    fwrite( $stdout, $string );

    return $stdout;
}

if (\PHP_VERSION_ID < 80000) {
    class ValueError extends Error
    {
    }
}


set_error_handler( function (
    int    $errno,
    string $message
): bool {

    throw new ValueError($message, $errno);
    out( sprintf( "%d: %s\n", $errno, $message ) );

    return true;
},
    E_WARNING
);

$run = static function ( ?array $descriptorSpec, $comment ) use ( &$results )
{

    static $test = 0;

    $comment = sprintf( "%2s - %s", ++ $test, $comment );

    out( "\nGoing to run: $comment\n" );

    try
    {

        if ( $descriptorSpec !== null )
        {
            $proc = proc_open( "/bin/true", $descriptorSpec, $pipes );
        }
        else
        {
            $proc = proc_open( "/bin/true", DESCRIPTORS, $pipes );
        }

        if ( $proc === false )
        {
            $result = "FAILED";
        }
        else
        {
            foreach ( $pipes as $pipe )
            {
                fclose( $pipe );
            }

            $return_value = proc_close( $proc );

            out( "rc=$return_value\n" );
            $result = "SUCCESS";
        }
    }
    catch ( ValueError $e )
    {
        printf( "%s: %s\n", get_class( $e ), $e->getMessage() );
        var_dump( $e->getTraceAsString() );
        $result  = 'FAILED';
        $comment .= ': ' . $e->getMessage();
    }

    $results[ $comment ] = $result;
    out( sprintf( "%-8s %s\n", "$result:", $comment ) );

    return $result === 'SUCCESS';
};

$descriptorSpec = DESCRIPTORS;

$run( $descriptorSpec, "clean array" ) || $run( $descriptorSpec = DESCRIPTORS, "re-run clean" );

$descriptorSpec = DESCRIPTORS;
foreach ( $descriptorSpec as $fd => $d )
{
    ;
}
$run( $descriptorSpec, "foreach by-val" ) || $run( $descriptorSpec = DESCRIPTORS, "re-run clean" );

$descriptorSpec = DESCRIPTORS;
foreach ( $descriptorSpec as $fd => &$d )
{
    ;
}
$run( $descriptorSpec, "foreach by-ref" ) || $run( $descriptorSpec = DESCRIPTORS, "re-run clean" );

$descriptorSpec = DESCRIPTORS;
array_walk( $descriptorSpec, static fn( $d ) => true );
$run( $descriptorSpec, "array_walk by-val" ) || $run( $descriptorSpec = DESCRIPTORS, "re-run clean" );

$descriptorSpec = DESCRIPTORS;
array_walk( $descriptorSpec, static fn( &$d ) => true );
$run( $descriptorSpec, "array_walk by-ref" ) || $run( $descriptorSpec = DESCRIPTORS, "re-run clean" );

$run( null, "original DESCRIPTORS" ) || $run( $descriptorSpec = DESCRIPTORS, "re-run clean" );

$descriptorSpec = DESCRIPTORS;
foreach ( DESCRIPTORS as $fd => $d )
{
    ;
}
$run( null, "foreach by-val DESCRIPTORS" ) || $run( $descriptorSpec, "re-run clean" );

foreach ( DESCRIPTORS as $fd => &$d )
{
    ;
}
$run( null, "foreach by-ref DESCRIPTORS" ) || $run( $descriptorSpec, "re-run clean" );

echo sprintf( "\n\nSUMMARY for php %s\n", PHP_VERSION );
array_walk( $results, static function ( $result, $comment )
{

    out( sprintf( "%-8s %s\n", "$result:", $comment ) );
} );

fclose( out( null ) );

Output prints something that ends with the following summary:

RESULT for /usr/bin/php7.4;
PHP 7.4.33 (cli) (built: Sep  2 2023 08:03:46) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.33, Copyright (c), by Zend Technologies
    with Xdebug v3.1.6, Copyright (c) 2002-2022, by Derick Rethans
SUCCESS:  1 - clean array
SUCCESS:  2 - foreach by-val
FAILED:   3 - foreach by-ref: proc_open(): Descriptor item must be either an array or a File-Handle
SUCCESS:  4 - re-run clean
FAILED:   5 - array_walk by-val: proc_open(): Descriptor item must be either an array or a File-Handle
SUCCESS:  6 - re-run clean
FAILED:   7 - array_walk by-ref: proc_open(): Descriptor item must be either an array or a File-Handle
SUCCESS:  8 - re-run clean
SUCCESS:  9 - original DESCRIPTORS
SUCCESS: 10 - foreach by-val DESCRIPTORS
SUCCESS: 11 - foreach by-ref DESCRIPTORS
rc=0


RESULT for /usr/bin/php8.1;
PHP 8.1.25 (cli) (built: Oct 27 2023 14:00:40) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.25, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.25, Copyright (c), by Zend Technologies
    with Xdebug v3.2.1, Copyright (c) 2002-2023, by Derick Rethans
SUCCESS:  1 - clean array
SUCCESS:  2 - foreach by-val
FAILED:   3 - foreach by-ref: proc_open(): Argument #2 ($descriptor_spec) must only contain arrays and streams
SUCCESS:  4 - re-run clean
FAILED:   5 - array_walk by-val: proc_open(): Argument #2 ($descriptor_spec) must only contain arrays and streams
SUCCESS:  6 - re-run clean
FAILED:   7 - array_walk by-ref: proc_open(): Argument #2 ($descriptor_spec) must only contain arrays and streams
SUCCESS:  8 - re-run clean
SUCCESS:  9 - original DESCRIPTORS
SUCCESS: 10 - foreach by-val DESCRIPTORS
SUCCESS: 11 - foreach by-ref DESCRIPTORS
rc=0


RESULT for /usr/bin/php8.2;
PHP 8.2.12 (cli) (built: Oct 26 2023 17:33:49) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.12, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.12, Copyright (c), by Zend Technologies
    with Xdebug v3.2.1, Copyright (c) 2002-2023, by Derick Rethans
SUCCESS:  1 - clean array
SUCCESS:  2 - foreach by-val
FAILED:   3 - foreach by-ref: proc_open(): Argument #2 ($descriptor_spec) must only contain arrays and streams
SUCCESS:  4 - re-run clean
FAILED:   5 - array_walk by-val: proc_open(): Argument #2 ($descriptor_spec) must only contain arrays and streams
SUCCESS:  6 - re-run clean
FAILED:   7 - array_walk by-ref: proc_open(): Argument #2 ($descriptor_spec) must only contain arrays and streams
SUCCESS:  8 - re-run clean
SUCCESS:  9 - original DESCRIPTORS
SUCCESS: 10 - foreach by-val DESCRIPTORS
SUCCESS: 11 - foreach by-ref DESCRIPTORS
rc=0

@martin-rueegg martin-rueegg changed the title proc_open(): Descriptor item must be either an array or a File-Handle proc_open(): Argument #2 ($descriptor_spec) must only contain arrays and streams [Descriptor item must be either an array or a File-Handle] Nov 12, 2023
@nielsdos
Copy link
Member

Can reproduce. Missing a ZVAL_DEREF, we've done similar fixes in the past. PR coming soon.

nielsdos added a commit to nielsdos/php-src that referenced this issue Nov 12, 2023
nielsdos added a commit that referenced this issue Nov 13, 2023
* PHP-8.2:
  Fix GH-12655: proc_open() does not take into account references in the descriptor array
nielsdos added a commit that referenced this issue Nov 13, 2023
* PHP-8.3:
  Fix GH-12655: proc_open() does not take into account references in the descriptor array
ramsey pushed a commit that referenced this issue Nov 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants