Make WordPress Core

Opened 5 years ago

Last modified 5 weeks ago

#48693 accepted defect (bug)

Silence ini_set caused error or check if function_exists first

Reported by: drazon's profile drazon Owned by: sergeybiryukov's profile SergeyBiryukov
Milestone: Future Release Priority: normal
Severity: normal Version: 5.3
Component: Bootstrap/Load Keywords: needs-testing has-patch
Focuses: Cc:

Description

https://core.trac.wordpress.org/browser/tags/5.3/src/wp-includes/load.php#L352

should be either

<?php
@ini_set( 'display_errors', 0 );

to silence the error

or

<?php
if ( function_exists( 'ini_set' ) ) {
ini_set( 'display_errors', 0 );
}

for the same reason as https://core.trac.wordpress.org/browser/tags/5.3/src/wp-admin/includes/class-wp-debug-data.php#L627

The same login could be applied to other ini_set lines in the wp_debug_mode() function

Attachments (1)

48693.diff (1.4 KB) - added by AkSDvP 5 years ago.
Added a condition check to check whether if "ini_set" core function is exists, then only include "ini_set" statement. Or else suppress the errors.

Download all attachments as: .zip

Change History (26)

#1 @drazon
5 years ago

  • Keywords reporter-feedback added; dev-feedback removed

#2 @drazon
5 years ago

  • Keywords reporter-feedback removed

#3 @SergeyBiryukov
5 years ago

  • Component changed from General to Bootstrap/Load

Hi there, welcome to WordPress Trac! Thanks for the report.

Related: [45611/trunk/src/wp-includes/load.php], which removed error suppression for ini_set() here.

Also related: [29330] / #26772, #33112, #37680.

Could you provide some more details about the environment and issue you're seeing? Is ini_set() disabled via PHP's disable_functions setting or in some other way?

#4 @drazon
5 years ago

thank you for your reply, I can see the error suppression removal. Maybe the if ( function_exists( 'ini_set' ) ) is a more elegant way at this point as it is being in other cases in the core.

Yes ini_set() is disabled at the php.ini disable_functions. I use PHP 7.3. The problem is that in production environments where

disable_functions = "ini_set, ....."
display_errors = 0
WP_DEBUG = false


the PHP error_log gets flooded with unnecessary warnings.

#6 @autotutorial
5 years ago

I'll answer you here and not in your other topic, add_filter or apply_filter is not available in the context of wp-config.php.
I customized the debug based on my preferences, if ini_set works set WP_DEBUG_DISPLAY, default to false, check reserved words in WP_DEBUG_LOG and react.
Create a folder in /wp-content/plugins/testsuper/
add a testsuper.php file to this folder
the plugin name is located at the beginning of the php file.
See if you solve it?

<?php
/**
 * Plugin Name: testsuper
 * Plugin URI: http://www.mywebsite.com/testsuper
 * Description: The very first plugin that I have ever created.
 * Version: 1.0
 * Author: Your Name
 * Author URI: http://www.mywebsite.com
 */
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
add_filter( 'enable_wp_debug_mode_checks', '__return_false' );
if ( WP_DEBUG ) {
error_reporting( E_ALL );
$display_conf = 0;
  if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY == true )
    $display_conf = 1;
  if( is_string( WP_DEBUG_LOG ) && strlen(WP_DEBUG_LOG) > 5 ) {
    $log_path = WP_DEBUG_LOG;
  } elseif (!empty( WP_DEBUG_LOG) && WP_DEBUG_LOG != 'false' ) {
    $log_path = WP_CONTENT_DIR . '/debug.log';
  } else {
    $log_path = false;
  }
  if( $log_path ) {
    @ini_set( 'log_errors', 1 );
      if( !@ini_set( 'error_log', $log_path ) )
        error_log('error unknow'."\n",3,$log_path);
  }
} else {
  error_reporting( E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_ERROR | E_WARNING | E_PARSE | E_USER_ERROR | E_USER_WARNING | E_RECOVERABLE_ERROR );
}
if ( defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || ( defined( 'WP_INSTALLING' ) && WP_INSTALLING ) || wp_doing_ajax() || wp_is_json_request() )
$display_config = 0;
@ini_set( 'display_startup_errors', $display_conf );
@ini_set( 'display_errors', $display_conf );

