2020-08-10 21:53:51

by Kees Cook

[permalink] [raw]
Subject: [PATCH] selftests/socket: Introduce simple SCM_RIGHTS tests

This makes sure that simple SCM_RIGHTS fd passing works as expected, to
avoid any future regressions. This is mostly code from Michael Kerrisk's
examples on how to set up and perform fd passing with SCM_RIGHTS. Add
a test script and wire it up to the selftests.

Signed-off-by: Kees Cook <[email protected]>
---
FYI, this also relicenses Michael's code (with his permission) from
GPL3+ to GPL2+, who is on CC to publicly confirm. :) Thank you Michael!
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/socket/.gitignore | 4 +
tools/testing/selftests/socket/Makefile | 11 ++
tools/testing/selftests/socket/hello.txt | 1 +
tools/testing/selftests/socket/scm_rights.h | 40 +++++
.../selftests/socket/scm_rights_recv.c | 168 ++++++++++++++++++
.../selftests/socket/scm_rights_send.c | 144 +++++++++++++++
.../selftests/socket/simple_scm_rights.sh | 30 ++++
tools/testing/selftests/socket/unix_sockets.c | 88 +++++++++
tools/testing/selftests/socket/unix_sockets.h | 23 +++
10 files changed, 510 insertions(+)
create mode 100644 tools/testing/selftests/socket/.gitignore
create mode 100644 tools/testing/selftests/socket/Makefile
create mode 100644 tools/testing/selftests/socket/hello.txt
create mode 100644 tools/testing/selftests/socket/scm_rights.h
create mode 100644 tools/testing/selftests/socket/scm_rights_recv.c
create mode 100644 tools/testing/selftests/socket/scm_rights_send.c
create mode 100755 tools/testing/selftests/socket/simple_scm_rights.sh
create mode 100644 tools/testing/selftests/socket/unix_sockets.c
create mode 100644 tools/testing/selftests/socket/unix_sockets.h

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index e03bc15ce731..97e155596660 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -51,6 +51,7 @@ TARGETS += rtc
TARGETS += seccomp
TARGETS += sigaltstack
TARGETS += size
+TARGETS += socket
TARGETS += sparc64
TARGETS += splice
TARGETS += static_keys
diff --git a/tools/testing/selftests/socket/.gitignore b/tools/testing/selftests/socket/.gitignore
new file mode 100644
index 000000000000..bc9a892956b0
--- /dev/null
+++ b/tools/testing/selftests/socket/.gitignore
@@ -0,0 +1,4 @@
+unix_sockets.o
+scm_rights_send
+scm_rights_recv
+scm_rights
diff --git a/tools/testing/selftests/socket/Makefile b/tools/testing/selftests/socket/Makefile
new file mode 100644
index 000000000000..3eb7ef0db997
--- /dev/null
+++ b/tools/testing/selftests/socket/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0+
+TEST_PROGS := simple_scm_rights.sh
+TEST_GEN_PROGS_EXTENDED := scm_rights_send scm_rights_recv
+
+include ../lib.mk
+
+$(OUTPUT)/unix_sockets.o: unix_sockets.h
+$(OUTPUT)/scm_rights_recv: $(OUTPUT)/unix_sockets.o scm_rights.h
+$(OUTPUT)/scm_rights_send: $(OUTPUT)/unix_sockets.o scm_rights.h
+
+EXTRA_CLEAN += $(OUTPUT)/unix_sockets.o $(OUTPUT)/scm_rights
diff --git a/tools/testing/selftests/socket/hello.txt b/tools/testing/selftests/socket/hello.txt
new file mode 100644
index 000000000000..e965047ad7c5
--- /dev/null
+++ b/tools/testing/selftests/socket/hello.txt
@@ -0,0 +1 @@
+Hello
diff --git a/tools/testing/selftests/socket/scm_rights.h b/tools/testing/selftests/socket/scm_rights.h
new file mode 100644
index 000000000000..4501a46bf1be
--- /dev/null
+++ b/tools/testing/selftests/socket/scm_rights.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * scm_rights.h
+ *
+ * Copyright (C) Michael Kerrisk, 2020.
+ *
+ * Header file used by scm_rights_send.c and scm_rights_recv.c.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "unix_sockets.h"
+
+#define SOCK_PATH "scm_rights"
+
+#define errExit(fmt, ...) do { \
+ fprintf(stderr, fmt, ## __VA_ARGS__); \
+ fprintf(stderr, ": %s\n", strerror(errno)); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+#define fatal(str) errExit("%s\n", str)
+
+#define usageErr(fmt, ...) do { \
+ fprintf(stderr, "Usage: "); \
+ fprintf(stderr, fmt, ## __VA_ARGS__); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+static bool debugging;
+
+#define debug(fmt, ...) do { \
+ if (debugging) \
+ fprintf(stderr, fmt, ## __VA_ARGS__); \
+ } while (0)
diff --git a/tools/testing/selftests/socket/scm_rights_recv.c b/tools/testing/selftests/socket/scm_rights_recv.c
new file mode 100644
index 000000000000..4c916e43c319
--- /dev/null
+++ b/tools/testing/selftests/socket/scm_rights_recv.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * scm_rights_recv.c
+ *
+ * Copyright (C) Michael Kerrisk, 2020.
+ *
+ * Used in conjunction with scm_rights_send.c to demonstrate passing of
+ * file descriptors via a UNIX domain socket.
+ *
+ * This program receives a file descriptor sent to a UNIX domain socket.
+ *
+ * Usage is as shown in the usageErr() call below.
+ *
+ * File descriptors can be exchanged over stream or datagram sockets. This
+ * program uses stream sockets by default; the "-d" command-line option
+ * specifies that datagram sockets should be used instead.
+ *
+ * This program is Linux-specific.
+ */
+#include "scm_rights.h"
+
+#define BUF_SIZE 100
+
+int
+main(int argc, char *argv[])
+{
+ int data, lfd, sfd, fd, opt;
+ ssize_t nr;
+ bool useDatagramSocket;
+ struct msghdr msgh;
+ struct iovec iov;
+
+ /* Allocate a char array of suitable size to hold the ancillary data.
+ * However, since this buffer is in reality a 'struct cmsghdr', use a
+ * union to ensure that it is aligned as required for that structure.
+ * Alternatively, we could allocate the buffer using malloc(), which
+ * returns a buffer that satisfies the strictest alignment
+ * requirements of any type
+ */
+
+ union {
+ char buf[CMSG_SPACE(sizeof(int))];
+ /* Space large enough to hold an 'int' */
+ struct cmsghdr align;
+ } controlMsg;
+ struct cmsghdr *cmsgp; /* Pointer used to iterate through
+ * headers in ancillary data
+ */
+
+ /* Parse command-line options */
+
+ useDatagramSocket = false;
+
+ while ((opt = getopt(argc, argv, "dD")) != -1) {
+ switch (opt) {
+ case 'd':
+ useDatagramSocket = true;
+ break;
+
+ default:
+ usageErr("%s [-dD]\n"
+ " -D enable debugging\n"
+ " -d use datagram socket\n", argv[0]);
+ }
+ }
+
+ /* Create socket bound to a well-known address. In the case where
+ * we are using stream sockets, also make the socket a listening
+ * socket and accept a connection on the socket.
+ */
+
+ if (remove(SOCK_PATH) == -1 && errno != ENOENT)
+ errExit("remove-%s", SOCK_PATH);
+
+ if (useDatagramSocket) {
+ sfd = unixBind(SOCK_PATH, SOCK_DGRAM);
+ if (sfd == -1)
+ errExit("unixBind");
+
+ } else {
+ lfd = unixBind(SOCK_PATH, SOCK_STREAM);
+ if (lfd == -1)
+ errExit("unixBind");
+
+ if (listen(lfd, 5) == -1)
+ errExit("listen");
+
+ sfd = accept(lfd, NULL, NULL);
+ if (sfd == -1)
+ errExit("accept");
+ }
+
+ /* The 'msg_name' field can be set to point to a buffer where the
+ * kernel will place the address of the peer socket. However, we don't
+ * need the address of the peer, so we set this field to NULL.
+ */
+
+ msgh.msg_name = NULL;
+ msgh.msg_namelen = 0;
+
+ /* Set fields of 'msgh' to point to buffer used to receive the (real)
+ * data read by recvmsg()
+ */
+
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+ iov.iov_base = &data;
+ iov.iov_len = sizeof(int);
+
+ /* Set 'msgh' fields to describe the ancillary data buffer */
+
+ msgh.msg_control = controlMsg.buf;
+ msgh.msg_controllen = sizeof(controlMsg.buf);
+
+ /* Receive real plus ancillary data */
+
+ nr = recvmsg(sfd, &msgh, 0);
+ if (nr == -1)
+ errExit("recvmsg");
+ debug("recvmsg() returned %ld\n", (long) nr);
+
+ if (nr > 0)
+ debug("Received data = %d\n", data);
+
+ /* Get the address of the first 'cmsghdr' in the received
+ * ancillary data
+ */
+
+ cmsgp = CMSG_FIRSTHDR(&msgh);
+
+ /* Check the validity of the 'cmsghdr' */
+
+ if (cmsgp == NULL || cmsgp->cmsg_len != CMSG_LEN(sizeof(int)))
+ fatal("bad cmsg header / message length");
+ if (cmsgp->cmsg_level != SOL_SOCKET)
+ fatal("cmsg_level != SOL_SOCKET");
+ if (cmsgp->cmsg_type != SCM_RIGHTS)
+ fatal("cmsg_type != SCM_RIGHTS");
+
+ /* The data area of the 'cmsghdr' is an 'int' (a file descriptor);
+ * copy that integer to a local variable. (The received file descriptor
+ * is typically a different file descriptor number than was used in the
+ * sending process.)
+ */
+
+ memcpy(&fd, CMSG_DATA(cmsgp), sizeof(int));
+ debug("Received FD %d\n", fd);
+
+ /* Having obtained the file descriptor, read the file's contents and
+ * print them on standard output
+ */
+
+ for (;;) {
+ char buf[BUF_SIZE];
+ ssize_t numRead;
+
+ numRead = read(fd, buf, BUF_SIZE);
+ if (numRead == -1)
+ errExit("read");
+
+ if (numRead == 0)
+ break;
+
+ write(STDOUT_FILENO, buf, numRead);
+ }
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/tools/testing/selftests/socket/scm_rights_send.c b/tools/testing/selftests/socket/scm_rights_send.c
new file mode 100644
index 000000000000..c5718d10a80d
--- /dev/null
+++ b/tools/testing/selftests/socket/scm_rights_send.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * scm_rights_send.c
+ *
+ * Copyright (C) Michael Kerrisk, 2020.
+ *
+ * Used in conjunction with scm_rights_recv.c to demonstrate passing of
+ * file descriptors via a UNIX domain socket.
+ *
+ * This program sends a file descriptor to a UNIX domain socket.
+ *
+ * Usage is as shown in the usageErr() call below.
+ *
+ * File descriptors can be exchanged over stream or datagram sockets. This
+ * program uses stream sockets by default; the "-d" command-line option
+ * specifies that datagram sockets should be used instead.
+ *
+ * This program is Linux-specific.
+ */
+#include "scm_rights.h"
+
+int
+main(int argc, char *argv[])
+{
+ int data, sfd, opt, fd;
+ ssize_t ns;
+ bool useDatagramSocket;
+ struct msghdr msgh;
+ struct iovec iov;
+
+ /* Allocate a char array of suitable size to hold the ancillary data.
+ * However, since this buffer is in reality a 'struct cmsghdr', use a
+ * union to ensure that it is aligned as required for that structure.
+ * Alternatively, we could allocate the buffer using malloc(), which
+ * returns a buffer that satisfies the strictest alignment
+ * requirements of any type.
+ */
+
+ union {
+ char buf[CMSG_SPACE(sizeof(int))];
+ /* Space large enough to hold an 'int' */
+ struct cmsghdr align;
+ } controlMsg;
+ struct cmsghdr *cmsgp; /* Pointer used to iterate through
+ * headers in ancillary data
+ */
+
+ /* Parse command-line options */
+
+ useDatagramSocket = false;
+
+ while ((opt = getopt(argc, argv, "dD")) != -1) {
+ switch (opt) {
+ case 'd':
+ useDatagramSocket = true;
+ break;
+ case 'D':
+ debugging = true;
+ break;
+ default:
+ usageErr("%s [-dD] file\n"
+ " -D enable debugging\n"
+ " -d use datagram socket\n", argv[0]);
+ }
+ }
+
+ if (argc != optind + 1)
+ usageErr("%s [-dD] file\n", argv[0]);
+
+ /* Open the file named on the command line */
+
+ fd = open(argv[optind], O_RDONLY);
+ if (fd == -1)
+ errExit("open");
+
+ /* The 'msg_name' field can be used to specify the address of the
+ * destination socket when sending a datagram. However, we do not
+ * need to use this field because we use connect() below, which sets
+ * a default outgoing address for datagrams.
+ */
+
+ msgh.msg_name = NULL;
+ msgh.msg_namelen = 0;
+
+ /* 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);
+ data = 12345;
+ debug("Sending data = %d\n", data);
+
+ /* Set 'msgh' fields to describe the ancillary data buffer */
+
+ msgh.msg_control = controlMsg.buf;
+ msgh.msg_controllen = sizeof(controlMsg.buf);
+
+ /* The control message buffer must be zero-initialized in order
+ * for the CMSG_NXTHDR() macro to work correctly. Although we
+ * don't need to use CMSG_NXTHDR() in this example (because
+ * there is only one block of ancillary data), we show this
+ * step to demonstrate best practice
+ */
+
+ memset(controlMsg.buf, 0, sizeof(controlMsg.buf));
+
+ /* Set message header to describe the ancillary data that
+ * we want to send
+ */
+
+ cmsgp = CMSG_FIRSTHDR(&msgh);
+ cmsgp->cmsg_len = CMSG_LEN(sizeof(int));
+ cmsgp->cmsg_level = SOL_SOCKET;
+ cmsgp->cmsg_type = SCM_RIGHTS;
+ memcpy(CMSG_DATA(cmsgp), &fd, sizeof(int));
+
+ /* Connect to the peer socket */
+
+ sfd = unixConnect(SOCK_PATH, useDatagramSocket ? SOCK_DGRAM : SOCK_STREAM);
+ if (sfd == -1)
+ errExit("unixConnect");
+
+ debug("Sending FD %d\n", fd);
+
+ /* Send real plus ancillary data */
+
+ ns = sendmsg(sfd, &msgh, 0);
+ if (ns == -1)
+ errExit("sendmsg");
+
+ debug("sendmsg() returned %ld\n", (long) ns);
+
+ /* Once the file descriptor has been sent, it is no longer necessary
+ * to keep it open in the sending process
+ */
+
+ if (close(fd) == -1)
+ errExit("close");
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/tools/testing/selftests/socket/simple_scm_rights.sh b/tools/testing/selftests/socket/simple_scm_rights.sh
new file mode 100755
index 000000000000..31ea0fc1bb6d
--- /dev/null
+++ b/tools/testing/selftests/socket/simple_scm_rights.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0+
+set -e
+
+ret=0
+hello=$(cat hello.txt)
+
+rm -f scm_rights
+(sleep 0.1; ./scm_rights_send hello.txt) &
+out=$(./scm_rights_recv)
+
+if [ "$hello" != "$out" ] ; then
+ echo "FAIL: SCM_RIGHTS fd contents mismatch"
+ ret=1
+else
+ echo "ok: SOCK_STREAM"
+fi
+
+rm -f scm_rights
+(sleep 0.1; ./scm_rights_send -d hello.txt) &
+out=$(./scm_rights_recv -d)
+
+if [ "$hello" != "$out" ] ; then
+ echo "FAIL: SCM_RIGHTS fd contents mismatch"
+ ret=1
+else
+ echo "ok: SOCK_DGRAM"
+fi
+
+exit $ret
diff --git a/tools/testing/selftests/socket/unix_sockets.c b/tools/testing/selftests/socket/unix_sockets.c
new file mode 100644
index 000000000000..a7678fad1a16
--- /dev/null
+++ b/tools/testing/selftests/socket/unix_sockets.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * unix_sockets.c
+ *
+ * Copyright (C) Michael Kerrisk, 2020.
+ *
+ * A package of useful routines for UNIX domain sockets.
+ */
+#include "unix_sockets.h" /* Declares functions defined here */
+
+/* Build a UNIX domain socket address structure for 'path', returning
+ * it in 'addr'. Returns -1 on success, or 0 on error.
+ */
+
+int
+unixBuildAddress(const char *path, struct sockaddr_un *addr)
+{
+ if (addr == NULL || path == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ memset(addr, 0, sizeof(struct sockaddr_un));
+ addr->sun_family = AF_UNIX;
+ if (strlen(path) < sizeof(addr->sun_path)) {
+ strncpy(addr->sun_path, path, sizeof(addr->sun_path) - 1);
+ return 0;
+ } else {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+}
+
+/* Create a UNIX domain socket of type 'type' and connect it
+ * to the remote address specified by the 'path'.
+ * Return the socket descriptor on success, or -1 on error
+ */
+
+int
+unixConnect(const char *path, int type)
+{
+ int sd, savedErrno;
+ struct sockaddr_un addr;
+
+ if (unixBuildAddress(path, &addr) == -1)
+ return -1;
+
+ sd = socket(AF_UNIX, type, 0);
+ if (sd == -1)
+ return -1;
+
+ if (connect(sd, (struct sockaddr *) &addr,
+ sizeof(struct sockaddr_un)) == -1) {
+ savedErrno = errno;
+ close(sd); /* Might change 'errno' */
+ errno = savedErrno;
+ return -1;
+ }
+
+ return sd;
+}
+
+/* Create a UNIX domain socket and bind it to 'path'.
+ * Return the socket descriptor on success, or -1 on error.
+ */
+
+int
+unixBind(const char *path, int type)
+{
+ int sd, savedErrno;
+ struct sockaddr_un addr;
+
+ if (unixBuildAddress(path, &addr) == -1)
+ return -1;
+
+ sd = socket(AF_UNIX, type, 0);
+ if (sd == -1)
+ return -1;
+
+ if (bind(sd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) {
+ savedErrno = errno;
+ close(sd); /* Might change 'errno' */
+ errno = savedErrno;
+ return -1;
+ }
+
+ return sd;
+}
diff --git a/tools/testing/selftests/socket/unix_sockets.h b/tools/testing/selftests/socket/unix_sockets.h
new file mode 100644
index 000000000000..e03a5aecd10c
--- /dev/null
+++ b/tools/testing/selftests/socket/unix_sockets.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * unix_sockets.h
+ *
+ * Copyright (C) Michael Kerrisk, 2020.
+ *
+ * Header file for unix_sockets.c.
+ */
+#ifndef UNIX_SOCKETS_H
+#define UNIX_SOCKETS_H /* Prevent accidental double inclusion */
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+int unixBuildAddress(const char *path, struct sockaddr_un *addr);
+
+int unixConnect(const char *path, int type);
+
+int unixBind(const char *path, int type);
+
+#endif
--
2.25.1


--
Kees Cook


Subject: Re: [PATCH] selftests/socket: Introduce simple SCM_RIGHTS tests

Hi,

On Mon, 10 Aug 2020 at 23:52, Kees Cook <[email protected]> wrote:
>
> This makes sure that simple SCM_RIGHTS fd passing works as expected, to
> avoid any future regressions. This is mostly code from Michael Kerrisk's
> examples on how to set up and perform fd passing with SCM_RIGHTS. Add
> a test script and wire it up to the selftests.
>
> Signed-off-by: Kees Cook <[email protected]>

Acked-by: Michael Kerrisk <[email protected]>

(As per off-list discussion with Kees), I confirm that I'm okay with
relicensing to GPL2+.

Cheers,

Michael

> ---
> FYI, this also relicenses Michael's code (with his permission) from
> GPL3+ to GPL2+, who is on CC to publicly confirm. :) Thank you Michael!
> ---
> tools/testing/selftests/Makefile | 1 +
> tools/testing/selftests/socket/.gitignore | 4 +
> tools/testing/selftests/socket/Makefile | 11 ++
> tools/testing/selftests/socket/hello.txt | 1 +
> tools/testing/selftests/socket/scm_rights.h | 40 +++++
> .../selftests/socket/scm_rights_recv.c | 168 ++++++++++++++++++
> .../selftests/socket/scm_rights_send.c | 144 +++++++++++++++
> .../selftests/socket/simple_scm_rights.sh | 30 ++++
> tools/testing/selftests/socket/unix_sockets.c | 88 +++++++++
> tools/testing/selftests/socket/unix_sockets.h | 23 +++
> 10 files changed, 510 insertions(+)
> create mode 100644 tools/testing/selftests/socket/.gitignore
> create mode 100644 tools/testing/selftests/socket/Makefile
> create mode 100644 tools/testing/selftests/socket/hello.txt
> create mode 100644 tools/testing/selftests/socket/scm_rights.h
> create mode 100644 tools/testing/selftests/socket/scm_rights_recv.c
> create mode 100644 tools/testing/selftests/socket/scm_rights_send.c
> create mode 100755 tools/testing/selftests/socket/simple_scm_rights.sh
> create mode 100644 tools/testing/selftests/socket/unix_sockets.c
> create mode 100644 tools/testing/selftests/socket/unix_sockets.h
>
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index e03bc15ce731..97e155596660 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -51,6 +51,7 @@ TARGETS += rtc
> TARGETS += seccomp
> TARGETS += sigaltstack
> TARGETS += size
> +TARGETS += socket
> TARGETS += sparc64
> TARGETS += splice
> TARGETS += static_keys
> diff --git a/tools/testing/selftests/socket/.gitignore b/tools/testing/selftests/socket/.gitignore
> new file mode 100644
> index 000000000000..bc9a892956b0
> --- /dev/null
> +++ b/tools/testing/selftests/socket/.gitignore
> @@ -0,0 +1,4 @@
> +unix_sockets.o
> +scm_rights_send
> +scm_rights_recv
> +scm_rights
> diff --git a/tools/testing/selftests/socket/Makefile b/tools/testing/selftests/socket/Makefile
> new file mode 100644
> index 000000000000..3eb7ef0db997
> --- /dev/null
> +++ b/tools/testing/selftests/socket/Makefile
> @@ -0,0 +1,11 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +TEST_PROGS := simple_scm_rights.sh
> +TEST_GEN_PROGS_EXTENDED := scm_rights_send scm_rights_recv
> +
> +include ../lib.mk
> +
> +$(OUTPUT)/unix_sockets.o: unix_sockets.h
> +$(OUTPUT)/scm_rights_recv: $(OUTPUT)/unix_sockets.o scm_rights.h
> +$(OUTPUT)/scm_rights_send: $(OUTPUT)/unix_sockets.o scm_rights.h
> +
> +EXTRA_CLEAN += $(OUTPUT)/unix_sockets.o $(OUTPUT)/scm_rights
> diff --git a/tools/testing/selftests/socket/hello.txt b/tools/testing/selftests/socket/hello.txt
> new file mode 100644
> index 000000000000..e965047ad7c5
> --- /dev/null
> +++ b/tools/testing/selftests/socket/hello.txt
> @@ -0,0 +1 @@
> +Hello
> diff --git a/tools/testing/selftests/socket/scm_rights.h b/tools/testing/selftests/socket/scm_rights.h
> new file mode 100644
> index 000000000000..4501a46bf1be
> --- /dev/null
> +++ b/tools/testing/selftests/socket/scm_rights.h
> @@ -0,0 +1,40 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * scm_rights.h
> + *
> + * Copyright (C) Michael Kerrisk, 2020.
> + *
> + * Header file used by scm_rights_send.c and scm_rights_recv.c.
> + */
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +
> +#include "unix_sockets.h"
> +
> +#define SOCK_PATH "scm_rights"
> +
> +#define errExit(fmt, ...) do { \
> + fprintf(stderr, fmt, ## __VA_ARGS__); \
> + fprintf(stderr, ": %s\n", strerror(errno)); \
> + exit(EXIT_FAILURE); \
> + } while (0)
> +
> +#define fatal(str) errExit("%s\n", str)
> +
> +#define usageErr(fmt, ...) do { \
> + fprintf(stderr, "Usage: "); \
> + fprintf(stderr, fmt, ## __VA_ARGS__); \
> + exit(EXIT_FAILURE); \
> + } while (0)
> +
> +static bool debugging;
> +
> +#define debug(fmt, ...) do { \
> + if (debugging) \
> + fprintf(stderr, fmt, ## __VA_ARGS__); \
> + } while (0)
> diff --git a/tools/testing/selftests/socket/scm_rights_recv.c b/tools/testing/selftests/socket/scm_rights_recv.c
> new file mode 100644
> index 000000000000..4c916e43c319
> --- /dev/null
> +++ b/tools/testing/selftests/socket/scm_rights_recv.c
> @@ -0,0 +1,168 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * scm_rights_recv.c
> + *
> + * Copyright (C) Michael Kerrisk, 2020.
> + *
> + * Used in conjunction with scm_rights_send.c to demonstrate passing of
> + * file descriptors via a UNIX domain socket.
> + *
> + * This program receives a file descriptor sent to a UNIX domain socket.
> + *
> + * Usage is as shown in the usageErr() call below.
> + *
> + * File descriptors can be exchanged over stream or datagram sockets. This
> + * program uses stream sockets by default; the "-d" command-line option
> + * specifies that datagram sockets should be used instead.
> + *
> + * This program is Linux-specific.
> + */
> +#include "scm_rights.h"
> +
> +#define BUF_SIZE 100
> +
> +int
> +main(int argc, char *argv[])
> +{
> + int data, lfd, sfd, fd, opt;
> + ssize_t nr;
> + bool useDatagramSocket;
> + struct msghdr msgh;
> + struct iovec iov;
> +
> + /* Allocate a char array of suitable size to hold the ancillary data.
> + * However, since this buffer is in reality a 'struct cmsghdr', use a
> + * union to ensure that it is aligned as required for that structure.
> + * Alternatively, we could allocate the buffer using malloc(), which
> + * returns a buffer that satisfies the strictest alignment
> + * requirements of any type
> + */
> +
> + union {
> + char buf[CMSG_SPACE(sizeof(int))];
> + /* Space large enough to hold an 'int' */
> + struct cmsghdr align;
> + } controlMsg;
> + struct cmsghdr *cmsgp; /* Pointer used to iterate through
> + * headers in ancillary data
> + */
> +
> + /* Parse command-line options */
> +
> + useDatagramSocket = false;
> +
> + while ((opt = getopt(argc, argv, "dD")) != -1) {
> + switch (opt) {
> + case 'd':
> + useDatagramSocket = true;
> + break;
> +
> + default:
> + usageErr("%s [-dD]\n"
> + " -D enable debugging\n"
> + " -d use datagram socket\n", argv[0]);
> + }
> + }
> +
> + /* Create socket bound to a well-known address. In the case where
> + * we are using stream sockets, also make the socket a listening
> + * socket and accept a connection on the socket.
> + */
> +
> + if (remove(SOCK_PATH) == -1 && errno != ENOENT)
> + errExit("remove-%s", SOCK_PATH);
> +
> + if (useDatagramSocket) {
> + sfd = unixBind(SOCK_PATH, SOCK_DGRAM);
> + if (sfd == -1)
> + errExit("unixBind");
> +
> + } else {
> + lfd = unixBind(SOCK_PATH, SOCK_STREAM);
> + if (lfd == -1)
> + errExit("unixBind");
> +
> + if (listen(lfd, 5) == -1)
> + errExit("listen");
> +
> + sfd = accept(lfd, NULL, NULL);
> + if (sfd == -1)
> + errExit("accept");
> + }
> +
> + /* The 'msg_name' field can be set to point to a buffer where the
> + * kernel will place the address of the peer socket. However, we don't
> + * need the address of the peer, so we set this field to NULL.
> + */
> +
> + msgh.msg_name = NULL;
> + msgh.msg_namelen = 0;
> +
> + /* Set fields of 'msgh' to point to buffer used to receive the (real)
> + * data read by recvmsg()
> + */
> +
> + msgh.msg_iov = &iov;
> + msgh.msg_iovlen = 1;
> + iov.iov_base = &data;
> + iov.iov_len = sizeof(int);
> +
> + /* Set 'msgh' fields to describe the ancillary data buffer */
> +
> + msgh.msg_control = controlMsg.buf;
> + msgh.msg_controllen = sizeof(controlMsg.buf);
> +
> + /* Receive real plus ancillary data */
> +
> + nr = recvmsg(sfd, &msgh, 0);
> + if (nr == -1)
> + errExit("recvmsg");
> + debug("recvmsg() returned %ld\n", (long) nr);
> +
> + if (nr > 0)
> + debug("Received data = %d\n", data);
> +
> + /* Get the address of the first 'cmsghdr' in the received
> + * ancillary data
> + */
> +
> + cmsgp = CMSG_FIRSTHDR(&msgh);
> +
> + /* Check the validity of the 'cmsghdr' */
> +
> + if (cmsgp == NULL || cmsgp->cmsg_len != CMSG_LEN(sizeof(int)))
> + fatal("bad cmsg header / message length");
> + if (cmsgp->cmsg_level != SOL_SOCKET)
> + fatal("cmsg_level != SOL_SOCKET");
> + if (cmsgp->cmsg_type != SCM_RIGHTS)
> + fatal("cmsg_type != SCM_RIGHTS");
> +
> + /* The data area of the 'cmsghdr' is an 'int' (a file descriptor);
> + * copy that integer to a local variable. (The received file descriptor
> + * is typically a different file descriptor number than was used in the
> + * sending process.)
> + */
> +
> + memcpy(&fd, CMSG_DATA(cmsgp), sizeof(int));
> + debug("Received FD %d\n", fd);
> +
> + /* Having obtained the file descriptor, read the file's contents and
> + * print them on standard output
> + */
> +
> + for (;;) {
> + char buf[BUF_SIZE];
> + ssize_t numRead;
> +
> + numRead = read(fd, buf, BUF_SIZE);
> + if (numRead == -1)
> + errExit("read");
> +
> + if (numRead == 0)
> + break;
> +
> + write(STDOUT_FILENO, buf, numRead);
> + }
> +
> + exit(EXIT_SUCCESS);
> +}
> diff --git a/tools/testing/selftests/socket/scm_rights_send.c b/tools/testing/selftests/socket/scm_rights_send.c
> new file mode 100644
> index 000000000000..c5718d10a80d
> --- /dev/null
> +++ b/tools/testing/selftests/socket/scm_rights_send.c
> @@ -0,0 +1,144 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * scm_rights_send.c
> + *
> + * Copyright (C) Michael Kerrisk, 2020.
> + *
> + * Used in conjunction with scm_rights_recv.c to demonstrate passing of
> + * file descriptors via a UNIX domain socket.
> + *
> + * This program sends a file descriptor to a UNIX domain socket.
> + *
> + * Usage is as shown in the usageErr() call below.
> + *
> + * File descriptors can be exchanged over stream or datagram sockets. This
> + * program uses stream sockets by default; the "-d" command-line option
> + * specifies that datagram sockets should be used instead.
> + *
> + * This program is Linux-specific.
> + */
> +#include "scm_rights.h"
> +
> +int
> +main(int argc, char *argv[])
> +{
> + int data, sfd, opt, fd;
> + ssize_t ns;
> + bool useDatagramSocket;
> + struct msghdr msgh;
> + struct iovec iov;
> +
> + /* Allocate a char array of suitable size to hold the ancillary data.
> + * However, since this buffer is in reality a 'struct cmsghdr', use a
> + * union to ensure that it is aligned as required for that structure.
> + * Alternatively, we could allocate the buffer using malloc(), which
> + * returns a buffer that satisfies the strictest alignment
> + * requirements of any type.
> + */
> +
> + union {
> + char buf[CMSG_SPACE(sizeof(int))];
> + /* Space large enough to hold an 'int' */
> + struct cmsghdr align;
> + } controlMsg;
> + struct cmsghdr *cmsgp; /* Pointer used to iterate through
> + * headers in ancillary data
> + */
> +
> + /* Parse command-line options */
> +
> + useDatagramSocket = false;
> +
> + while ((opt = getopt(argc, argv, "dD")) != -1) {
> + switch (opt) {
> + case 'd':
> + useDatagramSocket = true;
> + break;
> + case 'D':
> + debugging = true;
> + break;
> + default:
> + usageErr("%s [-dD] file\n"
> + " -D enable debugging\n"
> + " -d use datagram socket\n", argv[0]);
> + }
> + }
> +
> + if (argc != optind + 1)
> + usageErr("%s [-dD] file\n", argv[0]);
> +
> + /* Open the file named on the command line */
> +
> + fd = open(argv[optind], O_RDONLY);
> + if (fd == -1)
> + errExit("open");
> +
> + /* The 'msg_name' field can be used to specify the address of the
> + * destination socket when sending a datagram. However, we do not
> + * need to use this field because we use connect() below, which sets
> + * a default outgoing address for datagrams.
> + */
> +
> + msgh.msg_name = NULL;
> + msgh.msg_namelen = 0;
> +
> + /* 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);
> + data = 12345;
> + debug("Sending data = %d\n", data);
> +
> + /* Set 'msgh' fields to describe the ancillary data buffer */
> +
> + msgh.msg_control = controlMsg.buf;
> + msgh.msg_controllen = sizeof(controlMsg.buf);
> +
> + /* The control message buffer must be zero-initialized in order
> + * for the CMSG_NXTHDR() macro to work correctly. Although we
> + * don't need to use CMSG_NXTHDR() in this example (because
> + * there is only one block of ancillary data), we show this
> + * step to demonstrate best practice
> + */
> +
> + memset(controlMsg.buf, 0, sizeof(controlMsg.buf));
> +
> + /* Set message header to describe the ancillary data that
> + * we want to send
> + */
> +
> + cmsgp = CMSG_FIRSTHDR(&msgh);
> + cmsgp->cmsg_len = CMSG_LEN(sizeof(int));
> + cmsgp->cmsg_level = SOL_SOCKET;
> + cmsgp->cmsg_type = SCM_RIGHTS;
> + memcpy(CMSG_DATA(cmsgp), &fd, sizeof(int));
> +
> + /* Connect to the peer socket */
> +
> + sfd = unixConnect(SOCK_PATH, useDatagramSocket ? SOCK_DGRAM : SOCK_STREAM);
> + if (sfd == -1)
> + errExit("unixConnect");
> +
> + debug("Sending FD %d\n", fd);
> +
> + /* Send real plus ancillary data */
> +
> + ns = sendmsg(sfd, &msgh, 0);
> + if (ns == -1)
> + errExit("sendmsg");
> +
> + debug("sendmsg() returned %ld\n", (long) ns);
> +
> + /* Once the file descriptor has been sent, it is no longer necessary
> + * to keep it open in the sending process
> + */
> +
> + if (close(fd) == -1)
> + errExit("close");
> +
> + exit(EXIT_SUCCESS);
> +}
> diff --git a/tools/testing/selftests/socket/simple_scm_rights.sh b/tools/testing/selftests/socket/simple_scm_rights.sh
> new file mode 100755
> index 000000000000..31ea0fc1bb6d
> --- /dev/null
> +++ b/tools/testing/selftests/socket/simple_scm_rights.sh
> @@ -0,0 +1,30 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: GPL-2.0+
> +set -e
> +
> +ret=0
> +hello=$(cat hello.txt)
> +
> +rm -f scm_rights
> +(sleep 0.1; ./scm_rights_send hello.txt) &
> +out=$(./scm_rights_recv)
> +
> +if [ "$hello" != "$out" ] ; then
> + echo "FAIL: SCM_RIGHTS fd contents mismatch"
> + ret=1
> +else
> + echo "ok: SOCK_STREAM"
> +fi
> +
> +rm -f scm_rights
> +(sleep 0.1; ./scm_rights_send -d hello.txt) &
> +out=$(./scm_rights_recv -d)
> +
> +if [ "$hello" != "$out" ] ; then
> + echo "FAIL: SCM_RIGHTS fd contents mismatch"
> + ret=1
> +else
> + echo "ok: SOCK_DGRAM"
> +fi
> +
> +exit $ret
> diff --git a/tools/testing/selftests/socket/unix_sockets.c b/tools/testing/selftests/socket/unix_sockets.c
> new file mode 100644
> index 000000000000..a7678fad1a16
> --- /dev/null
> +++ b/tools/testing/selftests/socket/unix_sockets.c
> @@ -0,0 +1,88 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * unix_sockets.c
> + *
> + * Copyright (C) Michael Kerrisk, 2020.
> + *
> + * A package of useful routines for UNIX domain sockets.
> + */
> +#include "unix_sockets.h" /* Declares functions defined here */
> +
> +/* Build a UNIX domain socket address structure for 'path', returning
> + * it in 'addr'. Returns -1 on success, or 0 on error.
> + */
> +
> +int
> +unixBuildAddress(const char *path, struct sockaddr_un *addr)
> +{
> + if (addr == NULL || path == NULL) {
> + errno = EINVAL;
> + return -1;
> + }
> +
> + memset(addr, 0, sizeof(struct sockaddr_un));
> + addr->sun_family = AF_UNIX;
> + if (strlen(path) < sizeof(addr->sun_path)) {
> + strncpy(addr->sun_path, path, sizeof(addr->sun_path) - 1);
> + return 0;
> + } else {
> + errno = ENAMETOOLONG;
> + return -1;
> + }
> +}
> +
> +/* Create a UNIX domain socket of type 'type' and connect it
> + * to the remote address specified by the 'path'.
> + * Return the socket descriptor on success, or -1 on error
> + */
> +
> +int
> +unixConnect(const char *path, int type)
> +{
> + int sd, savedErrno;
> + struct sockaddr_un addr;
> +
> + if (unixBuildAddress(path, &addr) == -1)
> + return -1;
> +
> + sd = socket(AF_UNIX, type, 0);
> + if (sd == -1)
> + return -1;
> +
> + if (connect(sd, (struct sockaddr *) &addr,
> + sizeof(struct sockaddr_un)) == -1) {
> + savedErrno = errno;
> + close(sd); /* Might change 'errno' */
> + errno = savedErrno;
> + return -1;
> + }
> +
> + return sd;
> +}
> +
> +/* Create a UNIX domain socket and bind it to 'path'.
> + * Return the socket descriptor on success, or -1 on error.
> + */
> +
> +int
> +unixBind(const char *path, int type)
> +{
> + int sd, savedErrno;
> + struct sockaddr_un addr;
> +
> + if (unixBuildAddress(path, &addr) == -1)
> + return -1;
> +
> + sd = socket(AF_UNIX, type, 0);
> + if (sd == -1)
> + return -1;
> +
> + if (bind(sd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) {
> + savedErrno = errno;
> + close(sd); /* Might change 'errno' */
> + errno = savedErrno;
> + return -1;
> + }
> +
> + return sd;
> +}
> diff --git a/tools/testing/selftests/socket/unix_sockets.h b/tools/testing/selftests/socket/unix_sockets.h
> new file mode 100644
> index 000000000000..e03a5aecd10c
> --- /dev/null
> +++ b/tools/testing/selftests/socket/unix_sockets.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * unix_sockets.h
> + *
> + * Copyright (C) Michael Kerrisk, 2020.
> + *
> + * Header file for unix_sockets.c.
> + */
> +#ifndef UNIX_SOCKETS_H
> +#define UNIX_SOCKETS_H /* Prevent accidental double inclusion */
> +
> +#include <errno.h>
> +#include <unistd.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +
> +int unixBuildAddress(const char *path, struct sockaddr_un *addr);
> +
> +int unixConnect(const char *path, int type);
> +
> +int unixBind(const char *path, int type);
> +
> +#endif
> --
> 2.25.1
>
>
> --
> Kees Cook



--
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/