Add a new "pfd:" passive stream class that accepts a pre-opened file
descriptor number.  This is the core building block for systemd socket
activation, where systemd opens and binds the listening socket before
starting the service.

The pfd_open() function validates that the file descriptor refers to
a listening stream socket via getsockopt(SO_TYPE) and
getsockopt(SO_ACCEPTCONN), sets it non-blocking, and wraps it in an
fd_pstream.  Unlike punix:, the unlink_path is NULL because the
service does not own the socket file.

For security, pfd: remotes are restricted to the command line
(--remote=pfd:N).  Runtime addition via ovsdb-server/add-remote
or the database is rejected at three entry points
(ovsdb_server_add_remote, add_manager_options, query_db_remotes),
preventing an attacker with database write access from hijacking
arbitrary file descriptors.

Reported-at: https://issues.redhat.com/browse/FDP-3413
Co-authored-by: Lubomir Rintel <[email protected]>
Signed-off-by: Lubomir Rintel <[email protected]>
Signed-off-by: Timothy Redaelli <[email protected]>
---
 Documentation/ref/ovsdb.7.rst | 12 ++++++++
 lib/stream-provider.h         |  1 +
 lib/stream-unix.c             | 53 +++++++++++++++++++++++++++++++++++
 lib/stream.c                  |  5 ++++
 ovsdb/ovsdb-server.c          | 23 ++++++++++++++-
 5 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/Documentation/ref/ovsdb.7.rst b/Documentation/ref/ovsdb.7.rst
index 42541dd7e..cf1ef3736 100644
--- a/Documentation/ref/ovsdb.7.rst
+++ b/Documentation/ref/ovsdb.7.rst
@@ -709,6 +709,18 @@ punix:<file>
     <file> to mimic the behavior of a Unix domain socket. The ACLs of the named
     pipe include LocalSystem, Administrators, and Creator Owner.
 
+pfd:<fd>
+    Listen on a pre-opened file descriptor <fd>.  The file descriptor must
+    refer to a bound, listening Unix domain stream socket.  This is intended
+    for use with systemd socket activation, where systemd opens the socket
+    and passes it to the service.
+
+    For security, ``pfd:`` may only be specified on the command line
+    (``--remote=pfd:<fd>``).  It is rejected if added at runtime via
+    ``ovsdb-server/add-remote`` or through the database.
+
+    This connection method is not supported on Windows.
+
 All IP-based connection methods accept IPv4 and IPv6 addresses.  To specify an
 IPv6 address, wrap it in square brackets, e.g.  ``ssl:[::1]:6640``.  Passive
 IP-based connection methods by default listen for IPv4 connections only; use
diff --git a/lib/stream-provider.h b/lib/stream-provider.h
index 44e3c6431..ddd468b09 100644
--- a/lib/stream-provider.h
+++ b/lib/stream-provider.h
@@ -195,6 +195,7 @@ extern const struct pstream_class ptcp_pstream_class;
 #ifndef _WIN32
 extern const struct stream_class unix_stream_class;
 extern const struct pstream_class punix_pstream_class;
+extern const struct pstream_class pfd_pstream_class;
 #else
 extern const struct stream_class windows_stream_class;
 extern const struct pstream_class pwindows_pstream_class;
diff --git a/lib/stream-unix.c b/lib/stream-unix.c
index d265efb83..5cb4e93d2 100644
--- a/lib/stream-unix.c
+++ b/lib/stream-unix.c
@@ -136,3 +136,56 @@ const struct pstream_class punix_pstream_class = {
     NULL,
 };
 
