• 转载 Memcached BinaryProtocol incr指令内存泄露的bug


    缘起

    最近有个分布式限速的需求。支付宝的接口双11只允许每秒调用10次。

    单机的限速,自然是用google guava的RateLimiter。

    http://docs.guava-libraries.googlecode.com/git-history/master/javadoc/com/google/common/util/concurrent/RateLimiter.html

    分布式的ReteLimiter,貌似没有现在的实现方案。不过用memcached或者Redis来实现一个简单的也很快。

    比如上面的要求,每秒钟只允许调用10次,则按下面的流程来执行,以memcached为例:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. incr alipay_ratelimiter  1 1  
    2. 如果返回NOT_FOUND,则  
    3.   add alipay_ratelimiter  0  1 1  
    4.   1  
    5.   即如果alipay_ratelimiter不存在,则设置alipay_ratelimiter的值为1,过期时间为1秒。  
    6. 如果incr返回不是具体的数值,则判断是否大于10,  
    7. 如果大于10则要sleep等待。  

    上面是Memcached 文本协议的做法。因为文本协议不允许incr 设置不存在的key。

    如果是二进制协议,则可以直接用incr命令设置初始值,过期时间。

    memcached二进制协议的bug

    上面扯远了,下面来说下memcached incr指令的bug。

    在测试的时间,用XMemcached做客户端,来测试,发现有的时候,incr函数返回两个1。

    于是,在命令行,用telnet来测试,结果发现有时候返回很奇怪的数据:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. get alipay_ratelimiter  
    2. VALUE alipay_ratelimiter 0 22  
    3. END2446744073709551608  


    明显END后面跟了一些很奇怪的数据。而且返回数据的长度是22,而正确的长度应该是1。

    正常的返回应该是这样的:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. get alipay_ratelimiter  
    2. VALUE alipay_ratelimiter 0 4  
    3. 1  
    4. END  

    抓包分析

    开始以为是XMemcached客户端的bug,也有可能是序列化方式有问题。于是调试了下代码,没发现什么可疑的地方。

    于是祭出wireshakr来抓包。发现XMemcached发出来的数据包是正常的。而且服务器的确返回了22字节的数据。

    那么这个可能是Memcached本身的bug了。这个令人比较惊奇,因为Memcached本身已经开发多年,很稳定了,怎么会有这么明显的bug?

    查找有问题的Memcached的版本

    检查下当前的Memcahcd版本,是memcached 1.4.14。

    于是去下载了最新的1.4.21版,编绎安装之后,再次测试。发现正常了。

    于是到release log里查看是哪个版本修复了:

    https://code.google.com/p/memcached/wiki/ReleaseNotes

    发现1417版的release note里有incr相关的信息:

    https://code.google.com/p/memcached/wiki/ReleaseNotes1417

    Fix for incorrect length of initial value set via binary increment protocol.

    查找bug发生的原因:

    于是再到github上查看修改了哪些内容:

    https://github.com/memcached/memcached/commit/8818bb698ea0abd5199b2792964bbc7fbe4cd845?diff=split

    对比下修改内容,和查看下源代码,可以发现,其实是开发人员在为incr指令存储的数据分配内存时,没有注意边界,一下子分配了INCR_MAX_STORAGE_LEN,即24字节的内存,却没有正常地设置' '到真实数据的最后。所以当Get请求拿到数据是,会把22字节 + " "的数据返回给用户,造成了内存数据泄露。

    修复前的代码:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. it = item_alloc(key, nkey, 0, realtime(req->message.body.expiration),  
    2.                 INCR_MAX_STORAGE_LEN);  
    3.   
    4. if (it != NULL) {  
    5.     snprintf(ITEM_data(it), INCR_MAX_STORAGE_LEN, "%llu",  
    6.              (unsigned long long)req->message.body.initial);  



    修复后的代码:

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. snprintf(tmpbuf, INCR_MAX_STORAGE_LEN, "%llu",  
    2.     (unsigned long long)req->message.body.initial);  
    3. int res = strlen(tmpbuf);  
    4. it = item_alloc(key, nkey, 0, realtime(req->message.body.expiration),  
    5.                 res + 2);  
    6.   
    7. if (it != NULL) {  
    8.     memcpy(ITEM_data(it), tmpbuf, res);  
    9.     memcpy(ITEM_data(it) + res, " ", 2);  



    为什么这个bug隐藏了这么久

    从测试的版本可以看到,至少从12年这个bug就存在了,从github上的代码来看,09年之前就存在了。直到13年12月才被修复。

    为什么这个bug隐藏了这个久?可能是因为返回的22字节数据里中间有正确的加了 ,后面的才是多余的泄露数据,可能大部分解析库都以" "为分隔,从而跳过了解析到的多余的数据。比如XMemcached就能解析到。

    另外,只有混用二进制协议和文本协议才可能会发现。

    估计有不少服务器上运行的memcached版本都是比1.4.17要老的,比如ubuntu14默认的就是1.4.14。

    这个bug的危害

    不过这个bug的危害比较小,因为泄露的只有20个字节的数据。对于一些云服务指供的cache服务,即使后面是memcached做支持,也会有一个中转的路由。

    窃取到有效信息的可能性很小。

    其它的一些东东

    wireshark设置解析memcached协议:

    wireshark默认是支持解析memcached文本和二进制协议的,不过默认解析端口是11211,所以如果想要解析其它端口的包,要设置下。

    在"Edit", ”Preferences“ 里,找到Memcached协议,就可以看到端口的配置了。

    参考:https://ask.wireshark.org/questions/24495/memcache-and-tcp 

    XMemcached的文本协议incr指令的实现:

    上面说到Memcached的文本协议是不支持incr设置不存在的key的,但是XMemcached却提供了相关的函数,而且能正常运行,是为什么呢?

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /** 
    2.  * "incr" are used to change data for some item in-place, incrementing it. 
    3.  * The data for the item is treated as decimal representation of a 64-bit 
    4.  * unsigned integer. If the current data value does not conform to such a 
    5.  * representation, the commands behave as if the value were 0. Also, the 
    6.  * item must already exist for incr to work; these commands won't pretend 
    7.  * that a non-existent key exists with value 0; instead, it will fail.This 
    8.  * method doesn't wait for reply. 
    9.  *  
    10.  * @param key 
    11.  *            key 
    12.  * @param delta 
    13.  *            increment delta 
    14.  * @param initValue 
    15.  *            the initial value to be added when value is not found 
    16.  * @param timeout 
    17.  *            operation timeout 
    18.  * @param exp 
    19.  *            the initial vlaue expire time, in seconds. Can be up to 30 
    20.  *            days. After 30 days, is treated as a unix timestamp of an 
    21.  *            exact date. 
    22.  * @return 
    23.  * @throws TimeoutException 
    24.  * @throws InterruptedException 
    25.  * @throws MemcachedException 
    26.  */  
    27. long incr(String key, long delta, long initValue, long timeout, int exp)  
    28.         throws TimeoutException, InterruptedException, MemcachedException;  



    实际上,XMemcached内部包装了incr和add指令,表明上是调用了incr但实际上是两条指令:

    [plain] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. incr alipay_ratelimiter 1  
    2. NOT_FOUND  
    3. add alipay_ratelimiter 0 0 1  
    4. 1  
    5. STORED  

    另外,要注意XMemcached默认是文本协议的,只有手动配置,才会使用二进制协议。

    Memcached二进制协议比文本协议要快多少?

    据这个演示的结果,二进制协议比文本协议要略快,第14页:

    http://www.slideshare.net/tmaesaka/memcached-binary-protocol-in-a-nutshell-presentation/

    参考:

    https://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol 

    二进制协议介绍的ppt:
    http://www.slideshare.net/tmaesaka/memcached-binary-protocol-in-a-nutshell-presentation/

    转载:http://blog.csdn.net/hengyunabc/article/details/40897421

    最新文章,请关注微信公众号

    作者:三石雨
    出处:http://www.cnblogs.com/exceptioneye

    再烦,也别忘微笑;再急,也要注意语气。
    再苦,也别忘坚持;再累,也要爱自己。
    低调做人,你会一次比一次稳健;高调做事,你会一次比一次优秀。
  • 相关阅读:
    iOS Simulator功能介绍关于Xamarin IOS开发
    Unity中制作游戏的快照游戏支持玩家拍快照
    手机数据抓包入门教程
    Swift语言中为外部参数设置默认值可变参数常量参数变量参数输入输出参数
    Hierarchy视图里的Transform和Camera组件
    用JAVA编写MP3解码器——GUI(FFT)(转)
    功率W与dBm的对照表及关系(转)
    单鞭天线的长度计算方法(转)
    STM32F10X SPI操作flash MX25L64读写数据(转)
    利用STM32F唯一96bit序列号实现反拷贝加密的源代码公开(转)
  • 原文地址:https://www.cnblogs.com/exceptioneye/p/4783566.html
Copyright © 2020-2023  润新知