Can I catch exit() and die() messages? - php

I'd like to be able to catch die() and exit() messages. Is this possible? I'm hoping for something similar to set_error_handler and set_exception_handler. I've looked at register_shutdown_function() but it seems to contain no context for the offending die() and exit() calls.
I realize that die() and exit() are bad ways to handle errors. I am not looking to be told not to do this. :) I am creating a generic system and want to be able to gracefully log exit() and die() if for some reason someone (not me) decides this is a good idea to do.

Yes you can, but you need ob_start, ob_get_contents, ob_end_clean and register_shutdown_function
function onDie(){
$message = ob_get_contents(); // Capture 'Doh'
ob_end_clean(); // Cleans output buffer
callWhateverYouWant();
}
register_shutdown_function('onDie');
//...
ob_start(); // You need this to turn on output buffering before using die/exit
#$dumbVar = 1000/0 or die('Doh'); // "#" prevent warning/error from php
//...
ob_end_clean(); // Remember clean your buffer before you need to use echo/print

According to the PHP manual, shutdown functions should still be notified when die() or exit() is called.
Shutdown functions and object destructors will always be executed even if exit() is called.
It doesn't seem to be possible to get the status sent in exit($status). Unless you can use output buffering to capture it, but I'm not sure how you'd know when to call ob_start().

Maybe override_function() could be interesting, if APD is available

As best as I can tell this is not really possible. Some of the solutions posted here may work but they require a lot of additional work or many dependencies. There is no way to easily and reliable trap the die() and exit() messages.

Why do not use custom error handling instead? If not, you could always use LD_PRELOAD and C Code injection to catch it :) Or recompile php with your customizations :P

