UNIX(4)

πŸŽ€ NAME πŸŽ€

okay so bestie, this is page UNIX(4) from the Kernel Interfaces Manual, and the star of the show is unix β€” the UNIX-domain protocol family. she's local, she's fast, she's iconic, and she's about to change your whole socket game. πŸ‘‘


✨ SYNOPSIS ✨

#include <sys/types.h>
#include <sys/un.h>

πŸ¦‹ DESCRIPTION πŸ¦‹

The Unix-domain protocol family is literally a whole collection of protocols, and what they're giving is local (on-machine) interprocess communication β€” same box, no network, no drama β€” all through the completely normal socket(2) mechanisms you already know and love. πŸ’…

She supports three socket types, periodt: SOCK_STREAM, SOCK_SEQPACKET, and SOCK_DGRAM. And for addressing? File system pathnames. That's the whole vibe. πŸ—‚οΈ


🌸 ADDRESSING 🌸

Unix-domain addresses are variable-length file system pathnames, and they max out at at most 104 characters. Not a single character more, bestie.

The include file <sys/un.h> defines this address:

struct sockaddr_un {
        u_char  sun_len;
        u_char  sun_family;
        char    sun_path[104];
};

When you bind a name to a Unix-domain socket with bind(2), a socket file gets created in the file system. And here's the part that gets people every time: that file is not removed when the socket is closed β€” you have to use unlink(2) to remove it yourself. She does not clean up after herself, no cap. 🧹

The length of a Unix-domain address β€” which bind(2) and connect(2) both need β€” can be calculated with the macro SUN_LEN(), defined in <sys/un.h>. For SUN_LEN() to work, the sun_path field must be terminated by a NUL character, but heads up: that terminating NUL is not part of the address. πŸ™ˆ

Also, the Unix-domain protocol family does not support broadcast addressing, and there's no form of β€œwildcard” matching on incoming messages either. Every address is an absolute- or relative-pathname pointing at some other Unix-domain socket. Normal file system access-control mechanisms still apply when you're referencing pathnames β€” like, for example, the destination of a connect(2) or sendto(2) has to be writable. πŸ”


πŸ’Œ CONTROL MESSAGES πŸ’Œ

Unix-domain sockets can pass around Unix file descriptors and process credentials, using the msg_control field in the msg argument to sendmsg(2) and recvmsg(2). The items you're passing get described with a struct cmsghdr, which lives in the include file <sys/socket.h>. 🎁

To send file descriptors, the type of the message is SCM_RIGHTS, and the data portion of the message is an array of integers representing the file descriptors you're passing. The number of descriptors being passed is defined by the length field of the message β€” and that length field is the sum of the size of the header plus the size of the array of file descriptors. Math, but make it sockets. ✨

The descriptor you receive is a duplicate of the sender's descriptor, as if it were created via dup(fd) or fcntl(fd, F_DUPFD_CLOEXEC, 0) β€” which one depends on whether MSG_CMSG_CLOEXEC was passed in the recvmsg(2) call. Descriptors that are awaiting delivery, or that are purposely not received, get automatically closed by the system when the destination socket is closed. the system handles it, don't stress. πŸ’–

Credentials of the sending process can be transmitted explicitly using a control message of type SCM_CREDS, with a data portion of type struct cmsgcred, defined in <sys/socket.h> as follows:

struct cmsgcred {
  pid_t cmcred_pid;             /* PID of sending process */
  uid_t cmcred_uid;             /* real UID of sending process */
  uid_t cmcred_euid;            /* effective UID of sending process */
  gid_t cmcred_gid;             /* real GID of sending process */
  short cmcred_ngroups;         /* number of groups */
  gid_t cmcred_groups[CMGROUP_MAX];     /* groups */
};

The sender should pass a zeroed buffer, which will be filled in by the system. 🫢

The group list is truncated to at most CMGROUP_MAX GIDs.

