Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:124810 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by qa.php.net (Postfix) with ESMTPS id 5EE481A00B7 for ; Tue, 6 Aug 2024 18:52:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1722970430; bh=jdlAFTJVgWTl7fNFqQsemLkTxz7hAYirm15ECFskQPk=; h=Date:From:To:In-Reply-To:References:Subject:From; b=EZoJlw57ecew9OwZ6KRh88WOM+sVKft7i+wOTT2WGeDuPiO5w8jU797dRQ+H8EJdQ c1uQvPKORGZzYlFL0YcZbSdirW0XQCqzR50r4HyvlTgc+meMeTIjrZuvrQVO/5kyl1 48TNv1CGkJg8K65GQJ+ECBzR62EjxdHxuIJYqn2iQO8Np0Jwa1PqVExe5RLl0ASqo9 mAwTTbF0t+WFShrEMV7SBTUUXICCbYEe8Bjg+11B6VkweNSil7WMBqX70oVgiQLaXi wdF9ggRwipZF33jK/SG+8miP3M8T5/t5cxZ4Iy523/ZxOc4LpDufI3Z20UE7AizDQk ZsLHw81VEv8hA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 763DB18006D for ; Tue, 6 Aug 2024 18:53:49 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fout4-smtp.messagingengine.com (fout4-smtp.messagingengine.com [103.168.172.147]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Tue, 6 Aug 2024 18:53:49 +0000 (UTC) Received: from compute3.internal (compute3.nyi.internal [10.202.2.43]) by mailfout.nyi.internal (Postfix) with ESMTP id EDBCB138801A for ; Tue, 6 Aug 2024 14:52:06 -0400 (EDT) Received: from imap49 ([10.202.2.99]) by compute3.internal (MEProxy); Tue, 06 Aug 2024 14:52:06 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm1; t=1722970326; x=1723056726; bh=MsmYIEbGOL 41Ilz6h7lc8Z1BVEX/dZY07tyrLsfX698=; b=dKDDMngZIw/k3Th2q/2a5Gs9Nh 1oCGab794H28Cn+4YkOdya0bfDagkEeUIi9Cb4FikgwVWe/k48MjiG7VKijguAv8 Sz6MHfq8U3pQjoQhvAPLDX13G6ao322cUJf/k5Qz0AX+RcKoM1Cx+0q1XGZIuyOt PhJWi8tTMj0gIubnbviZUxZeJnNHIUl9xOE56w/iwdOZ0lTJfTk3lrrIPvFVkNTB Wh1NL4FBM7NRM6gXNUMVfuyCAmIyObfGePEfqYYTk8x71Fv3oHchEopBkD4bVSWL cQcpfRRkXIawcGHOMsp5qCXTwDgGUBy4CboHCwWnZdqx+Yon/PYu0XpYOSZw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm3; t=1722970326; x=1723056726; bh=MsmYIEbGOL41Ilz6h7lc8Z1BVEX/ dZY07tyrLsfX698=; b=EICO9jz4wWPG69WGwWbHBsrLE1Ejx9Rb1YCargHs84Vj RTok+E5jpn+SwWUc+ClUY9vxTbOVzwSYJm2TJqXVq2RqBJAbbsgxQw+uiItHeU4Y uMzX+uDeXfgAxgH6c4G+FtTFSQhpvlSevx5ReSZeQeuIPm9ElfvLJrRmCEJaY1OK GtQ936qvF96/BcqvIT8MVNPmVkI4M/+/74v6SHKEhVD8ugYa8c2ZbMrsGmmRQDW6 vsh89LaBC7ttviNWClJkZUg0so4cWSlQuDZ5R2oMRj/Ip+tE08SyAs92M1GPDgQf pT3YGGOxa+NClmTY56g7hOqDj61oUYOE1OLOwZNNVw== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeftddrkeekgddufedtucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucenucfjughrpefoggffhffvkfgjfhfutgesrgdtre erredtjeenucfhrhhomhepfdftohgsucfnrghnuggvrhhsfdcuoehrohgssegsohhtthhl vggurdgtohguvghsqeenucggtffrrghtthgvrhhnpedtueejtdethfeulefhtdeliedute elffdtudelheffgedtieehhfelieejgfevgeenucevlhhushhtvghrufhiiigvpedtnecu rfgrrhgrmhepmhgrihhlfhhrohhmpehrohgssegsohhtthhlvggurdgtohguvghspdhnsg gprhgtphhtthhopedt X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id 940C115A0092; Tue, 6 Aug 2024 14:52:06 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 Date: Tue, 06 Aug 2024 20:51:30 +0200 To: internals@lists.php.net Message-ID: <662030fe-b87f-4afd-94a3-6e9210bc785b@app.fastmail.com> In-Reply-To: <70ea158f8c37b9d3acfb7a6ab0f2e76adfb63cdd.camel@ageofdream.com> References: <32236410-e4ad-4c63-b7e7-cdbd3832bbc1@app.fastmail.com> <70ea158f8c37b9d3acfb7a6ab0f2e76adfb63cdd.camel@ageofdream.com> Subject: Re: [PHP-DEV] [Discussion] Sandbox API Content-Type: multipart/alternative; boundary=fed8542351eb425eb814a15014ae1e47 From: rob@bottled.codes ("Rob Landers") --fed8542351eb425eb814a15014ae1e47 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hey Nick, Looking forward to the RFC! On Tue, Aug 6, 2024, at 19:28, Nick Lockheart wrote: > >=20 > > This looks quite valuable, and I assume auto loading would work just > > like normal? Register an autoloader that will eventually require the > > file and call this function? > >=20 > > It would be nice to provide a simplified api as well, maybe > > =E2=80=9CCopyCurrentEnvironment()=E2=80=9D or something? In most ca= ses, it is > > easier/faster to find things to remove vs. adding everything on every > > plugin/request every time.=20 > >=20 > > In saying that, it would be great if there was an api for =E2=80=9Cs= haring=E2=80=9D a > > base-sandbox pool via shm (or similar to a pool) so that the base vm > > doesn=E2=80=99t need to be recreated potentially hundreds of times p= er > > request.=20 > >=20 >=20 > I didn't want to be too overwhelming on the first post, but since it > seems the feedback is positive, here's a more complete list of what I > think should be included: >=20 >=20 > // Passthroughs: >=20 > // Make all user and built-in global functions > // available inside the sandbox: > SPLSandBox::PassGlobalFunctions(); Bike shed: maybe have PassGlobals() instead? Or rather, PassNamespace an= d have \ be a valid namespace.=20 >=20 > // Make all built-in (but not user) functions > // available inside the sandbox: > SPLSandBox::PassBuiltInFunctions(); >=20 >=20 > // Make all built-in (but not user) functions > // available inside the sandbox, EXCEPT blacklisted functions: > SPLSandBox::PassBuiltInFunctionsExcept(['eval','exit']); >=20 > (assuming exit becomes a function). I feel like exit (function or not) should just return from the sandbox a= nd shouldn=E2=80=99t be disable-able. For example, a plugin might detect= a valid etag and set headers to 302 and exit.=20 >=20 >=20 > // Allow only specific functions to be called (whitelist method): > $aWhiteList =3D ['array_key_exists','in_array']; > SPLSandBox::PassFunctions($aWhiteList); Might be a good idea to combine these two? Allow passing a whitelist AND= a blacklist? Are these supposed to be static or on an instance of a san= dbox? >=20 > // Allow specific classes to be used by sandbox code: > $aClassList =3D ['\MyAPP\PluginAPI']; > SPLSandBox::PassClasses($aClassList); >=20 >=20 > // Allow specific constants to be seen by sandbox code: > SPLSandBox::PassConstants(['\DB_USERNAME','\DB_PASSWORD']); >=20 >=20 >=20 > // Language Construct Callbacks: >=20 > The callbacks allow the outer code to control and monitor certain > language features of the sandboxed code during execution. >=20 > // Called when the sandbox code tries to include or require something: > SPLSandBox::RegisterIncludeHandler(); Does including a file from outside the sandbox (next call) call this han= dler? >=20 > // Includes a file into the sandbox: > SPLSandBox::Include('path/to/file.php'); >=20 > // Your sandbox autoloader logic could be incorporated here: > SPLSandBox::RegisterAutoLoadHandler(); >=20 >=20 > // But, for unit testing with mocks and stubs, > // it might be better to use: > SPLSandBox::RegisterNewHandler(); >=20 > The NewHandler callback is called every time sandboxed code tries to > instantiate an object with `new`. Why not use the current autoload logic? > // Example: Override what `new` returns to code running in the sandbox: > function MyNewHandler(string $ClassName, array $aConstructorArgs){ >=20 > if($ClassName =3D=3D=3D '\DateTime'){ > return new FakeDate(); > } > return new $ClassName($aConstructorArgs); > } >=20 >=20 > // Every time a sandboxed class calls a method, call this first: > SPLSandBox::RegisterMethodCallHandler(); >=20 > Useful for unit testing to monitor if the tested class is calling the > methods it should be calling. Ignores visibility rules. >=20 > Could also allow for infinite recursion detection from the outside. I think this is handled automatically now.=20 >=20 >=20 > // The companion for static method calls, gets called > // every time a method is called on a class statically: > SPLSandBox::RegisterStaticMethodCallHandler(); >=20 >=20 >=20 > // Each time a sandboxed loop iterates, call this first: > // Allows the outer code to put limit breaks on the sandboxed code. > SPLSandBox::RegisterLoopHandler(); >=20 > The callback takes the type of loop, and the variables that make up the > loop ($i for for(), $Key =3D> $value for foreach(), etc) >=20 >=20 > // If the sandboxed code calls echo, print, or > // causes any output to occur (ie outside of SPLSandBox::RegisterEchoHandler(); >=20 > Could be used to make sure templates behave as desired, but perhaps > even more useful, it lets you *fail* unit tests if any output occurs > from a test that shouldn't produce output. >=20 > ie. Catch echo statements used in testing, or whitespace after a > closing ?> tag. >=20 >=20 > // If the sandbox code tries to use `exit` or `die`, > // call this function instead: > SPLSandBox::RegisterExitHandler(); >=20 > You'll probably want to destroy the sandbox from the outside (see > below), rather than letting sandboxed code halt the test framework or > main application. >=20 >=20 > // If sandboxed code throws, it should *not* > // be a throw in the outer application space. > // Every exception throw triggers this callback, > // even if there is a catch block: > SPLSandBox::RegisterExceptionHandler(); >=20 > // When a catch block runs, invoke this callback first: > SPLSandBox::RegisterCatchHandler(); >=20 > Allows unit tests to make sure that exceptions are handled correctly. >=20 >=20 > // For non-Exceptions (warning, notice, deprecated, fatal, > // and yes, maybe even parse because we're in a sandbox): > SPLSandBox::RegisterErrorHandler(); >=20 >=20 >=20 > // Inside your Handlers, you may want to know the file and line > // that triggered the callback.=20 > SPLSandBox::GetCurrentLine(); > SPLSandBox::GetFileName(); > SPLSandBox::GetClassName(); > SPLSandBox::GetFunctionName(); >=20 > For example, if you want to catch any echo left behind from debugging, > you might also want to find the line and file where the statement is > located to remove it. The above methods would be usable inside any of > the callbacks. >=20 >=20 > // Inside your Handlers, abort execution: > SPLSandBox::Stop(); >=20 >=20 > // Resource Limits, hopefully self explanatory: > SPLSandBox::SetMemoryLimit(); > SPLSandBox::SetExecutionTimeLimit(); >=20 >=20 > // Mocks, Stubs: >=20 > // Put a function from the outer application > // into the sandbox as the specified name: > SPLSandBox::MockFunction('\mocks\fopen','\fopen'); >=20 >=20 > // Put a class from the outer application > // into the sandbox as the specified name: > SPLSandBox::MockClass('\Mocks\FakeTime','\DateTime'); >=20 >=20 > // Set global variables (and super globals) inside the sandbox: > SPLSandBox::MockGlobal('$_GET',$aGetVars);=20 >=20 >=20 > // Set global constants inside the sandbox: > SPLSandBox::MockConstant('MY_CONSTANT',$Value);=20 >=20 >=20 >=20 > // Invocation >=20 > // You can run procedural code to setup your test environment. > // Runs the array of code lines in the sandbox context: > $aProceduralCode =3D [ > "$a =3D 1;", > "$b =3D 2;", > "$c =3D DoSomething($a, $b);" > ]; >=20 > SPLSandBox::Procedure($aProceduralCode); >=20 >=20 > // You can get a pointer to an object instantiated in the sandbox: > $oClass =3D SPLSandBox::GetInstance('ClassName'); >=20 > // And use it like you normally would: > $oClass->DoSomething(); >=20 > This class runs entirely in the sandbox. >=20 >=20 > // You cleanup the resource with: > SPLSandBox::Destroy(); >=20 Looks pretty exciting and useful! Since you=E2=80=99re imagining it bein= g a part of SPL, why not implement this in its own extension? It looks l= ike the pecl process is pretty convoluted to get an extension listed the= re, but many popular extensions live outside of pecl too. =E2=80=94 Rob --fed8542351eb425eb814a15014ae1e47 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
Hey Nick,

Looking forward to the RFC!

=
On Tue, Aug 6, 2024, at 19:28, Nick Lockheart wrote:

= > This looks quite valuable, and I assume auto loading would work jus= t
> like normal? Register an autoloader that will event= ually require the
> file and call this function?

> It would be nice to provide a simp= lified api as well, maybe
> =E2=80=9CCopyCurrentEnviron= ment()=E2=80=9D or something?  In most cases, it is
&= gt; easier/faster to find things to remove vs. adding everything on ever= y
> plugin/request every time. 
>=  
> In saying that, it would be great if there was= an api for =E2=80=9Csharing=E2=80=9D a
> base-sandbox = pool via shm (or similar to a pool) so that the base vm
&g= t; doesn=E2=80=99t need to be recreated potentially hundreds of times pe= r
> request. 


I didn't want to be too overwhelming on the first post= , but since it
seems the feedback is positive, here's a mo= re complete list of what I
think should be included:


// Passthroughs:
// Make all user and built-in global functions
// available inside the sandbox:
SPLSandBox::PassGlobalF= unctions();

Bike shed: maybe h= ave PassGlobals() instead? Or rather, PassNamespace and have \ be a vali= d namespace. 


// Make all built-in (but not user) fu= nctions
// available inside the sandbox:
SPL= SandBox::PassBuiltInFunctions();


=
// Make all built-in (but not user) functions
// avai= lable inside the sandbox, EXCEPT blacklisted functions:
SP= LSandBox::PassBuiltInFunctionsExcept(['eval','exit']);
(assuming exit becomes a function).

I feel like exit (function or not) should just return = from the sandbox and shouldn=E2=80=99t be disable-able. For example, a p= lugin might detect a valid etag and set headers to 302 and exit. 

<= br>

// Allow only specific functions to be call= ed (whitelist method):
$aWhiteList =3D ['array_key_exists'= ,'in_array'];
SPLSandBox::PassFunctions($aWhiteList);
<= /div>

Might be a good idea to combine th= ese two? Allow passing a whitelist AND a blacklist? Are these supposed t= o be static or on an instance of a sandbox?


// Allow spec= ific classes to be used by sandbox code:
$aClassList =3D [= '\MyAPP\PluginAPI'];
SPLSandBox::PassClasses($aClassList);=


// Allow specific constants= to be seen by sandbox code:
SPLSandBox::PassConstants(['\= DB_USERNAME','\DB_PASSWORD']);



// Language Construct Callbacks:

The callbacks allow the outer code to control and monitor certai= n
language features of the sandboxed code during execution= .

// Called when the sandbox code tries to = include or require something:
SPLSandBox::RegisterIncludeH= andler();

Does including a fil= e from outside the sandbox (next call) call this handler?

=

// Includes a file into the sandbox:
SPLSandBox::Include(= 'path/to/file.php');

// Your sandbox autolo= ader logic could be incorporated here:
SPLSandBox::Registe= rAutoLoadHandler();


// But, = for unit testing with mocks and stubs,
// it might be bett= er to use:
SPLSandBox::RegisterNewHandler();

The NewHandler callback is called every time sandboxed c= ode tries to
instantiate an object with `new`.

Why not use the current autoload logic?

/= / Example: Override what `new` returns to code running in the sandbox:
function MyNewHandler(string $ClassName, array $aConstructo= rArgs){

   if($ClassName =3D=3D=3D= '\DateTime'){
      return new F= akeDate();
   }
   retur= n new $ClassName($aConstructorArgs);
}

<= /div>

// Every time a sandboxed class calls a method,= call this first:
SPLSandBox::RegisterMethodCallHandler();=

Useful for unit testing to monitor if the = tested class is calling the
methods it should be calling. = Ignores visibility rules.

Could also allow = for infinite recursion detection from the outside.

I think this is handled automatically now. 


// The companion for static method calls, ge= ts called
// every time a method is called on a class stat= ically:
SPLSandBox::RegisterStaticMethodCallHandler();
=



// Each time a s= andboxed loop iterates, call this first:
// Allows the out= er code to put limit breaks on the sandboxed code.
SPLSand= Box::RegisterLoopHandler();

The callback ta= kes the type of loop, and the variables that make up the
l= oop ($i for for(), $Key =3D> $value for foreach(), etc)


// If the sandboxed code calls echo, prin= t, or
// causes any output to occur (ie outside of <?ph= p tags:
SPLSandBox::RegisterEchoHandler();
<= br>
Could be used to make sure templates behave as desired, bu= t perhaps
even more useful, it lets you *fail* unit tests = if any output occurs
from a test that shouldn't produce ou= tput.

ie. Catch echo statements used in tes= ting, or whitespace after a
closing ?> tag.


// If the sandbox code tries to use `ex= it` or `die`,
// call this function instead:
SPLSandBox::RegisterExitHandler();

You'll = probably want to destroy the sandbox from the outside (see
below), rather than letting sandboxed code halt the test framework or
main application.


