memcached 非法key导致内存溢出

昨天晚上服务器开始报警一个内存泄漏的错误,直接导致页面出现500错误,所幸出现页面的地址只有一个,且此页面有做ats健康缓存,所以用户访问的始终是正常页面

错误描述:PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 976370274 bytes) in mc.php on line 31

出错的页面最近没有动到,由于是PHP Fatal error:级别,无法通过debug_backtrace拿到堆栈信息,给排查问题造成很大困扰。

排查问题:通过在测试环境复现问题,一行行调试,打印输出,最终定位到小编在录入一个id值时多复制了一个tab符号,代码中信任了此值

当它为int拼接为mc的key进行缓存获取到的数据,被缓存数据是一个数组,在set成功后get被缓存数据时内存溢出。

代码场景还原

<?php

$m = new Memcached();
$host = '127.0.0.1';
$port = 11211;
$m->addServer($host, $port);
echo "=connect mc $host:$port\n";
$mcKey = "\t188883";

$mcKey2 = "test_" . $mcKey;
# 注意被设置的值一定是array才能复现内存溢出
$ntest = array (
    'id' => '188883',
);

echo "=set mc\n";
var_dump($m->set($mcKey2, $ntest, 60));
echo "=set mc result\n";
var_dump($m->getResultMessage());
echo "=get mc\n";
var_dump($m->get($mcKey2));
echo "=get mc result\n";
var_dump($m->getResultMessage());

运行程序

#php -d "display_errors=On" test.php
=connect mc 127.0.0.1:11211
=set mc
bool(true)
=set mc result
string(7) "SUCCESS"
=get mc

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 976304738 bytes) in test.php on line 20

检查软件版本

php 5.4.25
libmemcached 1.0.18
memcached 2.2.0

为了找出底层问题和新版本修复情况找了一个php7.x的版本测试

#php -d "display_errors=On" test.php
=connect mc 127.0.0.1:11211
=set mc
bool(false)
=set mc result
string(46) "A BAD KEY WAS PROVIDED/CHARACTERS OUT OF RANGE"
=get mc
bool(false)
=get mc result
string(46) "A BAD KEY WAS PROVIDED/CHARACTERS OUT OF RANGE"

相关软件版本

php 7.0.7
libmemcached 1.0.18
memcached 3.0.3

由于是获取mc时出现的内存溢出,且从上面看由于新版本对mc的key做了增强的检查而避免的,所以
先下载两个memcached版本源码,进行查看

  // memcached 2.2.0 可以看到对key的检查只限于空格和长度,tab是被认为合法的
    if (key_len == 0 || strchr(key, ' ')) {
        i_obj->rescode = MEMCACHED_BAD_KEY_PROVIDED;
        RETURN_FROM_GET;
    }
// memcached 3.0.3 这里的key验证相对来说复杂了许多
#define MEMC_CHECK_KEY(intern, key)                                               \
    if (UNEXPECTED(ZSTR_LEN(key) == 0 ||                                          \
        ZSTR_LEN(key) > MEMC_OBJECT_KEY_MAX_LENGTH ||                             \
        (memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) \
                ? !s_memc_valid_key_binary(ZSTR_VAL(key))                         \
                : !s_memc_valid_key_ascii(ZSTR_VAL(key))                          \
        ))) {                                                                     \
        intern->rescode = MEMCACHED_BAD_KEY_PROVIDED;                             \
        RETURN_FALSE;                                                             \
    }

#ifdef HAVE_MEMCACHED_PROTOCOL

小结
memcached 2.2.0 是php5使用的最高版本了,memcached 3.x是给php7 使用的,如果要在php5项目中避免内存溢出只能在php代码层面对mckey进行检查或是提高代码书写质量,避免传递给扩展一个非法的key,对于有条件的用户也可以升级到php7

    原文作者:楠能柯贵
    原文地址: https://www.jianshu.com/p/b73128cc5665
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