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-11-03 17:48:17

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

Xmemcached 1.2.0-beta版本发布,支持memcached二进制协议

推迟了半个月之后,发布xmemcached-1.2.0的beta测试版本,此版本又是一个里程碑版本,主要亮点如下: 1.支持全部的二进制协议,包括noreply的二进制协议.memcached 1.4.0正式推出memcached的二进制协议,相比于文本协议,二进制协议更复杂,但是也更容易解析和编码,并且可扩展性也比较强,比如原来文本协议只允许key为String类型,二进制协议允许key是任意类型,并且长度可以达到2^16-1,大大超过原有的255的限制.另一方面,文本协议的可读性更好,在不同

Thrift的TProtocol类体系原理及源码详解:二进制协议类TBinaryProtocolT(TBi

Thrift的TProtocol类体系原理及源码详解:二进制协议类TBinaryProtocolT(TBinaryProtocol) 这个协议是Thrift支持的默认二进制协议,它以二进制的格式写所有的数据,基本上直接 发送原始数据.因为它直接从TVirtualProtocol类继承,而且是一个模板类.它的模板参数 就是一个封装具体传输发送的类,这个类才是真正实现数据传输的.这个类的定义上一节举 例已经出现过了就不在列出来了. 下面我就结合scribe的Log函数执行的具体过程来 分析使用这个协

RxJava 中的 Subscriptions 是怎样泄露内存的

本文讲的是RxJava 中的 Subscriptions 是怎样泄露内存的, 关于 RxJava 已经有了很多很好的的教程文章.在使用 Android 框架时,它确实显著地简化了工作.然而需要注意,这种简化有它自己的缺陷.在接下来的部分中,你将探索其中的一个,从而了解 RxJava 的 Subscriptions 有多容易造成内存泄漏. 解决简单任务 假设你的主管让你实现一个显示随机的电影名的控件.它必须基于一些外部的推荐服务.这个控件应当根据用户要求显示电影名称.如果用户没有要求,它也可以自己

mb-link如何建立超过100MB的二进制数组?是不是会浪费内存?

问题描述 link如何建立超过100MB的二进制数组?是不是会浪费内存? link如何建立超过100MB的二进制数组?是不是会浪费内存? 解决方案 不需要用linq,用 byte[] data = new byte[大小]; 即可,创建2GB都可以. 是否浪费内存看你的需要了,也可以用文件.

IE8 内存泄露(内存一直增长 )的原因及解决办法_javascript技巧

最近开发的时候对页面使用了定时的局部更新,结果在ie6,7和Firefox下,一切正常,而在ie8下过上几个小时就浏览器就崩溃了,显示是内存溢出,我以为是代码写的不好导致内存泄露,但是ie6,7又正常,调查了一下,原来这是ie8的bug. 问题点 在IE8中,生成特定Dom节点所占用的内存是不会被释放的,即使这些节点被删除内存也不会被释放. 内存泄露的节点类型包括:form.button.input.select.textarea.a.img和objec 其他的大部分节点类型是不会泄露的,例如:

C#轻量级通通讯组件StriveEngine —— C/S通信开源demo(2) —— 使用二进制协议 (附源码)

前段时间,有几个研究ESFramework通信框架的朋友对我说,ESFramework有点庞大,对于他们目前的项目来说有点"杀鸡用牛刀"的意思,因为他们的项目不需要文件传送.不需要P2P.不存在好友关系.也不存在组广播.不需要服务器均衡.不需要跨服务器通信.甚至都不需要使用UserID,只要客户端能与服务端进行简单的稳定高效的通信就可以了.于是,他们建议我,整一个轻量级的C#通讯组件来满足类似他们这种项目的需求.我觉得这个建议是有道理的,于是,花了几天时间,我将ESFramework的

一个查看内存数据的Delphi函数

unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Se

《深入浅出DPDK》—第3章3.2节指令并发与数据并行

3.2 指令并发与数据并行前面我们花了较大篇幅讲解多核并发对于整体性能提升的帮助,从本节开始,我们将从另外一个维度--指令并发,站在一个更小粒度的视角,去理解指令级并发对于性能提升的帮助.3.2.1 指令并发现代多核处理器几乎都采用了超标量的体系结构来提高指令的并发度,并进一步地允许对无依赖关系的指令乱序执行.这种用空间换时间的方法,极大提高了IPC,使得一个时钟周期完成多条指令成为可能.图3-6中Haswell微架构流水线是Haswell微架构的流水线参考,从中可以看到Scheduler下挂了

c#中用windows api函数修改内存数据

这个问题来自伴水的<划拳机器人>,对本文用途感兴趣的朋友请大致阅读伴水的帖子,在帖子中我用这个方法写了剪刀五号,战绩不错,当然属于作弊的方法了. 剪刀五号的思路就是每次出拳,尽量让对方能赢,然后根据一个地址段来扫描内存中对方所赢的局数的保存地址,找到后在得到比赛结果时把内存数据改掉.这个类似以前打单机游戏时用的fpe之类的修改工具.当然,如果对方故意犯规,一局也不赢,你是找不到他的地址的,这样可以通过正常途径来获取胜利. 把剪刀五号核心代码简化后,主要为三个api函数 OpenProcess,