Hi,

While running testsuite on third-party program, I found some weird
behaviour on us side regarding socketpair(2) and getpeereid(3).

I ported the unit test to C (it was Rust) to check more easily.

It just creates an UNIX domain socket using socketpair(2), and next
check the euid/egid of the peer connected socket. The expected result is
that euid of the peer is the euid of the process.

$ cat test.c
#include <sys/types.h>
#include <sys/socket.h>

#include <err.h>
#include <stdlib.h>
#include <stdio.h>

int
main(int argc, char *argv[])
{
        int sv[2];
        uid_t euid;
        gid_t egid;

        if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1)
                err(EXIT_FAILURE, "socketpair");

        if (getpeereid(sv[0], &euid, &egid) == -1)
                err(EXIT_FAILURE, "getpeereid");

        printf("euid = %d\negid = %d\n", euid, egid);

        return EXIT_SUCCESS;
}

But getpeereuid(3) returns ENOTCONN instead of printing peer information.

$ cc test.c && ./a.out
a.out: getpeereid: Socket is not connected


I dig a bit inside kernel.

getpeereid(3) is implemented using getsockopt(SO_PEERCRED) inside libc.
getsockopt(2) code returns the peer information from `unp->unp_connid'
if unp_flag UNP_FEIDS is set.

kern/uipc_socket.c
  1870                  case SO_PEERCRED:
  1871                          if (so->so_proto->pr_protocol == AF_UNIX) {
  1872                                  struct unpcb *unp = sotounpcb(so);
  1873
  1874                                  if (unp->unp_flags & UNP_FEIDS) {
  1875                                          m->m_len = 
sizeof(unp->unp_connid);
  1876                                          memcpy(mtod(m, caddr_t),
  1877                                              &(unp->unp_connid), 
m->m_len);
  1878                                          break;
  1879                                  }
  1880                                  return (ENOTCONN);
  1881                          }
  1882                          return (EOPNOTSUPP);


The peer information is setted inside `unp_connect()' function.

kern/uipc_usrreq.c
   517                  if (unp2->unp_addr)
   518                          unp3->unp_addr =
   519                              m_copym(unp2->unp_addr, 0, M_COPYALL, 
M_NOWAIT);
   520                  unp3->unp_connid.uid = p->p_ucred->cr_uid;
   521                  unp3->unp_connid.gid = p->p_ucred->cr_gid;
   522                  unp3->unp_connid.pid = p->p_p->ps_pid;
   523                  unp3->unp_flags |= UNP_FEIDS;
   524                  so2 = so3;
   525                  if (unp2->unp_flags & UNP_FEIDSBIND) {
   526                          unp->unp_connid = unp2->unp_connid;
   527                          unp->unp_flags |= UNP_FEIDS;
   528                  }
   529          }
   530          error = unp_connect2(so, so2);

When using socketpair(2), it is `soconnect2()' which is called, so the
unp_connid struct is never setted.

kern/uipc_syscalls.c
   458          error = socreate(SCARG(uap, domain), &so1, type, SCARG(uap, 
protocol));
   459          if (error)
   460                  return (error);
   461          error = socreate(SCARG(uap, domain), &so2, type, SCARG(uap, 
protocol));
   462          if (error)
   463                  goto free1;
   464
   465          error = soconnect2(so1, so2);
   466          if (error != 0)
   467                  goto free2;


Could I have confirmation if it is a bug or not ? I am unsure if
socketpair(2) should set the peer information or not. But at least,
ENOTCONN is wrong: socketpair(2) returns connected sockets.

Thanks.
-- 
Sebastien Marie

Reply via email to