diff options
author | Harald Welte <laforge@osmocom.org> | 2020-06-02 00:43:31 +0200 |
---|---|---|
committer | laforge <laforge@osmocom.org> | 2020-06-02 20:54:05 +0000 |
commit | 157ad6d7075ad56038c984ebf8634f1bc7c6c59c (patch) | |
tree | 6cef64283cd025ec46682e889a9331fee03b5fd6 | |
parent | b61eaaccc37c8e7842a86bc06dcd0c0dd444e576 (diff) |
src/stream: Work around more Linux kernel ABI breakage
Back in Change-Id Ia95dd1f9ffed9f743c049e05797b1a6f1f9f8c69 we tried
to work-around kernel ABI breakage introduced in kernel >= v5.5, but it
seems that there have already been similar ABI breakages in v4.11 and v4.12.
See https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/include/uapi/linux/sctp.h?id=b6e6b5f1da7e8d092f86a4351802c27c0170c5a5
and https://marc.info/?l=linux-sctp&m=158729301516157&w=2 for the most
recent incarnation.
See https://osmocom.org/issues/4573#note-6 for all known cases
of SCTP_EVENTS ABI breakage.
Closes: OS#4573
Change-Id: Icc49f347cdc0bb77a5c0e230597d662ac35b4acc
-rw-r--r-- | src/stream.c | 113 |
1 files changed, 93 insertions, 20 deletions
diff --git a/src/stream.c b/src/stream.c index 6e4c461..44ac1b4 100644 --- a/src/stream.c +++ b/src/stream.c @@ -73,6 +73,97 @@ #define MSG_NOSIGNAL 0 #endif +/* is any of the bytes from offset .. u8_size in 'u8' non-zero? return offset or -1 if all zero */ +static int byte_nonzero(const uint8_t *u8, unsigned int offset, unsigned int u8_size) +{ + int j; + + for (j = offset; j < u8_size; j++) { + if (u8[j] != 0) + return j; + } + + return -1; +} + +static int sctp_sockopt_event_subscribe_size = 0; + +static int determine_sctp_sockopt_event_subscribe_size(void) +{ + uint8_t buf[256]; + socklen_t buf_len = sizeof(buf); + int sd, rc; + + /* only do this once */ + if (sctp_sockopt_event_subscribe_size != 0) + return 0; + + sd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP); + if (sd < 0) + return sd; + + rc = getsockopt(sd, IPPROTO_SCTP, SCTP_EVENTS, buf, &buf_len); + if (rc < 0) + return rc; + + sctp_sockopt_event_subscribe_size = buf_len; + + LOGP(DLINP, LOGL_INFO, "sizes of 'struct sctp_event_subscribe': compile-time %zu, kernel: %u\n", + sizeof(struct sctp_event_subscribe), sctp_sockopt_event_subscribe_size); + return 0; +} + +/* Attempt to work around Linux kernel ABI breakage + * + * The Linux kernel ABI for the SCTP_EVENTS socket option has been broken repeatedly. + * - until commit 35ea82d611da59f8bea44a37996b3b11bb1d3fd7 ( kernel < 4.11), the size is 10 bytes + * - in 4.11 it is 11 bytes + * - in 4.12 .. 5.4 it is 13 bytes + * - in kernels >= 5.5 it is 14 bytes + * + * This wouldn't be a problem if the kernel didn't have a "stupid" assumption that the structure + * size passed by userspace will match 1:1 the length of the structure at kernel compile time. In + * an ideal world, it would just use the known first bytes and assume the remainder is all zero. + * But as it doesn't do that, let's try to work around this */ +static int sctp_setsockopt_events_linux_workaround(int fd, const struct sctp_event_subscribe *event) +{ + + const unsigned int compiletime_size = sizeof(*event); + int rc; + + if (determine_sctp_sockopt_event_subscribe_size() < 0) { + LOGP(DLINP, LOGL_ERROR, "Cannot determine SCTP_EVENTS socket option size\n"); + return -1; + } + + if (compiletime_size == sctp_sockopt_event_subscribe_size) { + /* no kernel workaround needed */ + return setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, event, compiletime_size); + } else if (compiletime_size < sctp_sockopt_event_subscribe_size) { + /* we are using an older userspace with a more modern kernel and hence need + * to pad the data */ + uint8_t buf[sctp_sockopt_event_subscribe_size]; + + memcpy(buf, event, compiletime_size); + memset(buf + sizeof(*event), 0, sctp_sockopt_event_subscribe_size - compiletime_size); + return setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, buf, sctp_sockopt_event_subscribe_size); + } else /* if (compiletime_size > sctp_sockopt_event_subscribe_size) */ { + /* we are using a newer userspace with an older kernel and hence need to truncate + * the data - but only if the caller didn't try to enable any of the events of the + * truncated portion */ + rc = byte_nonzero((const uint8_t *)event, sctp_sockopt_event_subscribe_size, + compiletime_size); + if (rc >= 0) { + LOGP(DLINP, LOGL_ERROR, "Kernel only supports sctp_event_subscribe of %u bytes, " + "but caller tried to enable more modern event at offset %u\n", + sctp_sockopt_event_subscribe_size, rc); + return -1; + } + + return setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, event, sctp_sockopt_event_subscribe_size); + } +} + static int sctp_sock_activate_events(int fd) { #ifdef HAVE_LIBSCTP @@ -89,28 +180,10 @@ static int sctp_sock_activate_events(int fd) event.sctp_shutdown_event = 1; /* IMPORTANT: Do NOT enable sender_dry_event here, see * https://bugzilla.redhat.com/show_bug.cgi?id=1442784 */ - rc = setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, - &event, sizeof(event)); - - /* - * Attempt to work around kernel ABI breakage - * - * In kernel commit b6e6b5f1da7e8d092f86a4351802c27c0170c5a5, the - * struct sctp_event_subscribe had a u8 field added to it at the end, thus - * breaking ABI. - * See https://marc.info/?l=linux-sctp&m=158729301516157&w=2 for discussion. - * - * We attempt to work around the issue where the kernel header are new - * and running kernel is old, by forcing the size of the struct to 13 which - * is the "old" size - */ - if ((rc < 0) && (sizeof(event) != 13)) - rc = setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, - &event, 13); + rc = sctp_setsockopt_events_linux_workaround(fd, &event); if (rc < 0) - LOGP(DLINP, LOGL_ERROR, "couldn't activate SCTP events " - "on FD %u\n", fd); + LOGP(DLINP, LOGL_ERROR, "couldn't activate SCTP events on FD %u\n", fd); return rc; #else return -1; |