仙石浩明の日記

2006年4月15日

getnameinfo のバグ? (1)

UNIX で TCP接続を accept したとき、そのソケット sd に対して

struct sockaddr_storage ss;
struct sockaddr *from = (struct sockaddr*)&ss;
socklen_t fromlen = sizeof(ss);
getpeername(sd, from, &fromlen);

などとすれば、接続元のアドレスが from に返る。 UNIXドメインソケットを listen し accept した場合は、

from->sa_family ← AF_UNIX
fromlen ← 2

という値が返るようだ。

ところが、
この返り値を getnameinfo に与えると、 fromlen で指定したサイズを超えて UNIX ドメインソケットのファイル名を読もうとする 問題があるようだ。

たとえば次のようなテストプログラムを書いてみる:

#include <stdio.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/un.h>
#define STRMAX  256
main() {
    struct sockaddr *sa;
    socklen_t salen;
    struct sockaddr_un sun;
    char name[STRMAX+1];
    char serv[STRMAX+1];
    int err;
    bzero(name, STRMAX+1);
    bzero(serv, STRMAX+1);
    sa = (struct sockaddr*)&sun;
    salen = sizeof(sun);
    sun.sun_family = AF_UNIX;
    strcpy(sun.sun_path, "/tmp/sock");
    salen = sizeof(sa_family_t);
    err = getnameinfo(sa, salen, name, STRMAX, serv, STRMAX, 0);
    printf("salen: %d, err: %d, name: '%s', serv: '%s'\n",
           salen, err, name, serv);
}

salen は 2 であるので、 sun.sun_path の部分の値は無視すべきであるのに、 serv には、"/tmp/sock" の値が返ってしまう。 sun.sun_path の部分が初期化されていなければ、 不定値が返るだろう。

実際、stone では struct sockaddr_storage を初期化せずに getpeername を呼び出して長さ 2 の struct sockaddr_un を受け取り、 これを getnameinfo の引数として与えると、 dserv に不定値が返ってきてしまった。

本来ならば、getnameinfo は長さ 0 のパス名を返すか、 あるいは UNIX ドメインソケットは扱わない、 ないしは長さが異常ということで EAI_FAMILY を返すか、 どちらかであるべきではなかろうか。 少なくとも glibc-2.3.5 と、glibc-2.2.5 では、 この問題があることを確認した。

とりあえず、stone は AF_UNIX のときは getnameinfo を呼ばずに sun_path を dserv へコピーするように修正した。

$Id: stone.c,v 2.2.2.19 2006/04/14 23:35:18 hiroaki_sengoku Exp $

Filed under: stone 開発日記 — hiroaki_sengoku @ 09:58

No Comments »

No comments yet.

RSS feed for comments on this post.

Leave a comment