Open dzentota opened 4 years ago
As I know, is identical implemented directly as an opcode handler and it doesn't call object handlers at all.
But you can install an opcode handler for OpCode::IS_IDENTICAL
code via OpCode->setHandler()
to handle comparison manually. But this handler could be changed soon to general hook scheme.
Thank you for quick reply. As I understood:
\ZEngine\System\OpCode::setHandler(\ZEngine\System\OpCode::IS_IDENTICAL,
function (\ZEngine\System\ExecutionData $executionState): int {
echo 'is_identical';
});
$o1 === $o2;
it should echo is_identical, but I see nothing;
Actually what I want to archive - I want to do taint analysis based on your lib. So I need a wrapper class (ValueObject) which can behave like the value it wraps in most cases and in the same time give the ability to call some methods on it. POC:
<?php
use ZEngine\ClassExtension\ObjectCastInterface;
use ZEngine\ClassExtension\ObjectCompareValuesInterface;
use ZEngine\ClassExtension\ObjectCreateInterface;
use ZEngine\ClassExtension\ObjectCreateTrait;
use ZEngine\ClassExtension\ObjectDoOperationInterface;
class Tainted implements
ObjectCastInterface,
ObjectCreateInterface,
ObjectCompareValuesInterface,
ObjectDoOperationInterface
{
use ObjectCreateTrait;
private $value;
private $isSafe = false;
public function __construct($value)
{
$this->value = $value;
}
public function value()
{
return $this->value;
}
public function markSafe()
{
$this->isSafe = true;
}
public function markUnsafe()
{
$this->isSafe = false;
}
public function isSafe(): bool
{
return $this->isSafe;
}
/**
* Performs comparison of given object with another value
*
* @param \ZEngine\ClassExtension\Hook\CompareValuesHook $hook Instance of current hook
*
* @return int Result of comparison: 1 is greater, -1 is less, 0 is equal
*/
public static function __compare(\ZEngine\ClassExtension\Hook\CompareValuesHook $hook): int
{
$first = $hook->getFirst();
$second = $hook->getSecond();
if (is_object($first) && get_class($first) === __CLASS__) {
$first = $first->value();
}
if (is_object($second) && get_class($second) === __CLASS__) {
$second = $second->value();
}
return $first <=> $second;
}
/**
* Performs an operation on given object
*
* @param \ZEngine\ClassExtension\Hook\DoOperationHook $hook Instance of current hook
*
* @return mixed Result of operation value
*/
public static function __doOperation(\ZEngine\ClassExtension\Hook\DoOperationHook $hook)
{
$first = $hook->getFirst();
$second = $hook->getSecond();
if (is_object($first) && get_class($first) === __CLASS__) {
$first = $first->value();
}
if (is_object($second) && get_class($second) === __CLASS__) {
$second = $second->value();
}
$opcode = $hook->getOpcode();
switch (true) {
case $opcode === \ZEngine\System\OpCode::ADD;
return new Tainted($first + $second);
case $opcode === \ZEngine\System\OpCode::SUB:
return new Tainted($first - $second);
case $opcode === \ZEngine\System\OpCode::CONCAT:
return new Tainted($first . $second);
case $opcode === \ZEngine\System\OpCode::IS_IDENTICAL:
return $first === $second;
// Here should be handling for all other opcodes...
}
}
/**
* Performs casting of given object to another value
*
* @param \ZEngine\ClassExtension\Hook\CastObjectHook $hook Instance of current hook
*
* @return mixed Casted value
*/
public static function __cast(\ZEngine\ClassExtension\Hook\CastObjectHook $hook)
{
return $hook->getObject()->value();
}
}
I expect next behavior:
$foo = new Tainted('foo');
$bar = new Tainted('bar');
$one = new Tainted(1);
$two = new Tainted(2);
echo $foo . $bar; //foobar
$one === 1; //true
$one == 1; //true
$two > $one;//true
// also instance of Tainted should pass type hints
function add(int $a, int $b) {
return $a + $b;
}
echo add($one, $two); //3
//And another key feature: ability to know function name in which tainted variable was passed
$xss = '<script>alert(1)</script>';
if (rand(0,1) {
//xss mitigation. **Tainted** should be some how notified in case it was to htmlspecialchars()
$xss = htmlspecialchars($xss);
}
var_dump($xss->isSafe());
Having such class I think it should be possible to wrap any variable at the beginning of the script and check if it safe at the end.
I'm not sure if your idea is possible right now, because messing with typehints and value boxing/unboxing in runtime is unpredictable. Of course, it is possible to wrap everything during AST processing, but object will violate your int
typehint anyway.
Regarding your question about opcode handler: you should install it before the first inclusion of file that contains operations itself. PHP checks if specific opcode has user handler and generates specific version of opcodes to invoke custom handler. If you try to install handler after that - nothing will work...
Thank you. Yes, it triggers handler if I put it in a separate file. Could provide a little example of how two write such handlers. It's not clear how to access $arguments and which integer should be returned. e.g. if I want to return true
if my objects are identical:
$o1 === $o2;
\ZEngine\System\OpCode::setHandler(\ZEngine\System\OpCode::IS_IDENTICAL,
function (\ZEngine\System\ExecutionData $executionState): int {
//$executionState->getArguments() returns empty array. $o1 and $o2 expected
return Core::ZEND_USER_OPCODE_DISPATCH;// What should be returned here?
});
How to intercept
is identical
(===) check ? e.g.will trigger __compare() handler, while
doesn't trigger neither compare() nor doOperation() with \ZEngine\System\OpCode::IS_IDENTICAL;