ときどきの雑記帖 倒行逆施編

最新ページへのリンク
目次ページへのリンク

一つ前へ 2016年2月(上旬)
一つ後へ 2016年2月(下旬)

ホームへ

2016年02月20日

■_

明倫館で、最近亡くなられた父親の蔵書を査定してもらっている現場に遭遇 (持ってきてたのはほんの一部みたいだけど)。 そういえば最近何度か どうみてもこれ著者からの献本だろうというのを見かけてるんだけど そういう時期なんだろうねえ。

Hython: a nearly-complete Python 3 interpreter written in Haskell : programming の、 How does it compare for performance? から Pretty bad. :) へのやり取りにふいたw

■_

■_

CVE-2015-7547 のはなし。続き。 Carlos O'Donell - [PATCH] CVE-2015-7547 --- glibc getaddrinfo() stack-based buffer overflo つーてもバグの詳細追いかけたというものではなく。

Carlos O'Donell - [PATCH] CVE-2015-7547 --- glibc getaddrinfo() stack-based buffer overflo

CVE-2015-7547

2016-02-15  Carlos O'Donell  

	[BZ #18665]
	* resolv/nss_dns/dns-host.c (gaih_getanswer_slice): Always set
	*herrno_p.
	(gaih_getanswer): Document functional behviour. Return tryagain
	if any result is tryagain.
	* resolv/res_query.c (__libc_res_nsearch): Set buffer size to zero
	when freed.
	* resolv/res_send.c: Add copyright text.
	(__libc_res_nsend): Document that MAXPACKET is expected.
	(send_vc): Document. Remove buffer reuse.
	(send_dg): Document. Remove buffer reuse. Set *thisanssizp to set the
	size of the buffer. Add Dprint for truncated UDP buffer.

修正が入ったファイルは↑にあるように三つあって (resolv/nss_dns/dns-host.c、resolv/res_query.c、resolv/res_send.c)、 差分を見ていくとコメントの量がかなりある。

diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
index a255d5e..47cfe27 100644
--- a/resolv/nss_dns/dns-host.c
+++ b/resolv/nss_dns/dns-host.c
@@ -1031,7 +1031,10 @@ gaih_getanswer_slice (const querybuf *answer, int anslen, const char *qname,
   int h_namelen = 0;
 
   if (ancount == 0)
-    return NSS_STATUS_NOTFOUND;
+    {
+      *h_errnop = HOST_NOT_FOUND;
+      return NSS_STATUS_NOTFOUND;
+    }
 
   while (ancount-- > 0 && cp < end_of_message && had_error == 0)
     {
@@ -1208,7 +1211,14 @@ gaih_getanswer_slice (const querybuf *answer, int anslen, const char *qname,
   /* Special case here: if the resolver sent a result but it only
      contains a CNAME while we are looking for a T_A or T_AAAA record,
      we fail with NOTFOUND instead of TRYAGAIN.  */
-  return canon == NULL ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND;
+  if (canon != NULL)
+    {
+      *h_errnop = HOST_NOT_FOUND;
+      return NSS_STATUS_NOTFOUND;
+    }
+
+  *h_errnop = NETDB_INTERNAL;
+  return NSS_STATUS_TRYAGAIN;
 }

↑ h_errnop は今回追加された関数の引数で、 関数自体の戻り値とは別にやっておかないと行けないので 呼び出し元の変数をここで(ポインター経由で)書きかえるんだったかな。 呼び出し元の方で対処できないのかとも思うけど もう一回コード見るのは面倒くさいので放置。

長いコメント一つめ。 たぶん今回の脆弱性の発火条件に関わってたので ステータスがどのように遷移するのか解説してるみたい。

@@ -1222,11 +1232,101 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
 
   enum nss_status status = NSS_STATUS_NOTFOUND;
 
+  /* Combining the NSS status of two distinct queries requires some
+     compromise and attention to symmetry (A or AAAA queries can be
+     returned in any order).  What follows is a breakdown of how this
+     code is expected to work and why. We discuss only SUCCESS,
+     TRYAGAIN, NOTFOUND and UNAVAIL, since they are the only returns
+     that apply (though RETURN and MERGE exist).  We make a distinction
+     between TRYAGAIN (recoverable) and TRYAGAIN' (not-recoverable).
+     A recoverable TRYAGAIN is almost always due to buffer size issues
+     and returns ERANGE in errno and the caller is expected to retry
+     with a larger buffer.
+
+     Lastly, you may be tempted to make significant changes to the
+     conditions in this code to bring about symmetry between responses.
+     Please don't change anything without due consideration for
+     expected application behaviour.  Some of the synthesized responses
+     aren't very well thought out and sometimes appear to imply that
+     IPv4 responses are always answer 1, and IPv6 responses are always
+     answer 2, but that's not true (see the implemetnation of send_dg
+     and send_vc to see response can arrive in any order, particlarly
+     for UDP). However, we expect it holds roughly enough of the time
+     that this code works, but certainly needs to be fixed to make this
+     a more robust implementation.
+
+     ----------------------------------------------
+     | Answer 1 Status /   | Synthesized | Reason |
+     | Answer 2 Status     | Status      |        |
+     |--------------------------------------------|
+     | SUCCESS/SUCCESS     | SUCCESS     | [1]    |
+     | SUCCESS/TRYAGAIN    | TRYAGAIN    | [5]    |
+     | SUCCESS/TRYAGAIN'   | SUCCESS     | [1]    |
+     | SUCCESS/NOTFOUND    | SUCCESS     | [1]    |
+     | SUCCESS/UNAVAIL     | SUCCESS     | [1]    |
+     | TRYAGAIN/SUCCESS    | TRYAGAIN    | [2]    |
+     | TRYAGAIN/TRYAGAIN   | TRYAGAIN    | [2]    |
+     | TRYAGAIN/TRYAGAIN'  | TRYAGAIN    | [2]    |
+     | TRYAGAIN/NOTFOUND   | TRYAGAIN    | [2]    |
+     | TRYAGAIN/UNAVAIL    | TRYAGAIN    | [2]    |
+     | TRYAGAIN'/SUCCESS   | SUCCESS     | [3]    |
+     | TRYAGAIN'/TRYAGAIN  | TRYAGAIN    | [3]    |
+     | TRYAGAIN'/TRYAGAIN' | TRYAGAIN'   | [3]    |
+     | TRYAGAIN'/NOTFOUND  | TRYAGAIN'   | [3]    |
+     | TRYAGAIN'/UNAVAIL   | UNAVAIL     | [3]    |
+     | NOTFOUND/SUCCESS    | SUCCESS     | [3]    |
+     | NOTFOUND/TRYAGAIN   | TRYAGAIN    | [3]    |
+     | NOTFOUND/TRYAGAIN'  | TRYAGAIN'   | [3]    |
+     | NOTFOUND/NOTFOUND   | NOTFOUND    | [3]    |
+     | NOTFOUND/UNAVAIL    | UNAVAIL     | [3]    |
+     | UNAVAIL/SUCCESS     | UNAVAIL     | [4]    |
+     | UNAVAIL/TRYAGAIN    | UNAVAIL     | [4]    |
+     | UNAVAIL/TRYAGAIN'   | UNAVAIL     | [4]    |
+     | UNAVAIL/NOTFOUND    | UNAVAIL     | [4]    |
+     | UNAVAIL/UNAVAIL     | UNAVAIL     | [4]    |
+     ----------------------------------------------
+
+     [1] If the first response is a success we return success.
+         This ignores the state of the second answer and in fact
+         incorrectly sets errno and h_errno to that of the second
+	 answer.  However because the response is a success we ignore
+	 *errnop and *h_errnop (though that means you touched errno on
+         success).  We are being conservative here and returning the
+         likely IPv4 response in the first answer as a success.
+
+     [2] If the first response is a recoverable TRYAGAIN we return
+	 that instead of looking at the second response.  The
+	 expectation here is that we have failed to get an IPv4 response
+	 and should retry both queries.
+
+     [3] If the first response was not a SUCCESS and the second
+	 response is not NOTFOUND (had a SUCCESS, need to TRYAGAIN,
+	 or failed entirely e.g. TRYAGAIN' and UNAVAIL) then use the
+	 result from the second response, otherwise the first responses
+	 status is used.  Again we have some odd side-effects when the
+	 second response is NOTFOUND because we overwrite *errnop and
+	 *h_errnop that means that a first answer of NOTFOUND might see
+	 its *errnop and *h_errnop values altered.  Whether it matters
+	 in practice that a first response NOTFOUND has the wrong
+	 *errnop and *h_errnop is undecided.
+
+     [4] If the first response is UNAVAIL we return that instead of
+	 looking at the second response.  The expectation here is that
+	 it will have failed similarly e.g. configuration failure.
+
+     [5] Testing this code is complicated by the fact that truncated
+	 second response buffers might be returned as SUCCESS if the
+	 first answer is a SUCCESS.  To fix this we add symmetry to
+	 TRYAGAIN with the second response.  If the second response
+	 is a recoverable error we now return TRYAGIN even if the first
+	 response was SUCCESS.  */

差分だけ見るとなんでこんなの抜けてたんだという気もするんだけど ポインター(*answerp2) を NULL にリセットしたので良しと判断してたのかも。 あ、nanserp2 ってのは、書きかえられる引数で answerp2 が指す領域の長さを保持するもの。 ついでに書いとくと answerp2 も関数の引数で ** で渡されてくる(ので一段階 deref するとポインターの値を書きかえられると)もの。

diff --git a/resolv/res_query.c b/resolv/res_query.c
@@ -396,6 +396,7 @@ __libc_res_nsearch(res_state statp,
 		  {
 		    free (*answerp2);
 		    *answerp2 = NULL;
+		    *nanswerp2 = 0;
 		    *answerp2_malloced = 0;
 		  }
 	}
@@ -447,6 +448,7 @@ __libc_res_nsearch(res_state statp,
 			  {
 			    free (*answerp2);
 			    *answerp2 = NULL;
+			    *nanswerp2 = 0;
 			    *answerp2_malloced = 0;
 			  }
 
@@ -521,6 +523,7 @@ __libc_res_nsearch(res_state statp,
 	  {
 	    free (*answerp2);
 	    *answerp2 = NULL;
+	    *nanswerp2 = 0;
 	    *answerp2_malloced = 0;
 	  }
 	if (saved_herrno != -1)

長いコメントもう一つ。

diff --git a/resolv/res_send.c b/resolv/res_send.c
@@ -630,6 +649,77 @@ get_nsaddr (res_state statp, int n)
     return (struct sockaddr *) (void *) &statp->nsaddr_list[n];
 }
 
+/* The send_vc function is responsible for sending a DNS query over TCP
+   to the nameserver numbered NS from the res_state STATP i.e.
+   EXT(statp).nssocks[ns].  The function supports sending both IPv4 and
+   IPv6 queries at the same serially on the same socket.
+
+   Please note that for TCP there is no way to disable sending both
+   queries, unlike UDP, which honours RES_SNGLKUP and RES_SNGLKUPREOP
+   and sends the queries serially and waits for the result after each
+   sent query.  This implemetnation should be corrected to honour these
+   options.
+
+   Please also note that for TCP we send both queries over the same
+   socket one after another.  This technically violates best practice
+   since the server is allowed to read the first query, respond, and
+   then close the socket (to service another client).  If the server
+   does this, then the remaining second query in the socket data buffer
+   will cause the server to send the client an RST which will arrive
+   asynchronously and the client's OS will likely tear down the socket
+   receive buffer resulting in a potentially short read and lost
+   response data.  This will force the client to retry the query again,
+   and this process may repeat until all servers and connection resets
+   are exhausted and then the query will fail.  It's not known if this
+   happens with any frequency in real DNS server implementations.  This
+   implementation should be corrected to use two sockets by default for
+   parallel queries.
+
+   The query stored in BUF of BUFLEN length is sent first followed by
+   the query stored in BUF2 of BUFLEN2 length.  Queries are sent
+   serially on the same socket.
+
+   Answers to the query are stored firstly in *ANSP up to a max of
+   *ANSSIZP bytes.  If more than *ANSSIZP bytes are needed and ANSCP
+   is non-NULL (to indicate that modifying the answer buffer is allowed)
+   then malloc is used to allocate a new response buffer and ANSCP and
+   ANSP will both point to the new buffer.  If more than *ANSSIZP bytes
+   are needed but ANSCP is NULL, then as much of the response as
+   possible is read into the buffer, but the results will be truncated.
+   When truncation happens because of a small answer buffer the DNS
+   packets header feild TC will bet set to 1, indicating a truncated
+   message and the rest of the socket data will be read and discarded.
+
+   Answers to the query are stored secondly in *ANSP2 up to a max of
+   *ANSSIZP2 bytes, with the actual response length stored in
+   *RESPLEN2.  If more than *ANSSIZP bytes are needed and ANSP2
+   is non-NULL (required for a second query) then malloc is used to
+   allocate a new response buffer, *ANSSIZP2 is set to the new buffer
+   size and *ANSP2_MALLOCED is set to 1.
+
+   The ANSP2_MALLOCED argument will eventually be removed as the
+   change in buffer pointer can be used to detect the buffer has
+   changed and that the caller should use free on the new buffer.
+
+   Note that the answers may arrive in any order from the server and
+   therefore the first and second answer buffers may not correspond to
+   the first and second queries.
+
+   It is not supported to call this function with a non-NULL ANSP2
+   but a NULL ANSCP.  Put another way, you can call send_vc with a
+   single unmodifiable buffer or two modifiable buffers, but no other
+   combination is supported.
+
+   It is the caller's responsibility to free the malloc allocated
+   buffers by detecting that the pointers have changed from their
+   original values i.e. *ANSCP or *ANSP2 has changed.
+
+   If errors are encountered then *TERRNO is set to an appropriate
+   errno value and a zero result is returned for a recoverable error,
+   and a less-than zero result is returned for a non-recoverable error.
+
+   If no errors are encountered then *TERRNO is left unmodified and
+   a the length of the first response in bytes is returned.  */
 static int
 send_vc(res_state statp,
 	const u_char *buf, int buflen, const u_char *buf2, int buflen2,

変数に ANSSIZP とか ANSSIZP2 というのがあってなんだこの名前と思ったのだけど プログラムを読んでみたところでは 2 は 2個目というアレで、 p はポインターを表す接尾辞、 そして answer と size から三文字ずつ取ってきて anssiz と。 answer → ans はともかく size の e はケチらんでもと思うけどどうなんだろか。

↓この辺りは見直しで変数の数が減っていて、 差分も + より - が多いのが特徴的。 問題の変数は以前のバグ修正(要確認)で入ったものらしく、 そのせいで記述がスッキリしてなかった面もあったのかも。 上二つのに比べると長さはそれほどではないけど 細かくコメントが入っているのが印象的。

@@ -639,11 +729,7 @@ send_vc(res_state statp,
 {
 	const HEADER *hp = (HEADER *) buf;
 	const HEADER *hp2 = (HEADER *) buf2;
-	u_char *ans = *ansp;
-	int orig_anssizp = *anssizp;
-	// XXX REMOVE
-	// int anssiz = *anssizp;
-	HEADER *anhp = (HEADER *) ans;
+	HEADER *anhp = (HEADER *) *ansp;
 	struct sockaddr *nsap = get_nsaddr (statp, ns);
 	int truncating, connreset, n;
 	/* On some architectures compiler might emit a warning indicating

2016年02月19日

■_

git の sha1_to_hex のやつ - odz buffer 40バイト+NUL文字用の1バイトで切り上げて50バイトらしい。 ありがとうございます。 にしてもなんとも大雑把というかw でもまあ仮に非static配列でスタックに置かれたとしても それなりに境界あわせするだろうから細かく考えても…といったところですか。

CVE-2015-7547 で色々賑わってますが(すでに過去形か?)、 応急処置的なあれこれはどーでもよくて、 バグの詳細の解説記事が(日本語記事では) でてこねえなあと思ってたらようやく IIJ Security Diary: CVE-2015-7547 glibcにおけるgetaddrinfoの脆弱性について

英文の記事と、旧ソースとパッチ見ながら調べ始めてたのはここだけのはなし。 Carlos O'Donell - [PATCH] CVE-2015-7547 --- glibc getaddrinfo() stack-based buffer overflo

そういえば、

sourceware.org Git - glibc.git/commit

CVE-2015-7547: getaddrinfo() stack-based buffer overflow (Bug 18665). 
 
* A stack-based buffer overflow was found in libresolv when invoked from 
 libnss_dns, allowing specially crafted DNS responses to seize control 
 of execution flow in the DNS client. The buffer overflow occurs in 
 the functions send_dg (send datagram) and send_vc (send TCP) for the 
 NSS module libnss_dns.so.2 when calling getaddrinfo with AF_UNSPEC 
 family. The use of AF_UNSPEC triggers the low-level resolver code to 
 send out two parallel queries for A and AAAA. A mismanagement of the 
 buffers used for those queries could result in the response of a query 
 writing beyond the alloca allocated buffer created by 
 _nss_dns_gethostbyname4_r. Buffer management is simplified to remove 
 the overflow. Thanks to the Google Security Team and Red Hat for 
 reporting the security impact of this issue, and Robert Holiday of 
 Ciena for reporting the related bug 18665. (CVE-2015-7547) 
 
See also: 
https://sourceware.org/ml/libc-alpha/2016-02/msg00416.html 
https://sourceware.org/ml/libc-alpha/2016-02/msg00418.html

この、stack-based ~ ってのにちょっと引っかかったんだけど、 例えば再帰のネストが深すぎたときのアレとは区別するために こういう言い方してるのね。 CWE - CWE-121: Stack-based Buffer Overflow (2.9) Stack buffer overflow - Wikipedia, the free encyclopedia 確かにスタックに置かれたバッファが溢れたのであって スタックそのものが溢れたわけではない(場合がほとんど) なのだろうし。

■_

なんだこのオライリー本 発酵の技法 | Sandor Ellix Katz, 水原 文 | 本-通販 | Amazon.co.jp Books: 新刊・近刊情報

2016年02月18日

■_

2016年02月17日

■_

2016年02月16日

■_

2016年02月15日

■_

2016年02月14日

■_

2016年02月13日

■_

スティーブズ(4) (ビッグコミックス)
スティーブズ(4) (ビッグコミックス) 4巻にも(ちょろっとだけど)エストリッジ出てた○| ̄|_

■_

2016年02月12日

■_

ドン・エストリッジきたーっw >「スティーブズ」(雑誌連載の方ね。単行本じゃなく)

Philip Don Estridge - Wikipedia, the free encyclopedia Philip "Don" Estridge

■_

■_ なぞのすくりぷと

bi = []
bi[0] = 0
bi[1] = 0
bi[2] = 0
bi[3] = 0
bi[4] = bi[0] ^ 1
(5..18).each do |n|
  bi[n] = bi[n-4] ^ bi[n-1]
end

puts bi.each_cons(4).collect{|e| e.join }.sort

結果。

0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111

2016年02月11日

■_

Hacker Newsのコメント欄で紹介される数の多い30冊 - YAMDAS現更新履歴 のを見ると、じゃあ翻訳本が出てないのってどんな本なんだろうとか 気になりませんか? わたしはとても気になります :) というわけで抜き出してみるとこんなかんじでしょうか。

二つ三つ読んでみたいのが。

■_

■_

s/20/40/ - odz buffer ちょっと苦しいけど「元データ」が「20」バイト つーことで :)

■_

Rust は様子見してるだけなんですが Rust vs. C++: Fine-grained Performance

Rust vs. C++: Fine-grained Performance

C++:

int main(int argc, char** argv) {
    std::string name = (argc > 1) ? argv[1] : "/usr/share/dict/words";
    std::ifstream fs;
    std::istream& file = (name == "-") ? std::cin : (fs.open(name), fs);
    if (!file)
        return std::cerr << "file open failed: \"" << name << "\"\n", 1;

And Rust:

fn main() {
    let fname = &*env::args().nth(1).unwrap_or("/usr/share/dict/words".into());
    let stdin = io::stdin();
    let file: Box<Read> = match fname {
        "-" => Box::new(stdin.lock()),
        _ => Box::new(fs::File::open(fname).unwrap_or_else(|err| {
                 writeln!(io::stderr(), "{}: \"{}\"", err, fname).unwrap();
                 process::exit(1);
             }))
    };

Rust のプログラムに対する引数の処理の書き方が面白いな。と。


一つ前へ 2016年2月(上旬)
一つ後へ 2016年2月(下旬)

ホームへ


リンクはご自由にどうぞ

メールの宛先はこちらkbk AT kt DOT rim DOT or DOT jp