Timothy Redaelli via dev <[email protected]> writes:
> 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;
> + }
I would change this to:
if (!str_to_long(suffix, 10, &fd) || fd < 0) {
VLOG_ERR("%s: bad file descriptor", name);
return EINVAL;
}
> + /* 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);
Should we also drain down the socket when it gets opened? What I mean
is, in case we crash and systemd restarts us does the socket that the
service hold actually drain us down? I guess that isn't clear to me and
I worry about some kind of invalid data sitting in the queue.
I also wonder if we should also do something like
recv(,MSG_DONTWAIT|MSG_PEEK) and make sure that the socket is still
open. The manpage from systemd seems to imply that if the socket gets
closed the fd itself is no longer considered in a good state. I'm not
sure how the systemd service works for it.
> +}
> +
> +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,
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev