WordPress-Coding-Standards icon indicating copy to clipboard operation
WordPress-Coding-Standards copied to clipboard

Restricted functions used in callbacks

Open jrfnl opened this issue 9 years ago • 10 comments

While debugging the AbstractFunctionRestrictionsSniff class, I realized that any restricted functions used as callbacks, i.e. within add_action(), call_user_func(), array_map() are completely ignored.

That makes this kind of sniff extremely easy to bypass and while this is not so much an issue for people who elect to use the WPCS, this will be an issue for the Theme Review Theme Check to sniffs project as in that case, theme authors won't have a choice and bypassing checks that way is something we'll need to guard against.

I'm investigating how we can solve this. /cc @grappler

jrfnl avatar Jul 17 '16 04:07 jrfnl

Humm. Good catch! So probably the sniff needs to look for any instances of add_action, array_map, etc. and then check to see if the callback arg is among the restricted functions list.

westonruter avatar Jul 22 '16 00:07 westonruter

Yup, I've made a small start with this, but need to concentrate on some other things first now.

If someone else wants to take it on, feel free. I did already create a list of all the PHP and WP functions I could think of which take callbacks with the position of the callback in the argument list, so can share that as a starting point. Any takers ?

jrfnl avatar Jul 22 '16 00:07 jrfnl

@jrfnl Could you share the list of PHP and WP functions?

grappler avatar Oct 11 '16 03:10 grappler

    /**
     * List of known PHP and WP function which take a callback as an argument.
     *
     * @var array <string function name> => <int callback argument position>
     */
    protected $callback_functions = array(
        'add_filter' => 2,
        'add_action' => 2,
        'call_user_func' => 1,
        'call_user_func_array' => 1,
        'forward_static_call' => 1,
        'forward_static_call_array' => 1,
        'array_diff_uassoc' => -1, // = last argument passed
        'array_diff_ukey' => -1, // = last argument passed
        'array_filter' => 2,
        'array_intersect_uassoc' => -1, // = last argument passed
        'array_intersect_ukey' => -1, // = last argument passed
        'array_map' => 1,
        'array_reduce' => 2,
        'array_udiff_assoc' => -1, // = last argument passed
        'array_udiff_uassoc' => array( -1, -2 ), // = last argument passed
        'array_udiff' => -1, // = last argument passed
        'array_uintersect_assoc' => -1, // = last argument passed
        'array_uintersect_uassoc' => array( -1, -2 ), // = last argument passed
        'array_uintersect' => -1, // = last argument passed
        'array_walk' => 2,
        'array_walk_recursive' => 2,
        'iterator_apply' => 2,
        'usort' => 2,
        'uasort' => 2,
        'uksort' => 2,
        'preg_replace_callback' => 2,
        'preg_replace_callback_array' => 1, // = array of patterns and callbacks...
        'mb_ereg_replace_callback' => 2,
        'header_register_callback' => 1,
        'ob_start' => 1,
        'set_error_handler' => 1,
        'set_exception_handler' => 1,
        'register_shutdown_function' => 1,
        'register_tick_function' => 1,
    );

Also - don't forget eval() and create_function()

jrfnl avatar Oct 11 '16 03:10 jrfnl

I was able to solve this with three lines of code

        if ( isset( $this->callback_functions[ $token['content'] ] ) ) {
            $parameter_arg = $this->getFunctionCallParameter( $phpcsFile, $stackPtr, $this->callback_functions[ $token['content'] ] );
            $token['content'] = trim( $parameter_arg['raw'], '\'"' );
        }

Just need to wait for the utility functions like getFunctionCallParameter to become available.

grappler avatar Oct 17 '16 13:10 grappler

Now that the utility functions have been merged in I could have another go at this.

https://github.com/grappler/WordPress-Coding-Standards/tree/feature/611-restricted-callbacks

There are a couple of functions where a callback can be defined in two positions e.g. array_udiff_uassoc I am not sure how to check both. Any ideas?

As we need to support negative positions I was wondering if the support should be ported to teh utility function get_function_call_parameters().

grappler avatar Feb 10 '17 22:02 grappler

@grappler The initial array I provided was from before the utility functions were created. If you'd change the array format to actual param positions instead of relative positions, you could bypass that issue. Changing the values to all be arrays with positions would possibly also make things easier as you wouldn't have to check for number vs array, but could just foreach through it.

jrfnl avatar Feb 11 '17 07:02 jrfnl

If you'd change the array format to actual param positions instead of relative positions, you could bypass that issue.

The positions need to stay relative as for array_udiff_uassoc you can have a different number of arrays. So the last and second last position number changes.

Changing the values to all be arrays with positions would possibly also make things easier as you wouldn't have to check for number vs array, but could just foreach through it.

Ok, I got it work in the end. Had to change a few things around.

grappler avatar Feb 11 '17 19:02 grappler

@grappler Great to hear. I look forward to seeing what you've created!

jrfnl avatar Feb 12 '17 06:02 jrfnl

I'm marking this issue as something which should probably be solved via utility functions to be created in PHPCSUtils. Analyzing callbacks and the functions which expect them can get pretty complicated, so I think having this more complicated logic in PHPCSUtils make sense (if it can be solved at all).

jrfnl avatar Nov 11 '22 10:11 jrfnl