If you use the single point of entry method. (index.php) I can recommend this for your error handling:
Short version:
ob_start();
register_shutdown_function('shutdownHandler');
include('something');
define(CLEAN_EXIT, true);
function shutdownHandler() {
if(!defined("CLEAN_EXIT") || !CLEAN_EXIT) {
$msg = "Script stopped unexpectedly: ".ob_get_contents();
//Handle premature die()/exit() here
}
}
Additional steps and more detailed:
Roughly my way of doing it. I have even more going on than I show here (handling database transactions/rollback/sending e-mails/writing logs/displaying friendly error messages/user error reporting/etc), but this is the basic idea behind all of it).
Hope it helps someone.
<?php
//Some initialization
//starting output buffering. (fatalErrorHandler is optional, but I recommend using it)
ob_start('fatalErrorHandler');
//Execute code right at the end. Catch exit() and die() here. But also all other terminations inside PHPs control
register_shutdown_function('shutdownHandler');
//handling other errors: Also optional
set_error_handler('errorHandler');
try {
//Include of offensive code
include(...);
}
catch (Exception $ex) {
//Handling exception. Be careful to not raise exceptions here again. As you can end up in a cycle.
}
//Code reached this point, so it was a clean exit.
define(CLEAN_EXIT, true);
//Gets called when the script engine shuts down.
function shutdownHandler() {
$status = connection_status();
$statusText = "";
switch ($status) {
case 0:
if (!defined("CLEAN_EXIT") || !CLEAN_EXIT) {
$msg = "Script stopped unexpectedly: ".ob_get_contents();
//Handle premature die()/exit() here
}
else {
//Clean exit. Just return
return;
}
case 1: $statusText = "ABORTED (1)"; break;
case 2: $statusText = "TIMEOUT (2)"; break;
case 3: $statusText = "ABORTED & TIMEOUT (3)"; break;
default : $statusText = "UNKNOWN ($status)"; break;
}
//Handle other exit variants saved in $statusText here ob_get_contents() can have additional useful information here
}
// error handler function (This is optional in your case)
function errorHandler($errno, $errstr, $errfile, $errline) {
$msg = "[$errno] $errstr\nOn line $errline in file $errfile";
switch ($errno) {
case E_ERROR: $msg = "[E_ERROR] ".$msg; break;
case E_WARNING: $msg = "[E_WARNING] ".$msg; break;
case E_PARSE: $msg = "[E_PARSE] ".$msg; break;
case E_NOTICE: $msg = "[E_NOTICE] ".$msg; break;
case E_CORE_ERROR: $msg = "[E_CORE_ERROR] ".$msg; break;
case E_CORE_WARNING: $msg = "[E_CORE_WARNING] ".$msg; break;
case E_COMPILE_ERROR: $msg = "[E_COMPILE_ERROR] ".$msg; break;
case E_COMPILE_WARNING: $msg = "[E_COMPILE_WARNING] ".$msg; break;
case E_USER_ERROR: $msg = "[E_USER_ERROR] ".$msg; break;
case E_USER_WARNING: $msg = "[E_USER_WARNING] ".$msg; break;
case E_USER_NOTICE: $msg = "[E_USER_NOTICE] ".$msg; break;
case E_STRICT: $msg = "[E_STRICT] ".$msg; break;
case E_RECOVERABLE_ERROR: $msg = "[E_RECOVERABLE_ERROR] ".$msg; break;
case E_DEPRECATED: $msg = "[E_DEPRECIATED] ".$msg; break;
case E_USER_DEPRICIATED: $msg = "[E_USER_DEPRICIATED] ".$msg; break;
default: $msg = "[UNKNOWN] ".$msg; break;
}
//Handle Normal error/notice/warning here.
$handled = ...
if ($handled)
return true; //handled. Proceed execution
else
throw Exception($msg); //Be careful. this might quickly become cyclic. Be sure to have code that catches and handles exceptions. Else die() here after logging/reporting the error.
}
function fatalErrorHandler(&$buffer) {
$matches = null;
//Checking if the output contains a fatal error
if (preg_match('/<br \/>\s*<b>([^<>].*)error<\/b>:(.*)<br \/>$/', $buffer, $matches) ) {
$msg = preg_replace('/<.*?>/','',$matches[2]);
//Handle Fatal error here
return "There was an unexpected situation that resulted in an error. We have been informed and will look into it."
}
//No fatal exception. Return buffer and continue
return $buffer;
}

Catching exits is useful in automated tests. The way I do it is I throw a special runtime exception instead of calling exit directly.
<?php
class CatchableExit extends RuntimeException
{
}
class Quitter
{
public function run($i)
{
echo "Quitter called with \$i = $i \n";
throw new CatchableExit();
}
}
class Runner
{
public function run()
{
trigger_error('I am a harmless warning', E_USER_WARNING);
trigger_error('And I am a notice', E_USER_NOTICE);
for ($i = 0; $i < 10; $i++) {
$q = new Quitter();
try {
$q->run($i);
} catch (CatchableExit $e) {
}
}
}
}
function exception_handler(Throwable $exception)
{
if ($exception instanceof CatchableExit) {
exit();
}
}
set_exception_handler('exception_handler');
$runner = new Runner();
$runner->run();

yes: write a function and use that instead.
function kill($msg){
// Do your logging..
exit($msg);
}

Related

PHP eval code sandbox break

