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(); #endifsocket 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即可:https://patchwork.ozlabs.org/patch/289624/
參考:
https://sourceware.org/bugzilla/show_bug.cgi?id=6545
https://man7.org/tlpi/code/online/dist/sockets/scm_cred_recv.c.html
https://man7.org/tlpi/code/online/dist/sockets/scm_cred_send.c.html
最後更新:2017-10-26 17:34:40