// If sandboxed code throws, it should *not*
// be a th= row in the outer application space.
// Every exception thr= ow triggers this callback,
// even if there is a catch blo= ck:
SPLSandBox::RegisterExceptionHandler();
=
// When a catch block runs, invoke this callback first:
SPLSandBox::RegisterCatchHandler();

Allows unit tests to make sure that exceptions are handled correct= ly.


// For non-Exceptions (w= arning, notice, deprecated, fatal,
// and yes, maybe even = parse because we're in a sandbox):
SPLSandBox::RegisterErr= orHandler();



= // Inside your Handlers, you may want to know the file and line
// that triggered the callback. 
SPLSandBox::Ge= tCurrentLine();
SPLSandBox::GetFileName();
S= PLSandBox::GetClassName();
SPLSandBox::GetFunctionName();<= br>

For example, if you want to catch any echo = left behind from debugging,
you might also want to find th= e line and file where the statement is
located to remove i= t. The above methods would be usable inside any of
the cal= lbacks.


// Inside your Handl= ers, abort execution:
SPLSandBox::Stop();



// Resource Limits, hopefully self explan= atory:
SPLSandBox::SetMemoryLimit();
SPLSand= Box::SetExecutionTimeLimit();


// Mocks, Stubs:

// Put a function from t= he outer application
// into the sandbox as the specified = name:
SPLSandBox::MockFunction('\mocks\fopen','\fopen');