I noticed a heavily downvoted comment in here: http://php.net/manual/en/function.php-check-syntax.php
function eval_syntax($code)
{
$braces = 0;
$inString = 0;
// We need to know if braces are correctly balanced.
// This is not trivial due to variable interpolation
// which occurs in heredoc, backticked and double quoted strings
foreach (token_get_all('<?php ' . $code) as $token)
{
if (is_array($token))
{
switch ($token[0])
{
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES:
case T_START_HEREDOC: ++$inString; break;
case T_END_HEREDOC: --$inString; break;
}
}
else if ($inString & 1)
{
switch ($token)
{
case '`':
case '"': --$inString; break;
}
}
else
{
switch ($token)
{
case '`':
case '"': ++$inString; break;
case '{': ++$braces; break;
case '}':
if ($inString) --$inString;
else
{
--$braces;
if ($braces < 0) return false;
}
break;
}
}
}
// If $braces is not zero, then we are sure that $code is broken.
// We run it anyway in order to catch the error message and line number.
// Else, if $braces are correctly balanced, then we can safely put
// $code in a dead code sandbox to prevent its execution.
// Note that without this sandbox, a function or class declaration inside
// $code could throw a "Cannot redeclare" fatal error.
echo "Braces: ".$braces."\r\n";
$braces || $code = "if(0){{$code}\n}";
if (false === eval($code)) {}
}
eval_syntax("file_put_contents('/home/yourname/Desktop/done.txt', 'OVERWRITTEN');");
I tried to bypass the code to maliciously execute user-input, but I couldn't. I wonder why it got downvoted.
As you can see if curly brackets are not matching, it doesn't add the 'if(0){' . $code . '} and executes the user input with mismatching curly brackets which will throw exception and won't really run.
If curly brackets are a match, it calls the eval, but its inside a if {0} "sandbox". How can someone bypass this?
I know eval is insecure, but I want to know what's the trick here. How can you bypass security of if (0) and braces check in the code above?
You can try directly the code from php.net or my minified/edited version above. Point is proving that this code is not secure and user an execute arbitrary PHP Code

PHP Notice with page URL

I have received many PHP Notice in log, but i want to know what page URL where happened Notice, how can i log this info?
[29-Nov-2012 13:58:29] PHP Notice: Array to string conversion in /usr/home/sdf/data/www/sdfsdf.com/core/test.php on line 156
I want to log any info, when get NOTICE, how to log it?
Try to correlate the timestamps in the php error log with your web server access log.
Or, you could tail -f the log and trigger random pages.
Or, set a custom error handler to log all sorts of data.
http://php.net/manual/en/function.set-error-handler.php
function myErrorHandler( $errno, $errstr, $errfile, $errline ){
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting
return;
}
switch ($errno) {
case E_USER_ERROR:
echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
echo " Fatal error on line $errline in file $errfile";
echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
echo "Aborting...<br />\n";
exit(1);
break;
case E_USER_WARNING:
echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
break;
case E_USER_NOTICE:
echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
break;
default:
echo "Unknown error type: [$errno] $errstr<br />\n";
break;
}
/* Don't execute PHP internal error handler */
return true;
}
set_error_handler( "myErrorHandler" );
What you could do is to compare you php notice times to your webservers access log in order to find out the URL which caused that notice.
The notice actually does not look like URL dependent.
It indicates, that you're trying to cast a "type" array to a string which happens when you do something like this:
$array = array( 'foo', 'bar' );
echo $array;
$string = 'test' . $array;
printf ( 'foo %s', $array );
To find this error in particular, you could do this to figure out what's wrong (including printing a backtrace using debug_backtrace):
if (is_string($var)) {
//Then it's OK - do whatever you were doing on line 156
} else {
//Something's wrong! Let's log it to some file!
ob_start();
var_dump($var);
print_r(debug_backtrace());
echo "\n----------------------------------------------\n";
$debugContent = ob_get_clean();
$logHandle = fopen("wrong_var_type.log", "a");
if ($logHandle !== false) {
fwrite($logHandle, $debugContent . "\n");
}
#fclose($logHandle);
}
Alternatively, use a logger:
https://stackoverflow.com/a/10800296/247893
You need to set up something to capture error/debug info as it happens, but not show it to the entire world. Not sure if you have session management built in to this application, but you may want to add some basic controls for this exercise.
Sometimes errors are harder to track down if you use functions that are included on multiple pages. The error may appear to happen on the parent page, but is actually triggered in the function, which is included on another page. The error line numbers can be misleading in this case.
If you have intermittent errors that you aren't able to immediately isolate, it may help to get some feedback on what's happening in your script(s). Here's a rough example of how to do some basic debugging in functions:
function get_func_argNames($funcName)
{
$f = new ReflectionFunction($funcName);
$result = array();
foreach ($f->getParameters() as $param)
{
$result[] = $param->name;
}
return $result;
}
function myCoolFunction($arg1, $arg2, $arg3)
{
$debug = false;
$php_function_args = implode(', ',get_func_argNames(__FUNCTION__));
$_debug_txt = "<b><span style='color:blue;'>function</span> <span style='color:darkblue;'>" .__FUNCTION__. "</span></b>($php_function_args)";
if ($debug)
{
EmailAppDev($_debug_txt);
}
// myCoolFunction
$x = $arg1 + $arg2 + $arg3;
return $x
}
Ideally you'll have a session account that can control who $debug is enabled for.
If you aren't using functions, you'll need to set up something similar in strategic areas of your scripts, to find out when and where things are going sour.
Without having your entire app to look at it's pretty hard to give specifics.