Hi guys I hope you are okay, sorry I believe that null !== WP_DEBUG_DISPLAY confuses ideas, when I think of null I believe in an undefined constant, frankly there is not the first WP_DEBUG_DISPLAY control.

Version 4, edited 5 years ago by autotutorial (previous) (next) (diff)

#8 @SergeyBiryukov
5 years ago

#49148 was marked as a duplicate.

#9 @SergeyBiryukov
5 years ago

  • Milestone changed from Awaiting Review to 5.3.3
  • Owner set to SergeyBiryukov
  • Status changed from new to accepted

For reference, this was previously fixed in [37448] / #36708, but the fix was reverted in [45611/trunk/src/wp-includes/load.php] per the coding standards.

Since this still appears to cause warnings, let's either add a function_exists() check or restore the error suppression here for now with an exception for PHPCS.

@AkSDvP
5 years ago

Added a condition check to check whether if "ini_set" core function is exists, then only include "ini_set" statement. Or else suppress the errors.

#10 @AkSDvP
5 years ago

  • Keywords needs-testing added; needs-patch removed

#11 @AkSDvP
5 years ago

  • Keywords has-patch added

#12 @autotutorial
5 years ago

No need to be collected in error_log (since the suppression operator @ temporarily sets error_reporting to 0).

<?php
error_reporting(0);
ini_set( 'display_errors', 1);
if( ( $var = ini_set( 'display_errors', 1) ) {
//ini_set work
} elseif( null !== ini_get( 'display_errors') ) {
$var = ini_get( 'display_errors' );
$bool = true;
}
if( isset( $bool ) ) {
//return false value from ini_get in this case it is wrong,
//because only the true value is valid
} else {
//else value from ini_set, any value (boolean, string etc.)
}
if( $var && empty( $bool ) ) {
//ini_set is usable.
}
if( isset( $bool ) ) {
    if( ! $var ) {
        //ini_get work with false value, without ini_set
    } else {
        //ini_get work with true value, without ini_set
    }
}
error_reporting( E_ALL );

1) This code initially means set to true and then check the return value for the second ini_set, 2) otherwise if it is different from null it returns the value of ini_get which means ini_set disabled, (because initially the value is set to true), 3) or otherwise returns the value from ini_set null, false means ini_set disabled (because the first value is set to true)

Currently my hosting does not allow ini_set but the function exists, modifying the php source code is common practice to use screen errors except for ini_set, since php does not offer this support.
So the example pseudo code is always a good encoding (check the second value of ini_set if it is true, ini_set is usable).
Add Silence all errors, error_reporting start from E_ALL.

<?php
error_reporting( 0 );
        if ( WP_DEBUG ) {

                if ( WP_DEBUG_DISPLAY ) {
                        ini_set( 'display_errors', 1 );
                } elseif ( null !== WP_DEBUG_DISPLAY ) {
                        ini_set( 'display_errors', 0 );
                }

                if ( in_array( strtolower( (string) WP_DEBUG_LOG ), array( 'true', '1' ), true ) ) {
                        $log_path = WP_CONTENT_DIR . '/debug.log';
                } elseif ( is_string( WP_DEBUG_LOG ) ) {
                        $log_path = WP_DEBUG_LOG;
                } else {
                        $log_path = false;
                }

                if ( $log_path ) {
                        ini_set( 'log_errors', 1 );
                        ini_set( 'error_log', $log_path );
                }
                error_reporting( E_ALL );
        } else {
                error_reporting( E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_ERROR | E_WARNING | E_PARSE | E_USER_ERROR | E_USER_WARNING | E_RECOVERABLE_ERROR );
        }
$old_value = error_reporting();
error_reporting( 0 );
        if ( defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || ( defined( 'WP_INSTALLING' ) && WP_INSTALLING ) || wp_doing_ajax() || wp_is_json_request() ) {
                ini_set( 'display_errors', 0 );
        }
error_reporting( $old_value );
}
Last edited 5 years ago by autotutorial (previous) (diff)

#13 @audrasjb
5 years ago

  • Milestone changed from 5.3.3 to 5.4

Moving all unfixed tickets from 5.3.3 to milestone 5.4, as there is no plan for a 5.3.3 maintenance release for now.

#14 @ayeshrajans
5 years ago

I don't think that we should gracefully handle the lack of ini_set and ini_get in the runtime.