// Put a class from the outer= application
// into the sandbox as the specified name:
SPLSandBox::MockClass('\Mocks\FakeTime','\DateTime');


// Set global variables (and super= globals) inside the sandbox:
SPLSandBox::MockGlobal('$_GE= T',$aGetVars); 


// Set = global constants inside the sandbox:
SPLSandBox::MockConst= ant('MY_CONSTANT',$Value); 


=

// Invocation

// You ca= n run procedural code to setup your test environment.
// R= uns the array of code lines in the sandbox context:
$aProc= eduralCode =3D [
   "$a =3D 1;",
&= nbsp;  "$b =3D 2;",
   "$c =3D DoSomething(= $a, $b);"
];

SPLSandBox::Proc= edure($aProceduralCode);


// = You can get a pointer to an object instantiated in the sandbox:
$oClass =3D SPLSandBox::GetInstance('ClassName');
// And use it like you normally would:
$oClas= s->DoSomething();

This class runs entire= ly in the sandbox.


// You cl= eanup the resource with:
SPLSandBox::Destroy();
<= div>

Looks pretty exciting and= useful! Since you=E2=80=99re imagining it being a part of SPL, why not = implement this in its own extension? It looks like the pecl process is p= retty convoluted to get an extension listed there, but many popular exte= nsions live outside of pecl too.

=E2=80=94 Rob
--fed8542351eb425eb814a15014ae1e47--