PHP custom error handler log to output

I have a script that will run on server daily to download data from a resource with no HTML output. By default PHP will output my script errors to php_error.log and to the output window in NetBeans.
I have created a user error handler:
// user defined error handling function
function userErrorHandler($errno, $errmsg, $filename, $linenum, $vars)
{
error_log($errmsg, 3, "logs/aem.log");
switch ($errno) {
case E_ERROR:
exit("Error grave $errmsg at $filename:$linenum");
break;
case E_USER_ERROR:
exit("Error grave $errmsg at $filename:$linenum");
break;
}
}
error_reporting(0);
set_error_handler('userErrorHandler');
I include this file in my main script and everything goes fine.
While I develop the app I would like to continue seeing the error messages on Output screen in Netbeans as well as keeping the error log files (as the default handler does). I have tried changing the value of error_reporting or adding additional error_log functions trying the following values:
error_reporting(E_ALL | E_STRICT);
error_reporting(-1);
error_log($errmsg, 0);`
But I never get the error on the output unless I remove include('mycustomhandler'); from the file.
How can I emulate the behavior of the standard error handler?
You should try to use output to stderr (I guess netbeans parses it). For example:
fprintf( STDERR, "Normal error message %s\n", "With nested info");
For logging errors you may use error_log() (maybe will handle output to netbeans too) or try to parse error_log from php.ini (ini_get()).
However I guess php uses internaly syslog(). So your error handler should look like:
// Logging
function userErrorHandler($errno, $errmsg, $filename, $linenum, $vars){
$logLevel = 0;
$label = '';
$exit = false;
switch( $errno){
case E_ERROR:
case E_USER_ERROR:
$label = 'Critical error';
$exit = true;
$logLevel = LOG_ERR;
break;
// ...
}
$message = "$label: on line ... file, error string...";
if( $logLevel){
syslog( $logLevel, $message);
}
if( !ini_get( 'display_error') && $exit){
die( 'Fatal error occured.');
}
if( $label){
echo $message; // You're responsible for how error is displayed
}
if( $exit){
die();
}
}
error_reporting( E_ALL);
Ask questions in comments if I didn't hit what you were asking

php - How to catch an unexpected error?

I'm writing a script, where a lot of things could go wrong. I'm making if/else statements for the obvious things, that could heppen, but is there a way to catch something, that could possible heppen, but I don't know what it is yet?
For example something causes an error of some kind, in the middle of the script. I want to inform the user, that something has gone wrong, but without dozens of php warning scripts.
I would need something like
-- start listening && stop error reporting --
the script
-- end listening --
if(something went wrong)
$alert = 'Oops, something went wrong.';
else
$confirm = 'Everything is fine.'
Thanks.
Why not try...catch?
$has_errors = false;
try {
// code here
} catch (exception $e) {
// handle exception, or save it for later
$has_errors = true;
}
if ($has_errors!==false)
print 'This did not work';
Edit:
Here is a sample for set_error_handler, which will take care of any error that happens outside the context of a try...catch block. This will also handle notices, if PHP is configured to show notices.
based on code from: http://php.net/manual/en/function.set-error-handler.php
set_error_handler('genericErrorHandler');
function genericErrorHandler($errno, $errstr, $errfile, $errline) {
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting
return;
}
switch ($errno) {
case E_USER_ERROR:
echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
echo " Fatal error on line $errline in file $errfile";
echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
echo "Aborting...<br />\n";
exit(1);
break;
case E_USER_WARNING:
echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
break;
case E_USER_NOTICE:
echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
break;
default:
echo "Unknown error type: [$errno] $errstr<br />\n";
break;
}
/* Don't execute PHP internal error handler */
return true;
}
$v = 10 / 0 ;
die('here');
Read up on Exceptions:
try {
// a bunch of stuff
// more stuff
// some more stuff
} catch (Exception $e) {
// something went wrong
}
throw new Exception('Division by zero.');
try {
echo inverse(5) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
http://php.net/manual/en/language.exceptions.php
You should definitely use the try-catch syntax to catch any exception thrown by your script.
Additionally you can extend exceptions and implement new ones that fulfill your needs.This way, you can throw your own exceptions when you find any other kind of unexpected error (error for your script's logic).
A very short example explaining the use of extending exceptions :
//your own exception class
class limitExceededException extends Exception { ... }
try{
// your script here
if($limit > 10)
throw new limitExceededException();
}catch(limitExceededException $e){//catching only your limit exceeded exception
echo "limit exceeded! cause : ".$e->getMessage();
}catch(Exception $e){//catching all other exceptions
echo "unidentified exception : ".$e->getMessage();
}
Besides using try/catch, I think it's important to consider if you should catch an unexpected error. If it's unexpected then your code has no idea how to handle it and allowing the application to continue may produce bad data or other incorrect results. It may be better to just let it crash to an error page. I just recently had a problem where someone had added generic exception handlers to everything, and it hid the original location of the exception making the bug difficult to find.

PHP eval and capturing errors (as much as possible)

Disclaimer; I'm fully aware of the pitfalls and "evils" of eval, including but not limited to: performance issues, security, portability etc.
The problem
Reading the PHP manual on eval...
eval() returns NULL unless return is
called in the evaluated code, in which
case the value passed to return is
returned. If there is a parse error in
the evaluated code, eval() returns
FALSE and execution of the following
code continues normally. It is not
possible to catch a parse error in
eval() using set_error_handler().
In short, no error capture except returning false which is very helpful, but I'm sur eI could do way better!
The reason
A part of the site's functionality I'm working on relies on executing expressions. I'd like not to pass through the path of sandbox or execution modules, so I've ended using eval. Before you shout "what if the client turned bad?!" know that the client is pretty much trusted; he wouldn't want to break his own site, and anyone getting access to this functionality pretty much owns the server, regardless of eval.
The client knows about expressions like in Excel, and it isn't a problem explaining the little differences, however, having some form of warning is pretty much standard functionality.
This is what I have so far:
define('CR',chr(13));
define('LF',chr(10));
function test($cond=''){
$cond=trim($cond);
if($cond=='')return 'Success (condition was empty).'; $result=false;
$cond='$result = '.str_replace(array(CR,LF),' ',$cond).';';
try {
$success=eval($cond);
if($success===false)return 'Error: could not run expression.';
return 'Success (condition return '.($result?'true':'false').').';
}catch(Exception $e){
return 'Error: exception '.get_class($e).', '.$e->getMessage().'.';
}
}
Notes
The function returns a message string in any event
The code expression should be a single-line piece of PHP, without PHP tags and without an ending semicolon
New lines are converted to spaces
A variable is added to contain the result (expression should return either true or false, and in order not to conflict with eval's return, a temp variable is used.)
So, what would you add to further aide the user? Is there any further parsing functions which might better pinpoint possible errors/issues?
Chris.
Since PHP 7 eval() will generate a ParseError exception for syntax errors:
try {
$result = eval($code);
} catch (ParseError $e) {
// Report error somehow
}
In PHP 5 eval() will generate a parse error, which is special-cased to not abort execution (as parse errors would usually do). However, it also cannot be caught through an error handler. A possibility is to catch the printed error message, assuming that display_errors=1:
ob_start();
$result = eval($code);
if ('' !== $error = ob_get_clean()) {
// Report error somehow
}
I've found a good alternative/answer to my question.
First of, let me start by saying that nikic's suggestion works when I set error_reporting(E_ALL); notices are shown in PHP output, and thanks to OB, they can be captured.
Next, I've found this very useful code:
/**
* Check the syntax of some PHP code.
* #param string $code PHP code to check.
* #return boolean|array If false, then check was successful, otherwise an array(message,line) of errors is returned.
*/
function php_syntax_error($code){
if(!defined("CR"))
define("CR","\r");
if(!defined("LF"))
define("LF","\n") ;
if(!defined("CRLF"))
define("CRLF","\r\n") ;
$braces=0;
$inString=0;
foreach (token_get_all('<?php ' . $code) as $token) {
if (is_array($token)) {
switch ($token[0]) {
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES:
case T_START_HEREDOC: ++$inString; break;
case T_END_HEREDOC: --$inString; break;
}
} else if ($inString & 1) {
switch ($token) {
case '`': case '\'':
case '"': --$inString; break;
}
} else {
switch ($token) {
case '`': case '\'':
case '"': ++$inString; break;
case '{': ++$braces; break;
case '}':
if ($inString) {
--$inString;
} else {
--$braces;
if ($braces < 0) break 2;
}
break;
}
}
}
$inString = #ini_set('log_errors', false);
$token = #ini_set('display_errors', true);
ob_start();
$code = substr($code, strlen('<?php '));
$braces || $code = "if(0){{$code}\n}";
if (eval($code) === false) {
if ($braces) {
$braces = PHP_INT_MAX;
} else {
false !== strpos($code,CR) && $code = strtr(str_replace(CRLF,LF,$code),CR,LF);
$braces = substr_count($code,LF);
}
$code = ob_get_clean();
$code = strip_tags($code);
if (preg_match("'syntax error, (.+) in .+ on line (\d+)$'s", $code, $code)) {
$code[2] = (int) $code[2];
$code = $code[2] <= $braces
? array($code[1], $code[2])
: array('unexpected $end' . substr($code[1], 14), $braces);
} else $code = array('syntax error', 0);
} else {
ob_end_clean();
$code = false;
}
#ini_set('display_errors', $token);
#ini_set('log_errors', $inString);
return $code;
}
Seems it easily does exactly what I need (yay)!
How to test for parse errors inside eval():
$result = #eval($evalcode . "; return true;");
If $result == false, $evalcode has a parse error and does not execute the 'return true' part. Obviously $evalcode must not return itself something, but with this trick you can test for parse errors in expressions effectively...
Good news: As of PHP 7, eval() now* throws a ParseError exception if the evaluated code is invalid:
try
{
eval("Oops :-o");
}
catch (ParseError $err)
{
echo "YAY! ERROR CAPTURED: $err";
}
* Well, for quite a while then... ;)
I think that best solution is
try {
eval(/* ... */);
} catch (Throwable $t) {
//...
}
It catches every error and exception, including Call to undefined function etc
You can also try something like this:
$filePath = '/tmp/tmp_eval'.mt_rand();
file_put_contents($filePath, $evalCode);
register_shutdown_function('unlink', $filePath);
require($filePath);
So any errors in $evalCode will be handled by errors handler.

Categories