php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #80959 infinite loop in building cfg during JIT compilation
Submitted: 2021-04-15 22:32 UTC Modified: 2021-04-16 09:50 UTC
From: zengyhkyle at asu dot edu Assigned: dmitry (profile)
Status: Closed Package: JIT
PHP Version: 8.0.4RC1 OS: Linux
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: zengyhkyle at asu dot edu
New email:
PHP Version: OS:

 

 [2021-04-15 22:32 UTC] 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";
?>


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-04-15 22:34 UTC] zengyhkyle at asu dot edu
-PHP Version: 8.0.3 +PHP Version: 8.0.4RC1
 [2021-04-15 22:34 UTC] zengyhkyle at asu dot edu
in our experiment, the execution is stuck at function `zend_cfg_identify_loops`
 [2021-04-16 03:39 UTC] twosee@php.net
-Status: Open +Status: Verified
 [2021-04-16 08:23 UTC] 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-16 09:12 UTC] 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 09:25 UTC] 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:29 UTC] nikic@php.net
-Assigned To: +Assigned To: dmitry
 [2021-04-16 09:50 UTC] nikic@php.net
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.
 [2021-07-21 11:34 UTC] git@php.net
Automatic comment on behalf of dstogov
Revision: https://github.com/php/php-src/commit/a9991fbf281ae49c11780bc5bc43df4b1a080d37
Log: Fixed Bug #80959 (infinite loop in building cfg during JIT compilation)
 [2021-07-21 11:34 UTC] git@php.net
-Status: Verified +Status: Closed
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Sat Jul 24 05:01:23 2021 UTC