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)之所以高效
487 3
|
6月前
|
监控 安全 Unix
UNIX域套接字(Unix Domain Socket)在安全性和隐私性
UNIX域套接字(Unix Domain Socket)在安全性和隐私性
285 2
|
6月前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
6月前
|
Arthas 测试技术
这个错误提示表明Arthas无法打开目标进程的socket文
【1月更文挑战第11天】【1月更文挑战第55篇】这个错误提示表明Arthas无法打开目标进程的socket文
883 4
|
6月前
|
算法 Linux 调度
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
465 1
xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
|
6月前
|
监控 Unix Linux
socket监控进程,并对程序执行有关操作。
socket监控进程,并对程序执行有关操作。
|
6月前
|
Arthas 测试技术
错误提示表明Arthas无法打开目标进程的socket文件
错误提示表明Arthas无法打开目标进程的socket文件
100 2
|
4月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
4月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
176 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
3月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。

相关实验场景

更多
下一篇
无影云桌面