⚠️ And now the big one, listen up: the process ID cmcred_pid should not be looked up (such as via the KERN_PROC_PID sysctl) for making security decisions. The sending process could have exited and its process ID already been reused for a brand new process. so do not trust it, fr fr. πŸ’€

πŸ“ SOCKET OPTIONS πŸ“

UNIX domain sockets support a number of socket options for the options level SOL_LOCAL, which you can set with setsockopt(2) and test with getsockopt(2):

  • LOCAL_CREDS β€” this option may be enabled on a SOCK_DGRAM, SOCK_SEQPACKET, or SOCK_STREAM socket. It gives the receiver a mechanism to receive the credentials of the process calling write(2), send(2), sendto(2), or sendmsg(2), delivered as a recvmsg(2) control message. The msg_control field in the msghdr structure points to a buffer containing a cmsghdr structure followed by a variable length sockcred structure, defined in <sys/socket.h> as follows:
struct sockcred {
  uid_t sc_uid;         /* real user id */
  uid_t sc_euid;        /* effective user id */
  gid_t sc_gid;         /* real group id */
  gid_t sc_egid;        /* effective group id */
  int   sc_ngroups;     /* number of supplemental groups */
  gid_t sc_groups[1];   /* variable length */
};

The current implementation truncates the group list to at most CMGROUP_MAX groups.

The SOCKCREDSIZE() macro computes the size of the sockcred structure for a specified number of groups. The cmsghdr fields have the following values:

cmsg_len = CMSG_LEN(SOCKCREDSIZE(ngroups))
cmsg_level = SOL_SOCKET
cmsg_type = SCM_CREDS

On SOCK_STREAM and SOCK_SEQPACKET sockets, credentials are passed only on the first read from a socket β€” then the system clears the option on the socket. one and done, bestie. ✨

This option and the above explicit struct cmsgcred both use the same value SCM_CREDS but incompatible control messages. If this option is enabled and the sender attached an SCM_CREDS control message with a struct cmsgcred, it will be discarded and a struct sockcred will be included instead. she swaps it out, no consultation. πŸ”„

Many setuid programs will write(2) data that is at least partially controlled by the invoker β€” error messages and stuff like that. So a message accompanied by a particular sc_euid value should not be trusted as being from that user. ngl that's a whole security lesson in one sentence. 😭

  • LOCAL_CONNWAIT β€” used with SOCK_STREAM sockets, this option causes the connect(2) function to block until accept(2) has been called on the listening socket. she'll wait right there for you. 🫢
  • LOCAL_PEERCRED β€” requested via getsockopt(2) on a SOCK_STREAM socket, this returns the credentials of the remote side. They arrive in the form of a filled-in xucred structure, defined in <sys/ucred.h> as follows:
struct xucred {
  u_int cr_version;             /* structure layout version */
  uid_t cr_uid;                 /* effective user id */
  short cr_ngroups;             /* number of groups */
  gid_t cr_groups[XU_NGROUPS];  /* groups */
};

The cr_version fields should be checked against the XUCRED_VERSION define. πŸ‘€

The credentials presented to the server (the listen(2) caller) are those of the client when it called connect(2); the credentials presented to the client (the connect(2) caller) are those of the server when it called listen(2). This mechanism is reliable β€” there is no way for either party to influence the credentials presented to its peer except by calling the appropriate system call (e.g., connect(2) or listen(2)) under different effective credentials. no sneaky business allowed, periodt. πŸ’…

To reliably obtain peer credentials on a SOCK_DGRAM socket, refer to the LOCAL_CREDS socket option. (datagram girlies, she got you πŸ’œ)


🌈 SEE ALSO 🌈


and that's the whole page, bestie!! πŸ¦‹βœ¨ this one came from Debian, dated August 3, 2020 β€” so go forth, bind(2) responsibly, and remember to unlink(2) your socket files. mwah. πŸ’–πŸ’…πŸŽ€

πŸ“¦ where this page comes from