Bug #80959 [Ver]: infinite loop in building cfg during JIT compilation

From: Date: Fri, 16 Apr 2021 09:50:26 +0000
Subject: Bug #80959 [Ver]: infinite loop in building cfg during JIT compilation
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-233467@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=80959&edit=1

 ID:                 80959
 Updated by:         nikic@php.net
 Reported by:        zengyhkyle at asu dot edu
 Summary:            infinite loop in building cfg during JIT compilation
 Status:             Verified
 Type:               Bug
 Package:            JIT
 Operating System:   Linux
 PHP Version:        8.0.4RC1
 Assigned To:        dmitry
 Block user comment: N
 Private report:     N

 New Comment:

Possible patch to avoid infinite loop: https://gist.github.com/nikic/072a365f6fe2f07e523757de0eb6c61f

It uses missing idom to identify blocks that are unreachable or only abnormally reachable and skips
the loop header assignment for them.

Of course, that doesn't fix the problem that computed results for try/catch may be incorrect,
as we're working on an inaccurate CFG.


Previous Comments:
------------------------------------------------------------------------
[2021-04-16 09:25:52] nikic@php.net

Though it would be great if we could not run loop identification in the first place if a function
has try/catch. In that case we don't perform SSA construction or register allocation, which
need this for correctness. It seems like the only places that use the information in that case is
timeout checks and hot loop counters. Maybe we just want to pessimize those in the presence of
try/catch?

------------------------------------------------------------------------
[2021-04-16 09:12:46] nikic@php.net

CFG for reference: https://gist.github.com/nikic/84d11192df4d7b6cc42f971c7d15920b

The core problem here is that BB5 and BB6 are not reachable from entry through normal edges (they
have domtree level 0 and will be processed last). They are reachable in the reverse graph though,
which is traversed when loop headers are assigned. We end up with BB3 having BB6 as loop header, and
BB6 having BB3 as loop header, causing an infinite loop.

There's probably two separate problems here: We should be treating unreachable code more
gracefully, and we should not be treating this code as unreachable in the first place, though
I'm not sure what the right way to handle unwind edges would be right now.

------------------------------------------------------------------------
[2021-04-16 08:23:48] nikic@php.net

Nice find! Here's a somewhat reduced version:

<?php
function test($a, $b) {
    echo "Start\n";
    do {
        $i++;
        try {
           continue;
        } catch (Exception $e) {
        }   
        do { 
           $j++;
        } while ($j < $b);
    } while ($i < $a);
    echo "Done\n";
}
test();

------------------------------------------------------------------------
[2021-04-15 22:34:42] zengyhkyle at asu dot edu

in our experiment, the execution is stuck at function zend_cfg_identify_loops

------------------------------------------------------------------------
[2021-04-15 22:32:33] zengyhkyle at asu dot edu

Description:
------------
if JIT compilation is enabled, PHP will get stuck in infinitely during loop identification when
building the cfg for some code.

the configuration we use is listed as follow:
~~~
opcache.jit_debug=263
opcache.enable_cli=1
opcache.jit=1205
opcache.jit_buffer_size=1G
zend_extension=/home/user/php-src-php-8.0.3/modules/opcache.so
~~~

This vulnerability is found by Yihui Zeng, Jayakrishna Menon, Steven Wirsz, and Gokul Krishna P from
Arizona State University for class CSE598 Applied Vulnerability Research

a poc is attached.

Test script:
---------------
<?php
$v0 = -815;
$v1 = True;
$v2 = $v0;
if($v1){
   $v3 = [5, 5]; 
   $v2 = $v3;
}else{
   $v4 = False;
   $v5 = $v0;
   if($v4){
      $v6 = $v4|$v0;
      $v5 = $v6;
   }else{
      $v5 = $v5 + 1;
      $v7 = $v5 + 1;
      $v5 = $v7;
   }   
   $v2 = $v5;
}
$v8 = 0;
$v9 = 4;
$v10 = $v8;
do{
   $v10 = $v10 + 1;
   $v11 = $v10 + 1;
   $v12 = $v10;
   try{
      continue;
      $v10 = $v12;
   }catch(Exception $e){
      $v2 = $v12;
   }   
   $v13 = False;
   $v14 = $v13;
   if($v13){
      $v15 = $v12/$v14;
      $v14 = $v15;
   }else{
      $v16 = 0;
      $v17 = 9;
      $v18 = $v16;
      do{ 
         $v18 = $v18 + 1;
         $v19 = $v18 + 1;
         $v19 = $v19 - 1;
         $v20 = $v19 - 1;
      }while($v18<$v17);
      $v21 = "kBBqSjjO63";
      $v21[0] = $v12;
      $v14 = $v21;
   }   
}while($v10<$v9);
$v22 = [3.055141489223973, 5.118032201755781, 1.6115282496633003, 2.796353888054253,
1.607762036181039, 5.52005061408927, 2.4609577104054896];
$v23 = [4, 1, 4, 4]; 

echo "Done";
?>



------------------------------------------------------------------------



--
Edit this bug report at https://bugs.php.net/bug.php?id=80959&edit=1


Thread (8 messages)

« previous php.bugs (#233467) next »