天天看點

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

上面是memcached 文本協定的做法。因為文本協定不允許incr 設定不存在的key。

如果是二進制協定,則可以直接用incr指令設定初始值,過期時間。

上面扯遠了,下面來說下memcached incr指令的bug。

在測試的時間,用xmemcached做用戶端,來測試,發現有的時候,incr函數傳回兩個1。

于是,在指令行,用telnet來測試,結果發現有時候傳回很奇怪的資料:

明顯end後面跟了一些很奇怪的資料。而且傳回資料的長度是22,而正确的長度應該是1。

正常的傳回應該是這樣的:

開始以為是xmemcached用戶端的bug,也有可能是序列化方式有問題。于是調試了下代碼,沒發現什麼可疑的地方。

于是祭出wireshakr來抓包。發現xmemcached發出來的資料包是正常的。而且伺服器的确傳回了22位元組的資料。

那麼這個可能是memcached本身的bug了。這個令人比較驚奇,因為memcached本身已經開發多年,很穩定了,怎麼會有這麼明顯的bug?

檢查下目前的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.

于是再到github上檢視修改了哪些内容:

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

對比下修改内容,和檢視下源代碼,可以發現,其實是開發人員在為incr指令存儲的資料配置設定記憶體時,沒有注意邊界,一下子配置設定了incr_max_storage_len,即24位元組的記憶體,卻沒有正常地設定'\r\n'到真實資料的最後。是以當get請求拿到資料是,會把22位元組 + "\r\n"的資料傳回給使用者,造成了記憶體資料洩露。

修複前的代碼:

修複後的代碼:

從測試的版本可以看到,至少從12年這個bug就存在了,從github上的代碼來看,09年之前就存在了。直到13年12月才被修複。

為什麼這個bug隐藏了這個久?可能是因為傳回的22位元組資料裡中間有正确的加了\r\n,後面的才是多餘的洩露資料,可能大部分解析庫都以"\r\n"為分隔,進而跳過了解析到的多餘的資料。比如xmemcached就能解析到。

另外,隻有混用二進制協定和文本協定才可能會發現。

估計有不少伺服器上運作的memcached版本都是比1.4.17要老的,比如ubuntu14預設的就是1.4.14。

不過這個bug的危害比較小,因為洩露的隻有20個位元組的資料。對于一些雲服務指供的cache服務,即使後面是memcached做支援,也會有一個中轉的路由。

竊取到有效資訊的可能性很小。

wireshark預設是支援解析memcached文本和二進制協定的,不過預設解析端口是11211,是以如果想要解析其它端口的包,要設定下。

在"edit", ”preferences“ 裡,找到memcached協定,就可以看到端口的配置了。

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

上面說到memcached的文本協定是不支援incr設定不存在的key的,但是xmemcached卻提供了相關的函數,而且能正常運作,是為什麼呢?

實際上,xmemcached内部包裝了incr和add指令,表明上是調用了incr但實際上是兩條指令:

另外,要注意xmemcached預設是文本協定的,隻有手動配置,才會使用二進制協定。

據這個示範的結果,二進制協定比文本協定要略快,第14頁:

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

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

二進制協定介紹的ppt: