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

目录
相关文章
|
6月前
|
网络协议 安全 Unix
UNIX域套接字(Unix Domain Socket,UDS)之所以高效
UNIX域套接字(Unix Domain Socket,UDS)之所以高效
459 3
|
6月前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
6月前
|
监控 安全 Unix
UNIX域套接字(Unix Domain Socket)在安全性和隐私性
UNIX域套接字(Unix Domain Socket)在安全性和隐私性
262 2
|
6月前
|
Arthas 测试技术
这个错误提示表明Arthas无法打开目标进程的socket文
【1月更文挑战第11天】【1月更文挑战第55篇】这个错误提示表明Arthas无法打开目标进程的socket文
852 4
|
6月前
|
算法 Linux 调度
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
447 1
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
|
6月前
|
监控 Unix Linux
socket监控进程,并对程序执行有关操作。
socket监控进程,并对程序执行有关操作。
|
6月前
|
Arthas 测试技术
错误提示表明Arthas无法打开目标进程的socket文件
错误提示表明Arthas无法打开目标进程的socket文件
98 2
|
11月前
|
网络协议 Unix Go
Go unix domain socket通信
Go unix domain socket通信
|
6月前
|
Unix Shell Linux
在Unix/Linux操作系统中,Shell脚本广泛用于自动化任务
在Unix/Linux操作系统中,Shell脚本广泛用于自动化任务
69 2
|
29天前
|
Unix 物联网 大数据
操作系统的演化与比较:从Unix到Linux
本文将探讨操作系统的历史发展,重点关注Unix和Linux两个主要的操作系统分支。通过分析它们的起源、设计哲学、技术特点以及在现代计算中的影响,我们可以更好地理解操作系统在计算机科学中的核心地位及其未来发展趋势。