进程凭据是指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即可: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