unix domain socket進程憑據


進程憑據是指unix domain socket(AF_UNIX)發送方的pid,uid,gid信息。

只能是AF_UNIX,不能是AF_INET的原因很簡單,AF_INET可能都不在同一台機器上,pid,uid,gid沒有意義。

在以下的內容中,socket server作為接收方,socket client作為發送方,當然反過來也沒有問題,不過本文以這個為例。

有兩種方法傳遞進程憑據:

1、SO_PEERCRED

man pages中的解釋:

SO_PEERCRED
              Return the credentials of the foreign process connected to
              this socket.  This is possible only for connected AF_UNIX
              stream sockets and AF_UNIX stream and datagram socket pairs
              created using socketpair(2); see unix(7).  The returned
              credentials are those that were in effect at the time of the
              call to connect(2) or socketpair(2).  The argument is a ucred
              structure; define the _GNU_SOURCE feature test macro to obtain
              the definition of that structure from <sys/socket.h>.  This
              socket option is read-only.

在socket server端調用如下代碼:

struct ucred cred;
socklen_t len;
len = sizeof(struct ucred);
// ......, after accept
getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &cred, &len);
printf("Credentials from SO_PEERCRED: pid=%d, uid=%d, gid=%d\n", cred.pid, cred.uid, cred.gid);
注意編譯時先#define _GNU_SOURCE,再#include <sys/socket.h>,否則struct ucred的定義找不到的;

需要對client_fd調用getsockopt,如果對listen_fd調用的話,每次都是socket server自己的pid,uid,gid,沒啥用處;

得到的pid,uid,gid是socket client在connect或者socketpair時的值;

在socket client端無需特殊的操作,也無需發送消息數據。


2、SO_PASSCRED + SCM_CREDENTIALS

man pages上的解釋:

SO_PASSCRED
              Enables the receiving of the credentials of the sending
              process in an ancillary message.  When this option is set and
              the socket is not yet connected a unique name in the abstract
              namespace will be generated automatically.  Expects an integer
              boolean flag.
SCM_CREDENTIALS
              Send or receive UNIX credentials.  This can be used for
              authentication.  The credentials are passed as a struct ucred
              ancillary message.  Thus structure is defined in
              <sys/socket.h> as follows:

                  struct ucred {
                      pid_t pid;    /* process ID of the sending process */
                      uid_t uid;    /* user ID of the sending process */
                      gid_t gid;    /* group ID of the sending process */
                  };

              Since glibc 2.8, the _GNU_SOURCE feature test macro must be
              defined (before including any header files) in order to obtain
              the definition of this structure.

              The credentials which the sender specifies are checked by the
              kernel.  A process with effective user ID 0 is allowed to
              specify values that do not match its own.  The sender must
              specify its own process ID (unless it has the capability
              CAP_SYS_ADMIN), its user ID, effective user ID, or saved set-
              user-ID (unless it has CAP_SETUID), and its group ID,
              effective group ID, or saved set-group-ID (unless it has
              CAP_SETGID).  To receive a struct ucred message the
              SO_PASSCRED option must be enabled on the socket.
socket client同樣無需特殊的操作,不過需要sendmsg之后,接收端才能夠得到憑據,憑據數據由內核填充到消息結構體的控制數據中,如果發送方想自己填充也可以,偽造的數據會導致sendmsg失敗。除非有root權限,才可能偽造pid,uid,gid信息。

socket client構建消息結構體的代碼為:

// 權限數據由內核填充還是程序填充
#define AUTO_FILL_DATA

    struct msghdr msgh;
    struct iovec iov;
    int data = 0xbeef;
#ifndef AUTO_FILL_DATA
    union {
        struct cmsghdr cmh;
        char   control[CMSG_SPACE(sizeof(struct ucred))];
                        /* Space large enough to hold a ucred structure */
    } control_un;
    struct cmsghdr *cmhp;
    struct ucred *ucp;
#endif

    /* On Linux, we must transmit at least 1 byte of real data in
       order to send ancillary data */
    msgh.msg_iov = &iov;
    msgh.msg_iovlen = 1;
    iov.iov_base = &data;
    iov.iov_len = sizeof(int);
    msgh.msg_name = NULL;
    msgh.msg_namelen = 0;
#ifdef AUTO_FILL_DATA
    msgh.msg_control = NULL;
    msgh.msg_controllen = 0;
#else
    msgh.msg_control = control_un.control;
    msgh.msg_controllen = sizeof(control_un.control);
    cmhp = CMSG_FIRSTHDR(&msgh);
    cmhp->cmsg_len = CMSG_LEN(sizeof(struct ucred));
    cmhp->cmsg_level = SOL_SOCKET;
    cmhp->cmsg_type = SCM_CREDENTIALS;
    ucp = (struct ucred *) CMSG_DATA(cmhp);
    ucp->pid = getpid();
    ucp->uid = getuid();
    ucp->gid = getgid();
#endif
socket server端,需要設置期望接收的消息的結構體:

    struct msghdr msgh;
    struct iovec iov;
    int data;
    struct ucred *ucredp;
    struct cmsghdr *cmhp;
    union {
        struct cmsghdr cmh;
        char   control[CMSG_SPACE(sizeof(struct ucred))];
                        /* Space large enough to hold a ucred structure */
    } control_un;

    control_un.cmh.cmsg_len = CMSG_LEN(sizeof(struct ucred));
    control_un.cmh.cmsg_level = SOL_SOCKET;
    control_un.cmh.cmsg_type = SCM_CREDENTIALS;
    msgh.msg_control = control_un.control;
    msgh.msg_controllen = sizeof(control_un.control);
    msgh.msg_iov = &iov;
    msgh.msg_iovlen = 1;
    iov.iov_base = &data;
    iov.iov_len = sizeof(int);
    msgh.msg_name = NULL;
    msgh.msg_namelen = 0;
在accept之后,對client_fd調用:

setsockopt(client_fd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval))
其中int optval = 1;

然后使用如下代碼接收消息,並獲取控制信息,即憑據:

    if (recvmsg(client_fd, &msgh, 0) <= 0)
    {
        printf("ERROR recvmsg\n");
        goto out;
    }
    cmhp = CMSG_FIRSTHDR(&msgh);
    if (cmhp == NULL || cmhp->cmsg_len != CMSG_LEN(sizeof(struct ucred)))
    {
        printf("bad cmsg header / message length");
        goto out;
    }
    if (cmhp->cmsg_level != SOL_SOCKET)
    {
        printf("cmsg_level != SOL_SOCKET");
        goto out;
    }
    if (cmhp->cmsg_type != SCM_CREDENTIALS)
    {
        printf("cmsg_type != SCM_CREDENTIALS");
        goto out;
    }

    ucredp = (struct ucred *) CMSG_DATA(cmhp);
    printf("Received credentials pid=%d, uid=%d, gid=%d\n",
                ucredp->pid, ucredp->uid, ucredp->gid);

和第一種方式不同,這里也可以在accept之前,對listen_fd調用
setsockopt(listen_fd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval))

來獲取client_fd上的憑據。但是,這種方法不保險,有些內核是不支持的,比如android goldfish 3.4。

因為在accept的時候,client_fd沒有從listen_fd繼承相關的標志位,所以會不支持。

在內核中添加這個patch即可:http://patchwork.ozlabs.org/patch/289624/



參考:

https://sourceware.org/bugzilla/show_bug.cgi?id=6545

http://man7.org/tlpi/code/online/dist/sockets/scm_cred_recv.c.html
http://man7.org/tlpi/code/online/dist/sockets/scm_cred_send.c.html


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com