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为例:

incr alipay_ratelimiter  1 1
如果返回NOT_FOUND,则
  add alipay_ratelimiter  0  1 1
  1
  即如果alipay_ratelimiter不存在,则设置alipay_ratelimiter的值为1,过期时间为1秒。
如果incr返回不是具体的数值,则判断是否大于10,
如果大于10则要sleep等待。

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

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

memcached二进制协议的bug

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

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

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

get alipay_ratelimiter
VALUE alipay_ratelimiter 0 22
END2446744073709551608

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

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

get alipay_ratelimiter
VALUE alipay_ratelimiter 0 4
1
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字节的内存,却没有正常地设置‘\r\n‘到真实数据的最后。所以当Get请求拿到数据是,会把22字节 + "\r\n"的数据返回给用户,造成了内存数据泄露。

修复前的代码:

            it = item_alloc(key, nkey, 0, realtime(req->message.body.expiration),
                            INCR_MAX_STORAGE_LEN);

            if (it != NULL) {
                snprintf(ITEM_data(it), INCR_MAX_STORAGE_LEN, "%llu",
                         (unsigned long long)req->message.body.initial);

修复后的代码:

            snprintf(tmpbuf, INCR_MAX_STORAGE_LEN, "%llu",
                (unsigned long long)req->message.body.initial);
            int res = strlen(tmpbuf);
            it = item_alloc(key, nkey, 0, realtime(req->message.body.expiration),
                            res + 2);

            if (it != NULL) {
                memcpy(ITEM_data(it), tmpbuf, res);
                memcpy(ITEM_data(it) + res, "\r\n", 2);

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

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

为什么这个bug隐藏了这个久?可能是因为返回的22字节数据里中间有正确的加了\r\n,后面的才是多余的泄露数据,可能大部分解析库都以"\r\n"为分隔,从而跳过了解析到的多余的数据。比如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却提供了相关的函数,而且能正常运行,是为什么呢?

	/**
	 * "incr" are used to change data for some item in-place, incrementing it.
	 * The data for the item is treated as decimal representation of a 64-bit
	 * unsigned integer. If the current data value does not conform to such a
	 * representation, the commands behave as if the value were 0. Also, the
	 * item must already exist for incr to work; these commands won't pretend
	 * that a non-existent key exists with value 0; instead, it will fail.This
	 * method doesn't wait for reply.
	 *
	 * @param key
	 *            key
	 * @param delta
	 *            increment delta
	 * @param initValue
	 *            the initial value to be added when value is not found
	 * @param timeout
	 *            operation timeout
	 * @param exp
	 *            the initial vlaue expire time, in seconds. Can be up to 30
	 *            days. After 30 days, is treated as a unix timestamp of an
	 *            exact date.
	 * @return
	 * @throws TimeoutException
	 * @throws InterruptedException
	 * @throws MemcachedException
	 */
	long incr(String key, long delta, long initValue, long timeout, int exp)
			throws TimeoutException, InterruptedException, MemcachedException;

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

incr alipay_ratelimiter 1
NOT_FOUND
add alipay_ratelimiter 0 0 1
1
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/

时间: 2024-10-13 01:40:38

Memcached 二进制协议(BinaryProtocol) incr指令泄露内存数据的bug的相关文章

转载 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来实现一个简单的也

iOS-二进制协议的封装

对于在SDK socket通信时会存在二进制协议的通信模式,对于此根据以往的工作内容进行小结: 首先在socket通讯中可以有字符串协议和二进制协议,通过协议来达到通讯的目的.对于字符串协议就是通过字符串来制定通讯的标准模式是"string"-"value"模式,通过XML或者json来达到网络传输,解析封装也是基于XML或者json进行信息提取. 对于二进制协议,在C语言是通过struct对协议进行封装,在iOS中使用的是OC,在OC中你也可以通过C语言对二进制协

【转】C# 串口操作系列(3) -- 协议篇,二进制协议数据解析

我们的串口程序,除了通用的,进行串口监听收发的简单工具,大多都和下位机有关,这就需要关心我们的通讯协议如何缓存,分析,以及通知界面. 我们先说一下通讯协议.通讯协议就是通讯双方共同遵循的一套规则,定义协议的原则是尽可能的简单以提高传输率,尽可能的具有安全性保证数据传输完整正确.基于这2点规则,我们一个通讯协议应该是这样的:头+数据长度+数据正文+校验 例如:AA 44 05 01 02 03 04 05 EA 这里我假设的一条数据,协议如下: 数据头:     AA 44 数据长度: 05 数据

C# 串口操作系列(3) -- 协议篇,二进制协议数据解析

C# 串口操作系列(3) -- 协议篇,二进制协议数据解析 标签: c#bufferobject通讯byte硬件驱动 2010-05-27 09:54 51565人阅读 评论(215) 收藏 举报  分类: 通讯类库设计(4)  版权声明:本文为博主原创文章,未经博主允许不得转载. 我们的串口程序,除了通用的,进行串口监听收发的简单工具,大多都和下位机有关,这就需要关心我们的通讯协议如何缓存,分析,以及通知界面. 我们先说一下通讯协议.通讯协议就是通讯双方共同遵循的一套规则,定义协议的原则是尽可

一种通用的树形二进制协议描述方法与处理算法

概述: 本方法定义了一种数据结构,可用于描述任意的树形二进制协议,并配合一个特定的处理算法,可实现一种通用的,由该种树形二进制协议定义的比特流解析与填充的处理,该数据结构的定义如下: /* 以下结构用于定义一个协议节点的描述信息. */ struct _proto_info; typedef struct _proto_des { const char *              name; /* 用于描述一个协议节点的名称. */ size_t                    stat

[C/C++]_[输出内存数据的二进制和十六进制的字符串表示]

场景: 1. 在读取文件或内存时,有时候需要输出那段内存的十六或二进制表示进行分析. 2. 标准的printf没有显示二进制的,而%x显示有最大上限,就是8字节,超过8字节就不行了. test_binary_hex.cpp #include <stdlib.h> #include <string.h> #include <stdio.h> #include <assert.h> #include <stdint.h> #include <i

文本协议与二进制协议的选择

进行网络通信时,我们经常纠结于到底使用什么样的协议传输数据,下面我谈谈应该怎么选择一种合理的协议格式. 网络协议 标准定义是这样的: 为计算机网络中进行数据交换而建立的规则.标准或约定的集合. 网络协议至少包括三要素: 语法:语法是用户数据与控制信息的结构与格式,以及数据出现的顺序. 语义:解释控制信息每个部分的意义.它规定了需要发出何种控制信息,以及完成的动作与做出什么样的响应. 时序:时序是对事件发生顺序的详细说明. 人们形象地把这三个要素描述为:语义表示要做什么,语法表示要怎么做,时序表示

轻量级通信引擎StriveEngine —— C/S通信demo(2) —— 使用二进制协议 (附源码)

在网络上,交互的双方基于TCP或UDP进行通信,通信协议的格式通常分为两类:文本消息.二进制消息. 文本协议相对简单,通常使用一个特殊的标记符作为一个消息的结束. 二进制协议,通常是由消息头(Header)和消息体(Body)构成的,消息头的长度固定,而且,通过解析消息头,可以知道消息体的长度.如此,我们便可以从网络流中解析出一个个完整的二进制消息. 两种类型的协议格式各有优劣:文本协议直观.容易理解,但是在文本消息中很难嵌入二进制数据,比如嵌入一张图片:而二进制协议的优缺点刚刚相反. 在 轻量

主程序员的练成:HTTP协议和二进制协议的对比

在上一篇<主程序员的练成:TCP.消息分包和协议设计>中谈了协议设计的一些话题,这里补充聊聊HTTP协议和二进制协议的对比. HTTP协议是一种文本协议,也是一种Name-Based协议,就从这两方面来说. 文本协议 vs 二进制协议 文本协议的特点: 便于人 易于阅读.理解.调试.构造 解析复杂.冗余多 需要考虑字符转义 二进制协议的特点: 便于机器 Name-Based vs Position-Based Name-Based协议的特点: 协议字段都用Name标识 协议字段与位置无关 协议