PHP GD の imagescale が 64bit 環境で動かない

PHP GD の imagescale を使ってみたら seg.fault で落ちたので調べてみた。

まとめ

  • 64bit 環境だと imagescale は動作しない。
    • seg.fault で落ちるか警告で止まるか
  • function の引数解析で型を間違えてポインタ破壊。(printf系のよくある罠)
    • 32bit環境だと long と int の幅が同じなので問題にならない。
  • 最新のコードでも同じ不具合がある。
    • 同じ原因と思われる違う現象のバグ報告が、draft という事でスルーされてる。

現象

$data = file_get_contents($argv[1]);
$im_in = imagecreatefromstring($data);
$im_out = imagescale($im_in, 256, 192, IMG_NEAREST_NEIGHBOUR);
imagepng($im_out, 'output.png');
  • 実行結果
$ php gd2scale.php azunyan.jpg
Segmentation fault

調査

  • IM ポインタのアドレス値
$ php gd2scale.php azunyan.jpg
IM=0x7f3700000000 le_gd=0x24
Segmentation fault

こんなキリの良い数値の訳ないですね。ポインタが壊れてます。

int new_width, new_height = -1;
gdInterpolationMethod method = GD_BILINEAR_FIXED;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl|ll", &IM, &new_width, &new_height, &method) == FAILURE)  {

これは駄目です。sizeof で見るとこんな感じ。

int:4 long:8 gdInterpolationMethod:4

つまり new_width を更新する際にオフセットがずれて、IM を破壊する。

  • 以下のでとりあえず治る。(コードは汚いけど)
// int new_width, new_height = -1;
// gdInterpolationMethod method = GD_BILINEAR_FIXED;
long new_width, new_height = -1;
long method = GD_BILINEAR_FIXED;

関連バグ

同じ原因っぽいけど、たまたま破壊した結果 IM ポインタの8バイト目のMSBが1になって、負の値チェックに引っかかって警告出力になってるっぽい。

報告した

  • imagescale() segmentation fault on 64bits environment
PHP_FUNCTION(imagescale)
{
        zval *IM;
        gdImagePtr im;
        gdImagePtr im_scaled;
        int new_width, new_height = -1;
        gdInterpolationMethod method = GD_BILINEAR_FIXED;

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl|ll", &IM, &new_width, &new_height, &method) == FAILURE)  {
----

On 64 bits environment ,'l'(long) have 64 bits width,
int and gdInterpolationMethod type have 32 bits,
so IM pointer be destroyed by value of other variables.

This problems related to id:65171, I guess.
- https://bugs.php.net/bug.php?id=65171
  • patch (imagescale-parse-parameters-fix.patch)

    • (空欄)
  • Test Script.
$data = file_get_contents($argv[1]);
$im = ImageCreateFromString($data);
$im2 = imagescale($im, 256, 192, IMG_NEAREST_NEIGHBOUR);
imagepng($im2, 'output.png');
  • Expected result

    • (空欄)
  • Actual result:
Segmentation fault

備考

IM の後ろがどんどんズレるなら直観的に分かりやすいけど、IM の後ろのサイズが不一致で IM が壊されるというのはピンと来ない。引数がスタックに詰まれてるので後ろからのオフセットの積み重ねなんだろうか。va_start, va_end を復習しないと分からない。