+/* Pre-opened file descriptor passive stream.
+ *
+ * Used for systemd socket activation: systemd opens and binds the socket,
+ * then passes it to the service as a pre-opened file descriptor. */
+
+static int
+pfd_open(const char *name, char *suffix,
+         struct pstream **pstreamp, uint8_t dscp OVS_UNUSED)
+{
+    char *end;
+    long fd;
+
+    fd = strtol(suffix, &end, 10);
+    if (*end != '\0' || fd < 0 || fd > INT_MAX) {
+        VLOG_ERR("%s: bad file descriptor", name);
+        return ERANGE;
+    }
+
+    /* Verify it is a listening stream socket. */
+    int sock_type;
+    socklen_t len = sizeof sock_type;
+    if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &len)) {
+        VLOG_ERR("%s: not a socket (%s)", name, ovs_strerror(errno));
+        return errno;
+    }
+    if (sock_type != SOCK_STREAM) {
+        VLOG_ERR("%s: not a stream socket (type %d)", name, sock_type);
+        return EINVAL;
+    }
+    int listening;
+    len = sizeof listening;
+    if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &listening, &len)
+        || !listening) {
+        VLOG_ERR("%s: not a listening socket", name);
+        return EINVAL;
+    }
+
+    int error = set_nonblocking(fd);
+    if (error) {
+        return error;
+    }
+
+    return new_fd_pstream(xstrdup(name), fd, punix_accept, NULL, pstreamp);
+}
+
+const struct pstream_class pfd_pstream_class = {
+    "pfd",
+    false,
+    pfd_open,
+    NULL,
+    NULL,
+    NULL,
+};
diff --git a/lib/stream.c b/lib/stream.c
index feaa1cb2d..b3b21588a 100644
--- a/lib/stream.c
+++ b/lib/stream.c
@@ -69,6 +69,7 @@ static const struct pstream_class *pstream_classes[] = {
     &ptcp_pstream_class,
 #ifndef _WIN32
     &punix_pstream_class,
+    &pfd_pstream_class,
 #else
     &pwindows_pstream_class,
 #endif
@@ -147,6 +148,10 @@ stream_usage(const char *name, bool active, bool passive,
 #endif
         printf("  punix:FILE              "
                "listen on Unix domain socket FILE\n");
+#ifndef _WIN32
+        printf("  pfd:FD                  "
+               "listen on pre-opened file descriptor FD\n");
+#endif
     }
 
 #ifdef HAVE_OPENSSL
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
index 7c3a5ef11..2af62071e 100644
--- a/ovsdb/ovsdb-server.c
+++ b/ovsdb/ovsdb-server.c
@@ -1425,6 +1425,12 @@ add_manager_options(struct shash *remotes, const struct 
ovsdb_row *row)
         return;
     }
 
+    if (!strncmp("pfd:", target, 4)) {
+        VLOG_WARN_RL(&rl, "pfd: remotes can only be specified on the "
+                     "command line; ignoring \"%s\" from database", target);
+        return;
+    }
+
     options = add_remote(remotes, target, NULL);
     if (ovsdb_util_read_integer_column(row, "max_backoff", &max_backoff)) {
         options->rpc.max_backoff = max_backoff;
@@ -1485,7 +1491,16 @@ query_db_remotes(const char *name, const struct shash 
*all_dbs,
 
             datum = &row->fields[column->index];
             for (i = 0; i < datum->n; i++) {
-                add_remote(remotes, json_string(datum->keys[i].s), NULL);
+                const char *t = json_string(datum->keys[i].s);
+                if (!strncmp("pfd:", t, 4)) {
+                    static struct vlog_rate_limit pfd_rl
+                        = VLOG_RATE_LIMIT_INIT(1, 1);
+                    VLOG_WARN_RL(&pfd_rl, "pfd: remotes can only be "
+                                 "specified on the command line; ignoring "
+                                 "\"%s\" from database", t);
+                    continue;
+                }
+                add_remote(remotes, t, NULL);
             }
         }
     } else if (column->type.key.type == OVSDB_TYPE_UUID
@@ -2291,6 +2306,12 @@ ovsdb_server_add_remote(struct unixctl_conn *conn, int 
argc OVS_UNUSED,
         return;
     }
 
+    if (!strncmp("pfd:", remote, 4)) {
+        unixctl_command_reply_error(conn,
+            "pfd: remotes can only be specified on the command line");
+        return;
+    }
+
     retval = (strncmp("db:", remote, 3)
               ? NULL
               : parse_db_column(config->all_dbs, remote,
-- 
2.53.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to