This can have severe impact in security, because throughout the WordPress core and plugins, I highly doubt every ini_set call is checked. In the site health report, WordPress reports that ini_get function is not available, and I believe this as far as WordPress should be flexible.

For example, if we cannot set display_errors=Off, we should at least trigger a warning in site owners logs instead of silently not setting this configuration value, which might result in the whole stack trace to be printed on screen.

Last edited 5 years ago by ayeshrajans (previous) (diff)

#15 @audrasjb
5 years ago

  • Milestone changed from 5.4 to Future Release

Hi,

With 5.4 Beta 3 approaching and the Beta period reserved for bugs introduced during the cycle, this is being moved to Future Release. If any maintainer or committer feels this should be included or wishes to assume ownership during a specific cycle, feel free to update the milestone accordingly.

#16 @swissspidy
5 weeks ago

#62047 was marked as a duplicate.

#17 @maltfield
5 weeks ago

To be clear: This isn't just a low-priority issue of error logs.

This is a high-priority issue that causes PHP Fatal Errors and breaks the wordpress website such that users cannot login to the WP Dashboard.See #62047

#18 follow-up: @maltfield
5 weeks ago

  • Severity changed from normal to major

I'm raising the priority of this ticket because, since PHP 8, this now triggers a fatal error that breaks wordpress completely.

#19 @maltfield
5 weeks ago

I created a PR to fix the Fatal Error and resolve this bug.

#21 @jrf
5 weeks ago

  • Severity changed from major to normal

@maltfield I understand you are frustrated, but that doesn't mean WP should work around every silly thing hosters can do. Disabling ini_set is not a good practice and is a form of "fake security", not real security. If WP would need to work around that, the same could be argued for literally every single function call to a PHP native function as they could all be disabled.

#22 in reply to: ↑ 18 @SergeyBiryukov
5 weeks ago

Replying to maltfield:

I'm raising the priority of this ticket because, since PHP 8, this now triggers a fatal error that breaks wordpress completely.

This appears to be similar to disabling error_reporting() via php.ini, which was addressed in [50447] / #52226 and [58905] / #61873, but only for the instances called prior to wp-config.php loading.

On systems where ini_set() is disabled, it's best to add a dummy function to the wp-config.php file to avoid fatal errors in core or plugins, since it does not seem feasible to edit any plugin calling ini_set().

If there are any calls to ini_set() in core before wp-config.php is loaded, those may be wrapped in a function_exists() check.

#23 follow-up: @maltfield
5 weeks ago

  • Severity changed from normal to major

@jrf Security should be layered.

As stated in my other ticket (linked above), it absolutely *is* best-practice to disable dangerous functions like exec() in php by adding them to disable_functions in php.ini. Of course, doing so necessitates disabling ini_set() as well, as that would just allow a script to re-enable disabled functions like exec()

It is common practice to do this. It is not silly.

Note that I'm not much of a php developer; my profession is working as a security consultant. Disabling ini_set in php.ini is common practice for many, many orgs.

If WP would need to work around that, the same could be argued for literally every single function call to a PHP native function as they could all be disabled.

For infamously dangerous functions (such as shell(), exec(), ini_set(), etc), wordpress *should* always check to see if it has access to a function before trying to call it. Developers shouldn't assume that admins will allow them to do dangerous things that are very commonly disabled for obvious security reasons.

#24 in reply to: ↑ 23 ; follow-up: @SergeyBiryukov
5 weeks ago

Replying to maltfield:

Of course, doing so necessitates disabling ini_set() as well, as that would just allow a script to re-enable disabled functions like exec()

I don't think that's the case. Looking at the description of core php.ini directives in the PHP manual, disable_functions has INI_SYSTEM next to it, which means it cannot be set by user scripts (as opposed to INI_USER or INI_ALL). It can only be set in the php.ini file itself, or in httpd.conf.

#25 in reply to: ↑ 24 @jrf
5 weeks ago

  • Severity changed from major to normal

Replying to SergeyBiryukov:

Replying to maltfield:

Of course, doing so necessitates disabling ini_set() as well, as that would just allow a script to re-enable disabled functions like exec()

I don't think that's the case. Looking at the description of core php.ini directives in the PHP manual, disable_functions has INI_SYSTEM next to it, which means it cannot be set by user scripts (as opposed to INI_USER or INI_ALL). It can only be set in the php.ini file itself, or in httpd.conf.

Exactly.

Note: See TracTickets for help on using tickets.