[PATCH v7 00/18] Dynamic configuration update implementation
Changes in v7: * Addressed comments from Laurent in 6/18, 8/18, 9/18, 10/18, 11/18, 12/18, 14/18, 15/18 (details in commit messages of single patches, before my Signed-off-by) * Note: this doesn't include yet --add and --delete, I'm still working on that David Gibson (17): conf, fwd: Stricter rule checking in fwd_rule_add() fwd_rule: Move ephemeral port probing to fwd_rule.c fwd, conf: Move rule parsing code to fwd_rule.[ch] fwd_rule: Move conflict checking back within fwd_rule_add() fwd: Generalise fwd_rules_info() pif: Limit pif names to 128 bytes fwd_rule: Fix some format specifiers pesto: Introduce stub configuration tool pesto, log: Share log.h (but not log.c) with pesto tool pesto, conf: Have pesto connect to passt and check versions pesto: Expose list of pifs to pesto and display them ip: Prepare ip.[ch] for sharing with pesto tool inany: Prepare inany.[ch] for sharing with pesto tool pesto: Read current ruleset from passt/pasta and optionally display it pesto: Parse and add new rules from command line pesto, conf: Send updated rules from pesto back to passt/pasta conf, fwd: Allow switching to new rules received from pesto Stefano Brivio (1): fwd_rule: Fix static checkers warnings in fwd_rule_add() .gitignore | 2 + Makefile | 53 ++-- common.h | 116 +++++++++ conf.c | 701 +++++++++++++++++++++++---------------------------- conf.h | 2 + epoll_type.h | 4 + flow.c | 4 +- fwd.c | 169 ++++--------- fwd.h | 41 +-- fwd_rule.c | 611 ++++++++++++++++++++++++++++++++++++++++++-- fwd_rule.h | 66 ++++- inany.c | 19 +- inany.h | 17 +- ip.c | 56 +--- ip.h | 4 +- lineread.c | 2 +- log.h | 53 +++- passt.1 | 5 + passt.c | 8 + passt.h | 8 + pesto.1 | 176 +++++++++++++ pesto.c | 475 ++++++++++++++++++++++++++++++++++ pesto.h | 54 ++++ pif.c | 2 +- pif.h | 7 +- serialise.c | 7 + serialise.h | 1 + siphash.h | 13 + tap.c | 52 ++++ util.h | 110 +------- 30 files changed, 2046 insertions(+), 792 deletions(-) create mode 100644 common.h create mode 100644 pesto.1 create mode 100644 pesto.c create mode 100644 pesto.h -- 2.43.0
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
The new checks are actually sufficient but not enough for Coverity
Scan. Now that fwd->sock_count and new->last are affected or supplied
by clients, we need explicit (albeit redundant) checks on them.
Signed-off-by: Stefano Brivio
On Tue, May 05, 2026 at 01:11:42AM +0200, Stefano Brivio wrote:
The new checks are actually sufficient but not enough for Coverity Scan. Now that fwd->sock_count and new->last are affected or supplied by clients, we need explicit (albeit redundant) checks on them.
Signed-off-by: Stefano Brivio
I'm assuming this does squash the warnings, but I think it does so in a somewhat confusing way.
--- fwd_rule.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/fwd_rule.c b/fwd_rule.c index b55e4df..03e8e80 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -271,13 +271,22 @@ int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) warn("Too many rules (maximum %d)", ARRAY_SIZE(fwd->rules)); return -ENOSPC; } + if ((fwd->sock_count + num) > ARRAY_SIZE(fwd->socks)) { warn("Rules require too many listening sockets (maximum %d)", ARRAY_SIZE(fwd->socks)); return -ENOSPC; } + /* Redundant, to make static checkers happy */ + if (fwd->sock_count > ARRAY_SIZE(fwd->socks)) + return -ENOSPC;
So there's actually two conditions that this is kind of relevant to: 1) (fwd->sock_count > ARRAY_SIZE(fwd->socks)) on entry That means something is horribly wrong before we were even called. So, I think that would be better as an assert(). 2) (fwd->sock_count + num) overflows That's a closer-to-real concern. I'm pretty sure we can't hit it for real, because num is necessarily <= 65536, so as long as (1) is true this can't overflow. But that relies on the specific value of ARRAY_SIZE(fwd->socks), so it's kind of fragile. I think an explicit check for this is a good idea, but it should actually check for this, not just side-effects of it, so: if (fwd->sock_count + num <= fwd->sock_count) { warn("Blah blah overflow"); return -EFAULT; /* or whatever */ }
fwd->rulesocks[fwd->count] = &fwd->socks[fwd->sock_count]; + + /* Redundant ('num' checked above), but not for static checkers */ + if (new->last > ARRAY_SIZE(fwd->socks) + new->first) + return -ENOSPC;
This way of organising the check is very confusing to me. I'm not really sure what it's trying to catch. We've already checked that last >= first, so using num is safer to deal with at this point than ARRAY_SIZE() + first, which could in principle overflow even if sock_count + num is perfectly ok.
for (port = new->first; port <= new->last; port++) fwd->rulesocks[fwd->count][port - new->first] = -1;
-- 2.43.0
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
Build a new "pesto" binary, which will become the tool to update a running passt/pasta's configuration. For now, we just build a stub binary which sets up a basic environment, parses trivial command line options but does nothing else.
Signed-off-by: David Gibson
[sbrivio: Dropped leading _ from comment to include guard endif, reported by Laurent] [sbrivio: Formatting changes in pesto.1: use 80 columns instead of wrapping at about 75. Add description for -d, -h, --version.] Signed-off-by: Stefano Brivio
Reviewed-by: Laurent Vivier
--- .gitignore | 2 + Makefile | 42 +++++++++++------ common.h | 24 ++++++++++ pesto.1 | 59 ++++++++++++++++++++++++ pesto.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++ pesto.h | 12 +++++ util.h | 12 +---- 7 files changed, 257 insertions(+), 26 deletions(-) create mode 100644 common.h create mode 100644 pesto.1 create mode 100644 pesto.c create mode 100644 pesto.h
diff --git a/.gitignore b/.gitignore index 3c16adc..3e40d9f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,11 @@ /pasta /pasta.avx2 /passt-repair +/pesto /qrap /pasta.1 /seccomp.h +/seccomp_pesto.h /seccomp_repair.h /c*.json README.plain.md diff --git a/Makefile b/Makefile index 7875d23..030681b 100644 --- a/Makefile +++ b/Makefile @@ -47,19 +47,21 @@ PASST_SRCS = arch.c arp.c bitmap.c checksum.c conf.c dhcp.c dhcpv6.c \ vhost_user.c virtio.c vu_common.c QRAP_SRCS = qrap.c PASST_REPAIR_SRCS = passt-repair.c -SRCS = $(PASST_SRCS) $(QRAP_SRCS) $(PASST_REPAIR_SRCS) - -MANPAGES = passt.1 pasta.1 qrap.1 passt-repair.1 - -PASST_HEADERS = arch.h arp.h bitmap.h checksum.h conf.h dhcp.h dhcpv6.h \ - epoll_ctl.h flow.h fwd.h fwd_rule.h flow_table.h icmp.h icmp_flow.h \ - inany.h iov.h ip.h isolation.h lineread.h log.h migrate.h ndp.h \ - netlink.h packet.h passt.h pasta.h pcap.h pif.h repair.h serialise.h \ - siphash.h tap.h tcp.h tcp_buf.h tcp_conn.h tcp_internal.h tcp_splice.h \ - tcp_vu.h udp.h udp_flow.h udp_internal.h udp_vu.h util.h vhost_user.h \ - virtio.h vu_common.h +PESTO_SRCS = pesto.c +SRCS = $(PASST_SRCS) $(QRAP_SRCS) $(PASST_REPAIR_SRCS) $(PESTO_SRCS) + +MANPAGES = passt.1 pasta.1 pesto.1 qrap.1 passt-repair.1 + +PASST_HEADERS = arch.h arp.h bitmap.h checksum.h common.h conf.h dhcp.h \ + dhcpv6.h epoll_ctl.h flow.h fwd.h fwd_rule.h flow_table.h icmp.h \ + icmp_flow.h inany.h iov.h ip.h isolation.h lineread.h log.h migrate.h \ + ndp.h netlink.h packet.h passt.h pasta.h pcap.h pesto.h pif.h repair.h \ + serialise.h siphash.h tap.h tcp.h tcp_buf.h tcp_conn.h tcp_internal.h \ + tcp_splice.h tcp_vu.h udp.h udp_flow.h udp_internal.h udp_vu.h util.h \ + vhost_user.h virtio.h vu_common.h QRAP_HEADERS = arp.h ip.h passt.h util.h PASST_REPAIR_HEADERS = linux_dep.h +PESTO_HEADERS = common.h pesto.h
C := \#include
\nint main(){int a=getrandom(0, 0, 0);} ifeq ($(shell printf "$(C)" | $(CC) -S -xc - -o - >/dev/null 2>&1; echo $$?),0) @@ -78,7 +80,7 @@ docdir ?= $(datarootdir)/doc/passt mandir ?= $(datarootdir)/man man1dir ?= $(mandir)/man1 -BASEBIN = passt qrap passt-repair +BASEBIN = passt qrap passt-repair pesto ifeq ($(TARGET_ARCH),x86_64) BASEBIN += passt.avx2 endif @@ -100,6 +102,9 @@ seccomp.h: seccomp.sh $(PASST_SRCS) $(PASST_HEADERS) seccomp_repair.h: seccomp.sh $(PASST_REPAIR_SRCS) $(PASST_REPAIR_HEADERS) @ ARCH="$(TARGET_ARCH)" CC="$(CC)" ./seccomp.sh seccomp_repair.h $(PASST_REPAIR_SRCS)
+seccomp_pesto.h: seccomp.sh $(PESTO_SRCS) + @ ARCH="$(TARGET_ARCH)" CC="$(CC)" ./seccomp.sh seccomp_pesto.h $(PESTO_SRCS) + $(BASEBIN): %: $(CC) $(BASE_CPPFLAGS) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(filter %.c,$^) -o $@
@@ -116,6 +121,8 @@ qrap: $(QRAP_SRCS) $(QRAP_HEADERS)
passt-repair: $(PASST_REPAIR_SRCS) $(PASST_REPAIR_HEADERS) seccomp_repair.h
+pesto: $(PESTO_SRCS) $(PESTO_HEADERS) seccomp_pesto.h + valgrind: EXTRA_SYSCALLS += rt_sigprocmask rt_sigtimedwait rt_sigaction \ rt_sigreturn getpid gettid kill clock_gettime \ mmap|mmap2 munmap open unlink gettimeofday futex \ @@ -126,7 +133,7 @@ valgrind: all
.PHONY: clean clean: - $(RM) $(BIN) *~ *.o seccomp.h seccomp_repair.h pasta.1 \ + $(RM) $(BIN) *~ *.o seccomp.h seccomp_repair.h seccomp_pesto.h pasta.1 \ passt.tar passt.tar.gz *.deb *.rpm \ passt.pid README.plain.md
@@ -183,7 +190,8 @@ docs: README.md CLANG_TIDY = clang-tidy CLANG_TIDY_FLAGS = -DCLANG_TIDY_58992
-clang-tidy: passt.clang-tidy passt-repair.clang-tidy qrap.clang-tidy +clang-tidy: passt.clang-tidy passt-repair.clang-tidy pesto.clang-tidy \ + qrap.clang-tidy
.PHONY: %.clang-tidy %.clang-tidy: @@ -191,6 +199,7 @@ clang-tidy: passt.clang-tidy passt-repair.clang-tidy qrap.clang-tidy
passt.clang-tidy: $(PASST_SRCS) $(PASST_HEADERS) seccomp.h passt-repair.clang-tidy: $(PASST_REPAIR_SRCS) $(PASST_REPAIR_HEADERS) seccomp_repair.h +pesto.clang-tidy: $(PESTO_SRCS) $(PESTO_HEADERS) seccomp_pesto.h qrap.clang-tidy: $(QRAP_SRCS) $(QRAP_HEADERS)
CPPCHECK = cppcheck @@ -206,7 +215,7 @@ CPPCHECK_FLAGS = --std=c11 --error-exitcode=1 --enable=all --force \ --suppress=unusedStructMember \ -D CPPCHECK_6936
-cppcheck: passt.cppcheck passt-repair.cppcheck qrap.cppcheck +cppcheck: passt.cppcheck passt-repair.cppcheck pesto.cppcheck qrap.cppcheck
.PHONY: %.cppcheck %.cppcheck: @@ -215,6 +224,9 @@ cppcheck: passt.cppcheck passt-repair.cppcheck qrap.cppcheck passt.cppcheck: $(PASST_SRCS) $(PASST_HEADERS) seccomp.h passt-repair.cppcheck: $(PASST_REPAIR_SRCS) $(PASST_REPAIR_HEADERS) seccomp_repair.h
+pesto.cppcheck: CPPCHECK_FLAGS += --suppress=unmatchedSuppression +pesto.cppcheck: $(PESTO_SRCS) $(PESTO_HEADERS) seccomp_pesto.h + qrap.cppcheck: BASE_CPPFLAGS += -DARCH=\"$(TARGET_ARCH)\" qrap.cppcheck: CPPCHECK_FLAGS += --suppress=unusedFunction qrap.cppcheck: $(QRAP_SRCS) $(QRAP_HEADERS) diff --git a/common.h b/common.h new file mode 100644 index 0000000..f3506b4 --- /dev/null +++ b/common.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright Red Hat + * Author: David Gibson
+ * + * Definitions used by both passt/pasta and other tools + */ + +#ifndef COMMON_H +#define COMMON_H + +#include + +#define VERSION_BLOB \ + VERSION "\n" \ + "Copyright Red Hat\n" \ + "GNU General Public License, version 2 or later\n" \ + " https://www.gnu.org/licenses/old-licenses/gpl-2.0.html\n" \ + "This is free software: you are free to change and redistribute it.\n" \ + "There is NO WARRANTY, to the extent permitted by law.\n\n" + +/* FPRINTF() intentionally silences cert-err33-c clang-tidy warnings */ +#define FPRINTF(f, ...) (void)fprintf(f, __VA_ARGS__) + +#endif /* COMMON_H */ diff --git a/pesto.1 b/pesto.1 new file mode 100644 index 0000000..b06433d --- /dev/null +++ b/pesto.1 @@ -0,0 +1,59 @@ +.\" SPDX-License-Identifier: GPL-2.0-or-later +.\" Copyright Red Hat +.\" Author: David Gibson +.TH pesto 1 + +.SH NAME +.B pesto +\- Configure a running \fBpasst\fR(1) or \fBpasta\fR(1) instance. + +.SH SYNOPSIS +.B pesto +[\fIOPTION\fR]... \fIPATH\fR + +.SH DESCRIPTION + +.B pesto +is an experimental client to view and update the port forwarding configuration +of a running \fBpasst\fR(1) or \fBpasta\fR(1) instance. + +\fIPATH\fR gives the path to the UNIX domain socket created by \fBpasst\fR or +\fBpasta\fR. It should match the \fB-c\fR command line option given to that +instance. + +.SH OPTIONS + +.TP +.BR \-d ", " \-\-debug +Be verbose. + +.TP +.BR \-h ", " \-\-help +Display a help message and exit. + +.TP +.BR \-\-version +Show version and exit. + +.SH AUTHORS + +Stefano Brivio , +David Gibson . + +.SH REPORTING BUGS + +Please report issues on the bug tracker at https://bugs.passt.top/, or send a +message to the passt-user@passt.top mailing list, see https://lists.passt.top/. + +.SH COPYRIGHT + +Copyright Red Hat + +\fBpesto\fR is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or (at +your option) any later version. + +.SH SEE ALSO + +\fBpasst\fR(1), \fBpasta\fR(1), \fBunix\fR(7). diff --git a/pesto.c b/pesto.c new file mode 100644 index 0000000..9f2fa5d --- /dev/null +++ b/pesto.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* PESTO - Programmable Extensible Socket Translation Orchestrator + * front-end for passt(1) and pasta(1) forwarding configuration + * + * pesto.c - Main program (it's not actually extensible) + * + * Copyright (c) 2026 Red Hat GmbH + * Author: Stefano Brivio + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common.h" +#include "seccomp_pesto.h" +#include "pesto.h" + +static bool debug_flag = false; + +static char stdout_buf[BUFSIZ]; + +#define die(...) \ + do { \ + FPRINTF(stderr, __VA_ARGS__); \ + FPRINTF(stderr, "\n"); \ + exit(EXIT_FAILURE); \ + } while (0) + +/** + * usage() - Print usage, exit with given status code + * @name: Executable name + * @f: Stream to print usage info to + * @status: Status code for exit(2) + * + * #syscalls:pesto exit_group fstat write + */ +static void usage(const char *name, FILE *f, int status) +{ + FPRINTF(f, "Usage: %s [OPTION]... PATH\n", name); + FPRINTF(f, + "\n" + " -d, --debug Print debugging messages\n" + " -h, --help Display this help message and exit\n" + " --version Show version and exit\n"); + exit(status); +} + +/** + * main() - Dynamic reconfiguration client main program + * @argc: Argument count + * @argv: Arguments: socket path, operation, port specifiers + * + * Return: 0 on success, won't return on failure + * + * #syscalls:pesto exit_group fstat read write + */ +int main(int argc, char **argv) +{ + const struct option options[] = { + {"debug", no_argument, NULL, 'd' }, + {"help", no_argument, NULL, 'h' }, + {"version", no_argument, NULL, 1 }, + { 0 }, + }; + const char *optstring = "dh"; + struct sock_fprog prog; + int optname; + + prctl(PR_SET_DUMPABLE, 0); + + prog.len = (unsigned short)sizeof(filter_pesto) / + sizeof(filter_pesto[0]); + prog.filter = filter_pesto; + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) || + prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) + die("Failed to apply seccomp filter"); + + /* Explicitly set stdout buffer, otherwise printf() might allocate, + * breaking our seccomp profile. + */ + if (setvbuf(stdout, stdout_buf, _IOFBF, sizeof(stdout_buf))) + die("Failed to set stdout buffer"); + + do { + optname = getopt_long(argc, argv, optstring, options, NULL); + + switch (optname) { + case -1: + case 0: + break; + case 'h': + usage(argv[0], stdout, EXIT_SUCCESS); + break; + case 'd': + debug_flag = true; + break; + case 1: + FPRINTF(stdout, "pesto "); + FPRINTF(stdout, VERSION_BLOB); + exit(EXIT_SUCCESS); + default: + usage(argv[0], stderr, EXIT_FAILURE); + } + } while (optname != -1); + + if (argc - optind != 1) + usage(argv[0], stderr, EXIT_FAILURE); + + printf("debug_flag=%d, path=\"%s\"\n", debug_flag, argv[optind]); + + die("pesto is not implemented yet"); +} diff --git a/pesto.h b/pesto.h new file mode 100644 index 0000000..e9b329f --- /dev/null +++ b/pesto.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright Red Hat + * Author: David Gibson + * + * Definitions and functions used by both client and server of the configuration + * update protocol (pesto). + */ + +#ifndef PESTO_H +#define PESTO_H + +#endif /* PESTO_H */ diff --git a/util.h b/util.h index 92aeabc..770ff93 100644 --- a/util.h +++ b/util.h @@ -19,16 +19,9 @@ #include #include +#include "common.h" #include "log.h"
-#define VERSION_BLOB \ - VERSION "\n" \ - "Copyright Red Hat\n" \ - "GNU General Public License, version 2 or later\n" \ - " https://www.gnu.org/licenses/old-licenses/gpl-2.0.html\n" \ - "This is free software: you are free to change and redistribute it.\n" \ - "There is NO WARRANTY, to the extent permitted by law.\n\n" - #ifndef SECCOMP_RET_KILL_PROCESS #define SECCOMP_RET_KILL_PROCESS SECCOMP_RET_KILL #endif @@ -307,9 +300,6 @@ static inline bool mod_between(unsigned x, unsigned i, unsigned j, unsigned m) return mod_sub(x, i, m) < mod_sub(j, i, m); }
-/* FPRINTF() intentionally silences cert-err33-c clang-tidy warnings */ -#define FPRINTF(f, ...) (void)fprintf(f, __VA_ARGS__) - void raw_random(void *buf, size_t buflen);
/*
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
This adds parsing of options using fwd_rule_parse(), validates them and adds them to the existing rules. It doesn't yet send those rules back to passt or pasta.
Message-ID: <20260322141843.4095972-3-sbrivio@redhat.com> [dwg: Based on an early draft by Stefano] Signed-off-by: David Gibson
[sbrivio: Recycled usage messages for -T and -U from conf.c as suggested by Laurent, dropped unrelated whitespace change] [sbrivio: Add description of -t, -u, -T, -U to pesto.1]
-T, -U, -s are still missing in pesto.1
otherwise:
Reviewed-by: Laurent Vivier
Signed-off-by: Stefano Brivio
--- Makefile | 1 + fwd_rule.c | 2 +- fwd_rule.h | 1 + pesto.1 | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++ pesto.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 227 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 9e99dd1..c746b55 100644 --- a/Makefile +++ b/Makefile @@ -226,6 +226,7 @@ cppcheck: passt.cppcheck passt-repair.cppcheck pesto.cppcheck qrap.cppcheck passt.cppcheck: BASE_CPPFLAGS += -UPESTO passt.cppcheck: CPPCHECK_FLAGS += \ --suppress=unusedFunction:fwd_rule.c \ + --suppress=staticFunction:fwd_rule.c \ --suppress=unusedFunction:serialise.c passt.cppcheck: $(PASST_SRCS) $(PASST_HEADERS) seccomp.h
diff --git a/fwd_rule.c b/fwd_rule.c index c2824d5..b55e4df 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -187,7 +187,7 @@ static bool fwd_rule_conflicts(const struct fwd_rule *a, const struct fwd_rule * * * Return: 0 on success, negative error code on failure */ -static int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) +int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) { /* Flags which can be set from the caller */ const uint8_t allowed_flags = FWD_WEAK | FWD_SCAN | FWD_DUAL_STACK_ANY; diff --git a/fwd_rule.h b/fwd_rule.h index 330d49e..f43b37d 100644 --- a/fwd_rule.h +++ b/fwd_rule.h @@ -103,6 +103,7 @@ const char *fwd_rule_fmt(const struct fwd_rule *rule, char *dst, size_t size); void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd); int fwd_rule_read(int fd, struct fwd_rule *rule); int fwd_rule_write(int fd, const struct fwd_rule *rule); +int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new);
/** * fwd_rules_dump() - Dump forwarding rules diff --git a/pesto.1 b/pesto.1 index b06433d..32d6ed1 100644 --- a/pesto.1 +++ b/pesto.1 @@ -31,6 +31,123 @@ Be verbose. .BR \-h ", " \-\-help Display a help message and exit.
+.TP +.BR \-t ", " \-\-tcp-ports " " \fIspec +Configure TCP port forwarding to guest or namespace. \fIspec\fR can be one of: +.RS + +.TP +.BR none +Don't forward any ports + +.TP +[\fIaddress\fR[\fB%\fR\fIinterface\fR]\fB/\fR]\fIports\fR ... +Specific ports to forward. Optionally, a specific listening address +and interface name (since Linux 5.7) can be specified. \fIports\fR +may be either: +.RS +.TP +\fBall\fR +Forward all unbound, non-ephemeral ports, as permitted by current capabilities. +No failures are reported for unavailable ports, unless no ports could be +forwarded at all. +.RE + +.RS +or a comma-separated list of entries which may be any of: +.TP +\fIfirst\fR[\fB-\fR\fIlast\fR][\fB:\fR\fItofirst\fR[\fB-\fR\fItolast\fR]] +Include range. Forward port numbers between \fIfirst\fR and \fIlast\fR +(inclusive) to ports between \fItofirst\fR and \fItolast\fR. If +\fItofirst\fR and \fItolast\fR are omitted, assume the same as +\fIfirst\fR and \fIlast\fR. If \fIlast\fR is omitted, assume the same +as \fIfirst\fR. + +.TP +\fB~\fR\fIfirst\fR[\fB-\fR\fIlast\fR] +Exclude range. Don't forward port numbers between \fIfirst\fR and +\fIlast\fR. This takes precedences over include ranges. + +.TP +.BR auto +\fBpasta\fR only. Only forward ports in the specified set if the +target ports are bound in the namespace. The list of ports is +periodically derived (every second) from listening sockets reported by +\fI/proc/net/tcp\fR and \fI/proc/net/tcp6\fR, see \fBproc\fR(5). +.RE + +Specifying excluded ranges only implies that all other non-ephemeral +ports are forwarded. Specifying no ranges at all implies forwarding +all non-ephemeral ports permitted by current capabilities. In this +case, no failures are reported for unavailable ports, unless no ports +could be forwarded at all. + +Examples: +.RS +.TP +-t all +Forward all unbound, non-ephemeral ports as permitted by current +capabilities to the corresponding port on the guest or namespace +.TP +-t ::1/all +For the local address ::1, forward all unbound, non-ephemeral ports as +permitted by current capabilities +.TP +-t 22 +Forward local port 22 to port 22 on the guest or namespace +.TP +-t 22:23 +Forward local port 22 to port 23 on the guest or namespace +.TP +-t 22,25 +Forward local ports 22 and 25 to ports 22 and 25 on the guest or namespace +.TP +-t 22-80 +Forward local ports between 22 and 80 to corresponding ports on the guest or +namespace +.TP +-t 22-80:32-90 +Forward local ports between 22 and 80 to ports between 32 and 90 on the guest or +namespace +.TP +-t 192.0.2.1/22 +Forward local port 22, bound to 192.0.2.1, to port 22 on the guest or namespace +.TP +-t 192.0.2.1%eth0/22 +Forward local port 22, bound to 192.0.2.1 and interface eth0, to port 22 +.TP +-t %eth0/22 +Forward local port 22, bound to any address on interface eth0, to port 22 +.TP +-t 2000-5000,~3000-3010 +Forward local ports between 2000 and 5000, except for those between 3000 and +3010 +.TP +-t 192.0.2.1/20-30,~25 +For the local address 192.0.2.1, forward ports between 20 and 24 and between 26 +and 30 +.TP +-t ~20000-20010 +Forward all ports to the guest, except for the range from 20000 to 20010 +.TP +-t auto +Automatically forward any ports which are bound in the namespace +.TP +-t ::1/auto +Automatically forward any ports which are bound in the namespace, +listening only on local port ::1 +.TP +-t 8000-8010,auto +Forward ports in the range 8000-8010 if and only if they are bound in +the namespace +.RE +.RE + +.TP +.BR \-u ", " \-\-udp-ports " " \fIspec +Configure UDP port forwarding to guest. \fIspec\fR is as described for TCP +above. + .TP .BR \-\-version Show version and exit. diff --git a/pesto.c b/pesto.c index 92a8cb2..16b3a5a 100644 --- a/pesto.c +++ b/pesto.c @@ -55,6 +55,43 @@ static void usage(const char *name, FILE *f, int status) FPRINTF(f, "Usage: %s [OPTION]... PATH\n", name); FPRINTF(f, "\n" + " -t, --tcp-ports SPEC TCP inbound port forwarding\n" + " can be specified multiple times\n" + " SPEC can be:\n" + " 'none': don't forward any ports\n" + " [ADDR[%%IFACE]/]PORTS: forward specific ports\n" + " PORTS is either 'all' (forward all unbound, non-ephemeral\n" + " ports), or a comma-separated list of ports, optionally\n" + " ranged with '-' and optional target ports after ':'.\n" + " Ranges can be reduced by excluding ports or ranges\n" + " prefixed by '~'.\n" + " The 'auto' keyword may be given to only forward\n" + " ports which are bound in the target namespace\n" + " Examples:\n" + " -t all Forward all ports\n" + " -t 127.0.0.1/all Forward all ports from local address\n" + " 127.0.0.1\n" + " -t 22 Forward local port 22 to 22\n" + " -t 22:23 Forward local port 22 to 23\n" + " -t 22,25 Forward ports 22, 25 to ports 22, 25\n" + " -t 22-80 Forward ports 22 to 80\n" + " -t 22-80:32-90 Forward ports 22 to 80 to\n" + " corresponding port numbers plus 10\n" + " -t 192.0.2.1/5 Bind port 5 of 192.0.2.1\n" + " -t 5-25,~10-20 Forward ports 5 to 9, and 21 to 25\n" + " -t ~25 Forward all ports except for 25\n" + " -t auto Forward all ports bound in namespace\n" + " -t 192.0.2.2/auto Forward ports from 192.0.2.2 if\n" + " they are bound in the namespace\n" + " -t 8000-8010,auto Forward ports 8000-8010 if they\n" + " are bound in the namespace\n" + " -u, --udp-ports SPEC UDP inbound port forwarding\n" + " SPEC is as described for TCP above\n" + " -T, --tcp-ns SPEC TCP port forwarding to init namespace\n" + " SPEC is as described above\n" + " -U, --udp-ns SPEC UDP port forwarding to init namespace\n" + " SPEC is as described above\n" + " -s, --show Show configuration before and after\n" " -d, --debug Print debugging messages\n" " -h, --help Display this help message and exit\n" " --version Show version and exit\n"); @@ -207,6 +244,8 @@ static void show_conf(const struct configuration *conf) fwd_rules_dump(printf, pc->fwd.rules, pc->fwd.count, " ", "\n"); } + /* Flush stdout, so this doesn't get misordered with later debug()s */ + (void)fflush(stdout); }
/** @@ -218,7 +257,7 @@ static void show_conf(const struct configuration *conf) * * #syscalls:pesto socket s390x:socketcall i686:socketcall * #syscalls:pesto connect shutdown close - * #syscalls:pesto exit_group fstat read write + * #syscalls:pesto exit_group fstat read write openat */ int main(int argc, char **argv) { @@ -226,11 +265,18 @@ int main(int argc, char **argv) {"debug", no_argument, NULL, 'd' }, {"help", no_argument, NULL, 'h' }, {"version", no_argument, NULL, 1 }, + {"tcp-ports", required_argument, NULL, 't' }, + {"udp-ports", required_argument, NULL, 'u' }, + {"tcp-ns", required_argument, NULL, 'T' }, + {"udp-ns", required_argument, NULL, 'U' }, + {"show", no_argument, NULL, 's' }, { 0 }, }; + struct pif_configuration *inbound, *outbound; struct sockaddr_un a = { AF_UNIX, "" }; + const char *optstring = "dht:u:T:U:s"; struct configuration conf = { 0 }; - const char *optstring = "dh"; + bool update = false, show = false; struct pesto_hello hello; struct sock_fprog prog; int optname, ret, s; @@ -251,6 +297,8 @@ int main(int argc, char **argv) if (setvbuf(stdout, stdout_buf, _IOFBF, sizeof(stdout_buf))) die_perror("Failed to set stdout buffer");
+ fwd_probe_ephemeral(); + do { optname = getopt_long(argc, argv, optstring, options, NULL);
@@ -258,6 +306,16 @@ int main(int argc, char **argv) case -1: case 0: break; + case 't': + case 'u': + case 'T': + case 'U': + /* Parse these options after we've read state from passt/pasta */ + update = true; + break; + case 's': + show = true; + break; case 'h': usage(argv[0], stdout, EXIT_SUCCESS); break; @@ -290,6 +348,8 @@ int main(int argc, char **argv) die_perror("Failed to connect to %s", a.sun_path); }
+ debug("Connected to passt/pasta control socket"); + ret = read_all_buf(s, &hello, sizeof(hello)); if (ret < 0) die_perror("Couldn't read server greeting"); @@ -327,9 +387,52 @@ int main(int argc, char **argv) while (read_pif_conf(s, &conf)) ;
- printf("passt/pasta configuration (%s)\n", a.sun_path); - show_conf(&conf); + if (!update) { + printf("passt/pasta configuration (%s)\n", a.sun_path); + show_conf(&conf); + goto noupdate; + } + + if (show) { + printf("Previous configuration (%s)\n", a.sun_path); + show_conf(&conf); + } + + inbound = pif_conf_by_name(&conf, "HOST"); + outbound = pif_conf_by_name(&conf, "SPLICE"); + + optind = 0; + do { + optname = getopt_long(argc, argv, optstring, options, NULL); + + switch (optname) { + case 't': + case 'u': + if (!inbound) { + die("Can't use -%c, no inbound interface", + optname); + } + fwd_rule_parse(optname, optarg, &inbound->fwd); + break; + case 'T': + case 'U': + if (!outbound) { + die("Can't use -%c, no outbound interface", + optname); + } + fwd_rule_parse(optname, optarg, &outbound->fwd); + break; + default: + continue; + } + } while (optname != -1); + + if (show) { + printf("Updated configuration (%s)\n", a.sun_path); + show_conf(&conf); + }
+noupdate: if (shutdown(s, SHUT_RDWR) < 0 || close(s) < 0) die_perror("Error shutting down control socket");
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
Extend pesto to send the updated rule configuration back to passt/pasta. Extend passt/pasta to read the new configuration and store the new rules in a "pending" table. We don't yet attempt to activate them.
Signed-off-by: Stefano Brivio
[dwg: Based on an early draft from Stefano] [sbrivio: Add redundant check on interface names being terminated in conf_recv_rules(), to make static checkers happy] [sbrivio: Make conf_recv_rules() return -1 if fwd_rule_read() fails, as suggested by Jon Maloy] Signed-off-by: David Gibson
Reviewed-by: Laurent Vivier
--- Makefile | 5 --- conf.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++-------- fwd.c | 10 +++++- passt.h | 2 ++ pesto.c | 35 +++++++++++++++++++++ 5 files changed, 127 insertions(+), 19 deletions(-)
diff --git a/Makefile b/Makefile index c746b55..ae755a0 100644 --- a/Makefile +++ b/Makefile @@ -224,10 +224,6 @@ cppcheck: passt.cppcheck passt-repair.cppcheck pesto.cppcheck qrap.cppcheck $(CPPCHECK) $(CPPCHECK_FLAGS) $(BASE_CPPFLAGS) $^
passt.cppcheck: BASE_CPPFLAGS += -UPESTO -passt.cppcheck: CPPCHECK_FLAGS += \ - --suppress=unusedFunction:fwd_rule.c \ - --suppress=staticFunction:fwd_rule.c \ - --suppress=unusedFunction:serialise.c passt.cppcheck: $(PASST_SRCS) $(PASST_HEADERS) seccomp.h
passt-repair.cppcheck: $(PASST_REPAIR_SRCS) $(PASST_REPAIR_HEADERS) seccomp_repair.h @@ -238,7 +234,6 @@ pesto.cppcheck: CPPCHECK_FLAGS += \ --suppress=unusedFunction:inany.h \ --suppress=unusedFunction:inany.c \ --suppress=unusedFunction:ip.h \ - --suppress=unusedFunction:fwd_rule.c \ --suppress=staticFunction:fwd_rule.c \ --suppress=unusedFunction:serialise.c pesto.cppcheck: $(PESTO_SRCS) $(PESTO_HEADERS) seccomp_pesto.h diff --git a/conf.c b/conf.c index 5e4e81e..f035fd3 100644 --- a/conf.c +++ b/conf.c @@ -1971,6 +1971,62 @@ static int conf_send_rules(const struct ctx *c, int fd) return 0; }
+/** + * conf_recv_rules() - Receive forwarding rules from configuration client + * @c: Execution context + * @fd: Socket to the client + * + * Return: 0 on success, -1 on failure + */ +static int conf_recv_rules(const struct ctx *c, int fd) +{ + while (1) { + struct fwd_table *fwd; + struct fwd_rule r; + uint32_t count; + uint8_t pif; + unsigned i; + + if (read_u8(fd, &pif)) + return -1; + + if (pif == PIF_NONE) + break; + + if (pif >= ARRAY_SIZE(c->fwd_pending) || + !(fwd = c->fwd_pending[pif])) { + err("Received rules for non-existent table"); + return -1; + } + + if (read_u32(fd, &count)) + return -1; + + if (count > MAX_FWD_RULES) { + err("Received %"PRIu32" rules (maximum %u)", + count, MAX_FWD_RULES); + return -1; + } + + for (i = 0; i < count; i++) { + if (fwd_rule_read(fd, &r)) + return -1; + + if (r.ifname[sizeof(r.ifname) - 1]) { + err("Interface name was not NULL terminated"); + return -1; + } + /* Redundant, to make static checkers happy */ + r.ifname[sizeof(r.ifname) - 1] = '\0'; + + if (fwd_rule_add(fwd, &r) < 0) + return -1; + } + } + + return 0; +} + /** * conf_close() - Close configuration / control socket and clean up * @c: Execution context @@ -2075,21 +2131,33 @@ fail: void conf_handler(struct ctx *c, uint32_t events) { if (events & EPOLLIN) { - char discard[BUFSIZ]; - ssize_t n; - - do { - n = read(c->fd_control, discard, sizeof(discard)); - if (n > 0) - debug("Discarded %zd bytes of config data", n); - } while (n > 0); - if (n == 0) { - debug("Configuration client EOF"); - goto close; + unsigned pif; + + /* Clear pending tables */ + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct fwd_table *fwd = c->fwd_pending[pif]; + + if (!fwd) + continue; + fwd->count = 0; + fwd->sock_count = 0; } - if (errno != EAGAIN && errno != EWOULDBLOCK) { - err_perror("Error reading config data"); + + /* FIXME: this could block indefinitely if the client doesn't + * write as much as it should + */ + if (conf_recv_rules(c, c->fd_control) < 0) goto close; + + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct fwd_table *fwd = c->fwd_pending[pif]; + + if (!fwd) + continue; + + info("New forwarding rules for %s:", pif_name(pif)); + fwd_rules_dump(info, fwd->rules, fwd->count, + " ", ""); } }
diff --git a/fwd.c b/fwd.c index 8849cfc..d93d2e5 100644 --- a/fwd.c +++ b/fwd.c @@ -247,6 +247,9 @@ void fwd_neigh_table_init(const struct ctx *c) static struct fwd_table fwd_in; static struct fwd_table fwd_out;
+static struct fwd_table fwd_in_pending; +static struct fwd_table fwd_out_pending; + /** * fwd_rule_init() - Initialise forwarding tables * @c: Execution context @@ -269,10 +272,15 @@ void fwd_rule_init(struct ctx *c) caps |= FWD_CAP_IFNAME;
fwd_in.caps = fwd_out.caps = caps; + fwd_in_pending.caps = fwd_out_pending.caps = caps;
c->fwd[PIF_HOST] = &fwd_in; - if (c->mode == MODE_PASTA) + c->fwd_pending[PIF_HOST] = &fwd_in_pending; + + if (c->mode == MODE_PASTA) { c->fwd[PIF_SPLICE] = &fwd_out; + c->fwd_pending[PIF_SPLICE] = &fwd_out_pending; + } }
/** diff --git a/passt.h b/passt.h index b3f049d..1726965 100644 --- a/passt.h +++ b/passt.h @@ -188,6 +188,7 @@ struct ip6_ctx { * @pasta_ifi: Index of namespace interface for pasta * @pasta_conf_ns: Configure namespace after creating it * @fwd: Forwarding tables + * @fwd_pending: Pending forward tables * @no_tcp: Disable TCP operation * @tcp: Context for TCP protocol handler * @no_udp: Disable UDP operation @@ -270,6 +271,7 @@ struct ctx { int pasta_conf_ns;
struct fwd_table *fwd[PIF_NUM_TYPES]; + struct fwd_table *fwd_pending[PIF_NUM_TYPES];
int no_tcp; struct tcp_ctx tcp; diff --git a/pesto.c b/pesto.c index 16b3a5a..73fdc39 100644 --- a/pesto.c +++ b/pesto.c @@ -230,6 +230,39 @@ static bool read_pif_conf(int fd, struct configuration *conf) return true; }
+/** + * send_conf() - Send updated configuration to passt/pasta + * @fd: Control socket + * @conf: Updated configuration + */ +static void send_conf(int fd, const struct configuration *conf) +{ + unsigned i; +
Perhaps it could be interesting to send a magic number (or a type id) if we want to be able to update something else than the rules in the future? We also can send the length of the data if we want to be able to ignore it if the type id is not supported? (Something like the chunks in IFF or PNG file format... but perhaps it's overcomplicated for our purpose...)
+ for (i = 0; i < conf->npifs; i++) { + const struct pif_configuration *pc = &conf->pif[i]; + unsigned j; + + if (write_u8(fd, pc->pif) < 0) + goto fail; + + if (write_u32(fd, pc->fwd.count) < 0) + goto fail; + + for (j = 0; j < pc->fwd.count; j++) { + if (fwd_rule_write(fd, &pc->fwd.rules[j]) < 0) + goto fail; + } + } + + if (write_u8(fd, PIF_NONE) < 0) + goto fail; + return; + +fail: + die_perror("Error writing to control socket"); +} + /** * show_conf() - Show current configuration obtained from passt/pasta * @conf: Configuration description @@ -432,6 +465,8 @@ int main(int argc, char **argv) show_conf(&conf); }
+ send_conf(s, &conf); + noupdate: if (shutdown(s, SHUT_RDWR) < 0 || close(s) < 0) die_perror("Error shutting down control socket");
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
We can now receive updates to the forwarding rules from the pesto client and store them in a "pending" copy of the forwarding tables. Implement switching to using the new rules.
The logic is in a new fwd_listen_switch(). For now this closes all listening sockets related to the old tables, swaps the active and pending tables, then listens based on the new tables. In future we look to improve this so that we don't temporarily stop listening on ports that both the old and new tables specify.
Signed-off-by: David Gibson
Signed-off-by: Stefano Brivio --- conf.c | 5 ++--- fwd.c | 34 ++++++++++++++++++++++++++++++++++ fwd.h | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/conf.c b/conf.c index f035fd3..75b8291 100644 --- a/conf.c +++ b/conf.c @@ -2159,15 +2159,14 @@ void conf_handler(struct ctx *c, uint32_t events) fwd_rules_dump(info, fwd->rules, fwd->count, " ", ""); } + + fwd_listen_switch(c); }
if (events & EPOLLHUP) { debug("Configuration client hangup"); - goto close; }
- return; - close: conf_close(c);
diff --git a/fwd.c b/fwd.c index d93d2e5..35b9e2b 100644 --- a/fwd.c +++ b/fwd.c @@ -534,6 +534,40 @@ int fwd_listen_init(const struct ctx *c) return 0; }
+/** + * fwd_listen_switch() - Switch from current to pending rules table + * @c: Execution context + */ +void fwd_listen_switch(struct ctx *c) +{ + struct fwd_table *tmp[PIF_NUM_TYPES]; + unsigned i; + + /* Stop listening on the old tables */ + for (i = 0; i < PIF_NUM_TYPES; i++) { + struct fwd_table *fwd = c->fwd[i]; + + if (!fwd) + continue; + + debug("Flushing %u old %s rules", fwd->count, pif_name(i)); + fwd_listen_close(fwd); + fwd->count = fwd->sock_count = 0;
Perhaps we can reset fwd->count and fwd->sock_count in fwd_listen_close() as after fwd_listen_close() these values are wrong?
+ } + + /* Swap active and pending tables */ + static_assert(sizeof(tmp) == sizeof(c->fwd) && + sizeof(tmp) == sizeof(c->fwd_pending), + "Temporary has wrong size"); + memcpy(&tmp, (void *)c->fwd, sizeof(tmp)); + memcpy((void *)c->fwd, (void *)c->fwd_pending, sizeof(tmp)); + memcpy((void *)c->fwd_pending, &tmp, sizeof(tmp));
I know we have the static_assert(), but with memcpy() we usually use the sizeof() of the destination to avoid write overflow. Why do we keep the old active table? Do we plan to have a "--restore" option?
+ + /* Start listening on the new tables */ + if (fwd_listen_init(c) < 0) + err("Error switching to new forwarding rules"); +} + /* See enum in kernel's include/net/tcp_states.h */ #define UDP_LISTEN 0x07 #define TCP_LISTEN 0x0a diff --git a/fwd.h b/fwd.h index ac24782..b60697d 100644 --- a/fwd.h +++ b/fwd.h @@ -61,6 +61,7 @@ int fwd_listen_sync(const struct ctx *c, uint8_t pif, const struct fwd_scan *tcp, const struct fwd_scan *udp); void fwd_listen_close(const struct fwd_table *fwd); int fwd_listen_init(const struct ctx *c); +void fwd_listen_switch(struct ctx *c);
bool nat_inbound(const struct ctx *c, const union inany_addr *addr, union inany_addr *translated);
On Tue, May 05, 2026 at 11:08:27AM +0200, Laurent Vivier wrote:
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
We can now receive updates to the forwarding rules from the pesto client and store them in a "pending" copy of the forwarding tables. Implement switching to using the new rules.
The logic is in a new fwd_listen_switch(). For now this closes all listening sockets related to the old tables, swaps the active and pending tables, then listens based on the new tables. In future we look to improve this so that we don't temporarily stop listening on ports that both the old and new tables specify.
Signed-off-by: David Gibson
Signed-off-by: Stefano Brivio --- conf.c | 5 ++--- fwd.c | 34 ++++++++++++++++++++++++++++++++++ fwd.h | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/conf.c b/conf.c index f035fd3..75b8291 100644 --- a/conf.c +++ b/conf.c @@ -2159,15 +2159,14 @@ void conf_handler(struct ctx *c, uint32_t events) fwd_rules_dump(info, fwd->rules, fwd->count, " ", ""); } + + fwd_listen_switch(c); } if (events & EPOLLHUP) { debug("Configuration client hangup"); - goto close; } - return; - close: conf_close(c); diff --git a/fwd.c b/fwd.c index d93d2e5..35b9e2b 100644 --- a/fwd.c +++ b/fwd.c @@ -534,6 +534,40 @@ int fwd_listen_init(const struct ctx *c) return 0; } +/** + * fwd_listen_switch() - Switch from current to pending rules table + * @c: Execution context + */ +void fwd_listen_switch(struct ctx *c) +{ + struct fwd_table *tmp[PIF_NUM_TYPES]; + unsigned i; + + /* Stop listening on the old tables */ + for (i = 0; i < PIF_NUM_TYPES; i++) { + struct fwd_table *fwd = c->fwd[i]; + + if (!fwd) + continue; + + debug("Flushing %u old %s rules", fwd->count, pif_name(i)); + fwd_listen_close(fwd); + fwd->count = fwd->sock_count = 0;
Perhaps we can reset fwd->count and fwd->sock_count in fwd_listen_close() as after fwd_listen_close() these values are wrong?
No, they're not. fwd_listen_close() closes the listening sockets, but it doesn't remove the rules. fwd->sock_count isn't the number of *open* listening sockets, it's the maximum potential number of sockets for all the rules. Having some or all of the sockets close (-1 stored in the array) is an allowed state. It's rare for most rules, but routine for SCAN ("auto") rules.
+ } + + /* Swap active and pending tables */ + static_assert(sizeof(tmp) == sizeof(c->fwd) && + sizeof(tmp) == sizeof(c->fwd_pending), + "Temporary has wrong size"); + memcpy(&tmp, (void *)c->fwd, sizeof(tmp)); + memcpy((void *)c->fwd, (void *)c->fwd_pending, sizeof(tmp)); + memcpy((void *)c->fwd_pending, &tmp, sizeof(tmp));
I know we have the static_assert(), but with memcpy() we usually use the sizeof() of the destination to avoid write overflow.
Why do we keep the old active table? Do we plan to have a "--restore" option?
Sort of. There are two reasons we keep the table around. One is allow for at rollback if switching to the new one fails. The other is that we'll need it in order to allow for rule changes without interrupting listening sockets.
+ + /* Start listening on the new tables */ + if (fwd_listen_init(c) < 0) + err("Error switching to new forwarding rules"); +} + /* See enum in kernel's include/net/tcp_states.h */ #define UDP_LISTEN 0x07 #define TCP_LISTEN 0x0a diff --git a/fwd.h b/fwd.h index ac24782..b60697d 100644 --- a/fwd.h +++ b/fwd.h @@ -61,6 +61,7 @@ int fwd_listen_sync(const struct ctx *c, uint8_t pif, const struct fwd_scan *tcp, const struct fwd_scan *udp); void fwd_listen_close(const struct fwd_table *fwd); int fwd_listen_init(const struct ctx *c); +void fwd_listen_switch(struct ctx *c); bool nat_inbound(const struct ctx *c, const union inany_addr *addr, union inany_addr *translated);
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
On Tue, May 05, 2026 at 09:53:04AM +0200, Laurent Vivier wrote:
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
Extend pesto to send the updated rule configuration back to passt/pasta. Extend passt/pasta to read the new configuration and store the new rules in a "pending" table. We don't yet attempt to activate them.
Signed-off-by: Stefano Brivio
[dwg: Based on an early draft from Stefano] [sbrivio: Add redundant check on interface names being terminated in conf_recv_rules(), to make static checkers happy] [sbrivio: Make conf_recv_rules() return -1 if fwd_rule_read() fails, as suggested by Jon Maloy] Signed-off-by: David Gibson Reviewed-by: Laurent Vivier
But one comment below
--- Makefile | 5 --- conf.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++-------- fwd.c | 10 +++++- passt.h | 2 ++ pesto.c | 35 +++++++++++++++++++++ 5 files changed, 127 insertions(+), 19 deletions(-)
diff --git a/Makefile b/Makefile index c746b55..ae755a0 100644 --- a/Makefile +++ b/Makefile @@ -224,10 +224,6 @@ cppcheck: passt.cppcheck passt-repair.cppcheck pesto.cppcheck qrap.cppcheck $(CPPCHECK) $(CPPCHECK_FLAGS) $(BASE_CPPFLAGS) $^ passt.cppcheck: BASE_CPPFLAGS += -UPESTO -passt.cppcheck: CPPCHECK_FLAGS += \ - --suppress=unusedFunction:fwd_rule.c \ - --suppress=staticFunction:fwd_rule.c \ - --suppress=unusedFunction:serialise.c passt.cppcheck: $(PASST_SRCS) $(PASST_HEADERS) seccomp.h passt-repair.cppcheck: $(PASST_REPAIR_SRCS) $(PASST_REPAIR_HEADERS) seccomp_repair.h @@ -238,7 +234,6 @@ pesto.cppcheck: CPPCHECK_FLAGS += \ --suppress=unusedFunction:inany.h \ --suppress=unusedFunction:inany.c \ --suppress=unusedFunction:ip.h \ - --suppress=unusedFunction:fwd_rule.c \ --suppress=staticFunction:fwd_rule.c \ --suppress=unusedFunction:serialise.c pesto.cppcheck: $(PESTO_SRCS) $(PESTO_HEADERS) seccomp_pesto.h diff --git a/conf.c b/conf.c index 5e4e81e..f035fd3 100644 --- a/conf.c +++ b/conf.c @@ -1971,6 +1971,62 @@ static int conf_send_rules(const struct ctx *c, int fd) return 0; } +/** + * conf_recv_rules() - Receive forwarding rules from configuration client + * @c: Execution context + * @fd: Socket to the client + * + * Return: 0 on success, -1 on failure + */ +static int conf_recv_rules(const struct ctx *c, int fd) +{ + while (1) { + struct fwd_table *fwd; + struct fwd_rule r; + uint32_t count; + uint8_t pif; + unsigned i; + + if (read_u8(fd, &pif)) + return -1; + + if (pif == PIF_NONE) + break; + + if (pif >= ARRAY_SIZE(c->fwd_pending) || + !(fwd = c->fwd_pending[pif])) { + err("Received rules for non-existent table"); + return -1; + } + + if (read_u32(fd, &count)) + return -1; + + if (count > MAX_FWD_RULES) { + err("Received %"PRIu32" rules (maximum %u)", + count, MAX_FWD_RULES); + return -1; + } + + for (i = 0; i < count; i++) { + if (fwd_rule_read(fd, &r)) + return -1; + + if (r.ifname[sizeof(r.ifname) - 1]) { + err("Interface name was not NULL terminated"); + return -1; + } + /* Redundant, to make static checkers happy */ + r.ifname[sizeof(r.ifname) - 1] = '\0'; + + if (fwd_rule_add(fwd, &r) < 0) + return -1; + } + } + + return 0; +} + /** * conf_close() - Close configuration / control socket and clean up * @c: Execution context @@ -2075,21 +2131,33 @@ fail: void conf_handler(struct ctx *c, uint32_t events) { if (events & EPOLLIN) { - char discard[BUFSIZ]; - ssize_t n; - - do { - n = read(c->fd_control, discard, sizeof(discard)); - if (n > 0) - debug("Discarded %zd bytes of config data", n); - } while (n > 0); - if (n == 0) { - debug("Configuration client EOF"); - goto close; + unsigned pif; + + /* Clear pending tables */ + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct fwd_table *fwd = c->fwd_pending[pif]; + + if (!fwd) + continue; + fwd->count = 0; + fwd->sock_count = 0; } - if (errno != EAGAIN && errno != EWOULDBLOCK) { - err_perror("Error reading config data"); + + /* FIXME: this could block indefinitely if the client doesn't + * write as much as it should + */ + if (conf_recv_rules(c, c->fd_control) < 0) goto close; + + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct fwd_table *fwd = c->fwd_pending[pif]; + + if (!fwd) + continue; + + info("New forwarding rules for %s:", pif_name(pif)); + fwd_rules_dump(info, fwd->rules, fwd->count, + " ", ""); } } diff --git a/fwd.c b/fwd.c index 8849cfc..d93d2e5 100644 --- a/fwd.c +++ b/fwd.c @@ -247,6 +247,9 @@ void fwd_neigh_table_init(const struct ctx *c) static struct fwd_table fwd_in; static struct fwd_table fwd_out; +static struct fwd_table fwd_in_pending; +static struct fwd_table fwd_out_pending; + /** * fwd_rule_init() - Initialise forwarding tables * @c: Execution context @@ -269,10 +272,15 @@ void fwd_rule_init(struct ctx *c) caps |= FWD_CAP_IFNAME; fwd_in.caps = fwd_out.caps = caps; + fwd_in_pending.caps = fwd_out_pending.caps = caps; c->fwd[PIF_HOST] = &fwd_in; - if (c->mode == MODE_PASTA) + c->fwd_pending[PIF_HOST] = &fwd_in_pending; + + if (c->mode == MODE_PASTA) { c->fwd[PIF_SPLICE] = &fwd_out; + c->fwd_pending[PIF_SPLICE] = &fwd_out_pending; + } } /** diff --git a/passt.h b/passt.h index b3f049d..1726965 100644 --- a/passt.h +++ b/passt.h @@ -188,6 +188,7 @@ struct ip6_ctx { * @pasta_ifi: Index of namespace interface for pasta * @pasta_conf_ns: Configure namespace after creating it * @fwd: Forwarding tables + * @fwd_pending: Pending forward tables * @no_tcp: Disable TCP operation * @tcp: Context for TCP protocol handler * @no_udp: Disable UDP operation @@ -270,6 +271,7 @@ struct ctx { int pasta_conf_ns; struct fwd_table *fwd[PIF_NUM_TYPES]; + struct fwd_table *fwd_pending[PIF_NUM_TYPES]; int no_tcp; struct tcp_ctx tcp; diff --git a/pesto.c b/pesto.c index 16b3a5a..73fdc39 100644 --- a/pesto.c +++ b/pesto.c @@ -230,6 +230,39 @@ static bool read_pif_conf(int fd, struct configuration *conf) return true; } +/** + * send_conf() - Send updated configuration to passt/pasta + * @fd: Control socket + * @conf: Updated configuration + */ +static void send_conf(int fd, const struct configuration *conf) +{ + unsigned i; +
Perhaps it could be interesting to send a magic number (or a type id) if we want to be able to update something else than the rules in the future? We also can send the length of the data if we want to be able to ignore it if the type id is not supported? (Something like the chunks in IFF or PNG file format... but perhaps it's overcomplicated for our purpose...)
Undoubtedly, amongst a bunch of other ways we could make the protocol more flexible. However, I'm inclined to leave that v2.
+ for (i = 0; i < conf->npifs; i++) { + const struct pif_configuration *pc = &conf->pif[i]; + unsigned j; + + if (write_u8(fd, pc->pif) < 0) + goto fail; + + if (write_u32(fd, pc->fwd.count) < 0) + goto fail; + + for (j = 0; j < pc->fwd.count; j++) { + if (fwd_rule_write(fd, &pc->fwd.rules[j]) < 0) + goto fail; + } + } + + if (write_u8(fd, PIF_NONE) < 0) + goto fail; + return; + +fail: + die_perror("Error writing to control socket"); +} + /** * show_conf() - Show current configuration obtained from passt/pasta * @conf: Configuration description @@ -432,6 +465,8 @@ int main(int argc, char **argv) show_conf(&conf); } + send_conf(s, &conf); + noupdate: if (shutdown(s, SHUT_RDWR) < 0 || close(s) < 0) die_perror("Error shutting down control socket");
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
On Tue, 5 May 2026 09:53:04 +0200
Laurent Vivier
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
Extend pesto to send the updated rule configuration back to passt/pasta. Extend passt/pasta to read the new configuration and store the new rules in a "pending" table. We don't yet attempt to activate them.
Signed-off-by: Stefano Brivio
[dwg: Based on an early draft from Stefano] [sbrivio: Add redundant check on interface names being terminated in conf_recv_rules(), to make static checkers happy] [sbrivio: Make conf_recv_rules() return -1 if fwd_rule_read() fails, as suggested by Jon Maloy] Signed-off-by: David Gibson Reviewed-by: Laurent Vivier
But one comment below
--- Makefile | 5 --- conf.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++-------- fwd.c | 10 +++++- passt.h | 2 ++ pesto.c | 35 +++++++++++++++++++++ 5 files changed, 127 insertions(+), 19 deletions(-)
diff --git a/Makefile b/Makefile index c746b55..ae755a0 100644 --- a/Makefile +++ b/Makefile @@ -224,10 +224,6 @@ cppcheck: passt.cppcheck passt-repair.cppcheck pesto.cppcheck qrap.cppcheck $(CPPCHECK) $(CPPCHECK_FLAGS) $(BASE_CPPFLAGS) $^
passt.cppcheck: BASE_CPPFLAGS += -UPESTO -passt.cppcheck: CPPCHECK_FLAGS += \ - --suppress=unusedFunction:fwd_rule.c \ - --suppress=staticFunction:fwd_rule.c \ - --suppress=unusedFunction:serialise.c passt.cppcheck: $(PASST_SRCS) $(PASST_HEADERS) seccomp.h
passt-repair.cppcheck: $(PASST_REPAIR_SRCS) $(PASST_REPAIR_HEADERS) seccomp_repair.h @@ -238,7 +234,6 @@ pesto.cppcheck: CPPCHECK_FLAGS += \ --suppress=unusedFunction:inany.h \ --suppress=unusedFunction:inany.c \ --suppress=unusedFunction:ip.h \ - --suppress=unusedFunction:fwd_rule.c \ --suppress=staticFunction:fwd_rule.c \ --suppress=unusedFunction:serialise.c pesto.cppcheck: $(PESTO_SRCS) $(PESTO_HEADERS) seccomp_pesto.h diff --git a/conf.c b/conf.c index 5e4e81e..f035fd3 100644 --- a/conf.c +++ b/conf.c @@ -1971,6 +1971,62 @@ static int conf_send_rules(const struct ctx *c, int fd) return 0; }
+/** + * conf_recv_rules() - Receive forwarding rules from configuration client + * @c: Execution context + * @fd: Socket to the client + * + * Return: 0 on success, -1 on failure + */ +static int conf_recv_rules(const struct ctx *c, int fd) +{ + while (1) { + struct fwd_table *fwd; + struct fwd_rule r; + uint32_t count; + uint8_t pif; + unsigned i; + + if (read_u8(fd, &pif)) + return -1; + + if (pif == PIF_NONE) + break; + + if (pif >= ARRAY_SIZE(c->fwd_pending) || + !(fwd = c->fwd_pending[pif])) { + err("Received rules for non-existent table"); + return -1; + } + + if (read_u32(fd, &count)) + return -1; + + if (count > MAX_FWD_RULES) { + err("Received %"PRIu32" rules (maximum %u)", + count, MAX_FWD_RULES); + return -1; + } + + for (i = 0; i < count; i++) { + if (fwd_rule_read(fd, &r)) + return -1; + + if (r.ifname[sizeof(r.ifname) - 1]) { + err("Interface name was not NULL terminated"); + return -1; + } + /* Redundant, to make static checkers happy */ + r.ifname[sizeof(r.ifname) - 1] = '\0'; + + if (fwd_rule_add(fwd, &r) < 0) + return -1; + } + } + + return 0; +} + /** * conf_close() - Close configuration / control socket and clean up * @c: Execution context @@ -2075,21 +2131,33 @@ fail: void conf_handler(struct ctx *c, uint32_t events) { if (events & EPOLLIN) { - char discard[BUFSIZ]; - ssize_t n; - - do { - n = read(c->fd_control, discard, sizeof(discard)); - if (n > 0) - debug("Discarded %zd bytes of config data", n); - } while (n > 0); - if (n == 0) { - debug("Configuration client EOF"); - goto close; + unsigned pif; + + /* Clear pending tables */ + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct fwd_table *fwd = c->fwd_pending[pif]; + + if (!fwd) + continue; + fwd->count = 0; + fwd->sock_count = 0; } - if (errno != EAGAIN && errno != EWOULDBLOCK) { - err_perror("Error reading config data"); + + /* FIXME: this could block indefinitely if the client doesn't + * write as much as it should + */ + if (conf_recv_rules(c, c->fd_control) < 0) goto close; + + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct fwd_table *fwd = c->fwd_pending[pif]; + + if (!fwd) + continue; + + info("New forwarding rules for %s:", pif_name(pif)); + fwd_rules_dump(info, fwd->rules, fwd->count, + " ", ""); } }
diff --git a/fwd.c b/fwd.c index 8849cfc..d93d2e5 100644 --- a/fwd.c +++ b/fwd.c @@ -247,6 +247,9 @@ void fwd_neigh_table_init(const struct ctx *c) static struct fwd_table fwd_in; static struct fwd_table fwd_out;
+static struct fwd_table fwd_in_pending; +static struct fwd_table fwd_out_pending; + /** * fwd_rule_init() - Initialise forwarding tables * @c: Execution context @@ -269,10 +272,15 @@ void fwd_rule_init(struct ctx *c) caps |= FWD_CAP_IFNAME;
fwd_in.caps = fwd_out.caps = caps; + fwd_in_pending.caps = fwd_out_pending.caps = caps;
c->fwd[PIF_HOST] = &fwd_in; - if (c->mode == MODE_PASTA) + c->fwd_pending[PIF_HOST] = &fwd_in_pending; + + if (c->mode == MODE_PASTA) { c->fwd[PIF_SPLICE] = &fwd_out; + c->fwd_pending[PIF_SPLICE] = &fwd_out_pending; + } }
/** diff --git a/passt.h b/passt.h index b3f049d..1726965 100644 --- a/passt.h +++ b/passt.h @@ -188,6 +188,7 @@ struct ip6_ctx { * @pasta_ifi: Index of namespace interface for pasta * @pasta_conf_ns: Configure namespace after creating it * @fwd: Forwarding tables + * @fwd_pending: Pending forward tables * @no_tcp: Disable TCP operation * @tcp: Context for TCP protocol handler * @no_udp: Disable UDP operation @@ -270,6 +271,7 @@ struct ctx { int pasta_conf_ns;
struct fwd_table *fwd[PIF_NUM_TYPES]; + struct fwd_table *fwd_pending[PIF_NUM_TYPES];
int no_tcp; struct tcp_ctx tcp; diff --git a/pesto.c b/pesto.c index 16b3a5a..73fdc39 100644 --- a/pesto.c +++ b/pesto.c @@ -230,6 +230,39 @@ static bool read_pif_conf(int fd, struct configuration *conf) return true; }
+/** + * send_conf() - Send updated configuration to passt/pasta + * @fd: Control socket + * @conf: Updated configuration + */ +static void send_conf(int fd, const struct configuration *conf) +{ + unsigned i; +
Perhaps it could be interesting to send a magic number (or a type id) if we want to be able to update something else than the rules in the future? We also can send the length of the data if we want to be able to ignore it if the type id is not supported? (Something like the chunks in IFF or PNG file format... but perhaps it's overcomplicated for our purpose...)
I think eventually we will need something like that (we might want to change addresses, options, etc.), but the idea for the moment is to keep the complexity to a minimum by hiding everything behind the protocol version number. The day we want to support something on top of forwarding rules we'll just bump the version and add type identifiers (I guess with length as you mentioned). Right now we're pretty much failing to deliver something that Podman can still use for their 6.0 plans (hopefully 6.1 is still in scope but I wouldn't take that for granted), so I'd definitely keep this kind of stuff for later. -- Stefano
On Tue, 5 May 2026 11:08:27 +0200
Laurent Vivier
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
We can now receive updates to the forwarding rules from the pesto client and store them in a "pending" copy of the forwarding tables. Implement switching to using the new rules.
The logic is in a new fwd_listen_switch(). For now this closes all listening sockets related to the old tables, swaps the active and pending tables, then listens based on the new tables. In future we look to improve this so that we don't temporarily stop listening on ports that both the old and new tables specify.
Signed-off-by: David Gibson
Signed-off-by: Stefano Brivio --- conf.c | 5 ++--- fwd.c | 34 ++++++++++++++++++++++++++++++++++ fwd.h | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/conf.c b/conf.c index f035fd3..75b8291 100644 --- a/conf.c +++ b/conf.c @@ -2159,15 +2159,14 @@ void conf_handler(struct ctx *c, uint32_t events) fwd_rules_dump(info, fwd->rules, fwd->count, " ", ""); } + + fwd_listen_switch(c); }
if (events & EPOLLHUP) { debug("Configuration client hangup"); - goto close; }
- return; - close: conf_close(c);
diff --git a/fwd.c b/fwd.c index d93d2e5..35b9e2b 100644 --- a/fwd.c +++ b/fwd.c @@ -534,6 +534,40 @@ int fwd_listen_init(const struct ctx *c) return 0; }
+/** + * fwd_listen_switch() - Switch from current to pending rules table + * @c: Execution context + */ +void fwd_listen_switch(struct ctx *c) +{ + struct fwd_table *tmp[PIF_NUM_TYPES]; + unsigned i; + + /* Stop listening on the old tables */ + for (i = 0; i < PIF_NUM_TYPES; i++) { + struct fwd_table *fwd = c->fwd[i]; + + if (!fwd) + continue; + + debug("Flushing %u old %s rules", fwd->count, pif_name(i)); + fwd_listen_close(fwd); + fwd->count = fwd->sock_count = 0;
Perhaps we can reset fwd->count and fwd->sock_count in fwd_listen_close() as after fwd_listen_close() these values are wrong?
Right, while not strictly necessary it still looks like a good idea, I'll change that.
+ } + + /* Swap active and pending tables */ + static_assert(sizeof(tmp) == sizeof(c->fwd) && + sizeof(tmp) == sizeof(c->fwd_pending), + "Temporary has wrong size"); + memcpy(&tmp, (void *)c->fwd, sizeof(tmp)); + memcpy((void *)c->fwd, (void *)c->fwd_pending, sizeof(tmp)); + memcpy((void *)c->fwd_pending, &tmp, sizeof(tmp));
I know we have the static_assert(), but with memcpy() we usually use the sizeof() of the destination to avoid write overflow.
I'll change this as well.
Why do we keep the old active table? Do we plan to have a "--restore" option?
It's just to add and delete rules using a temporary table so that we can abort cleanly and atomically on errors. Are you asking why we don't wipe the old table afterwards? No particular reason for that, even though I'm not sure if it's useful. Actually some kind of --restore option might be desirable, even though we would probably need to re-validate all the rules, or keep a "dirty" bit that's set on other types of changes and would tell us that the previous table can't be used as it is anymore. -- Stefano
+ + /* Start listening on the new tables */ + if (fwd_listen_init(c) < 0) + err("Error switching to new forwarding rules"); +} + /* See enum in kernel's include/net/tcp_states.h */ #define UDP_LISTEN 0x07 #define TCP_LISTEN 0x0a diff --git a/fwd.h b/fwd.h index ac24782..b60697d 100644 --- a/fwd.h +++ b/fwd.h @@ -61,6 +61,7 @@ int fwd_listen_sync(const struct ctx *c, uint8_t pif, const struct fwd_scan *tcp, const struct fwd_scan *udp); void fwd_listen_close(const struct fwd_table *fwd); int fwd_listen_init(const struct ctx *c); +void fwd_listen_switch(struct ctx *c);
bool nat_inbound(const struct ctx *c, const union inany_addr *addr, union inany_addr *translated);
On Tue, 5 May 2026 16:22:43 +1000
David Gibson
On Tue, May 05, 2026 at 01:11:42AM +0200, Stefano Brivio wrote:
The new checks are actually sufficient but not enough for Coverity Scan. Now that fwd->sock_count and new->last are affected or supplied by clients, we need explicit (albeit redundant) checks on them.
Signed-off-by: Stefano Brivio
I'm assuming this does squash the warnings, but I think it does so in a somewhat confusing way.
You don't need to assume that, you could try yourself without this patch and you'll see exactly two warnings with a lot of details.
--- fwd_rule.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/fwd_rule.c b/fwd_rule.c index b55e4df..03e8e80 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -271,13 +271,22 @@ int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) warn("Too many rules (maximum %d)", ARRAY_SIZE(fwd->rules)); return -ENOSPC; } + if ((fwd->sock_count + num) > ARRAY_SIZE(fwd->socks)) { warn("Rules require too many listening sockets (maximum %d)", ARRAY_SIZE(fwd->socks)); return -ENOSPC; } + /* Redundant, to make static checkers happy */ + if (fwd->sock_count > ARRAY_SIZE(fwd->socks)) + return -ENOSPC;
So there's actually two conditions that this is kind of relevant to:
1) (fwd->sock_count > ARRAY_SIZE(fwd->socks)) on entry
That means something is horribly wrong before we were even called. So, I think that would be better as an assert().
2) (fwd->sock_count + num) overflows
That's a closer-to-real concern. I'm pretty sure we can't hit it for real, because num is necessarily <= 65536, so as long as (1) is true this can't overflow. But that relies on the specific value of ARRAY_SIZE(fwd->socks), so it's kind of fragile.
I think an explicit check for this is a good idea, but it should actually check for this, not just side-effects of it, so: if (fwd->sock_count + num <= fwd->sock_count) { warn("Blah blah overflow"); return -EFAULT; /* or whatever */ }
fwd->rulesocks[fwd->count] = &fwd->socks[fwd->sock_count]; + + /* Redundant ('num' checked above), but not for static checkers */ + if (new->last > ARRAY_SIZE(fwd->socks) + new->first) + return -ENOSPC;
This way of organising the check is very confusing to me. I'm not really sure what it's trying to catch.
Same as above.
We've already checked that last >= first, so using num is safer to deal with at this point than ARRAY_SIZE() + first, which could in principle overflow even if sock_count + num is perfectly ok.
Using 'num' won't work. It shouldn't overflow anyway because the addition happens in 'int'. I'll try to change the rest if I find some time but it doesn't really look that critical to me.
for (port = new->first; port <= new->last; port++) fwd->rulesocks[fwd->count][port - new->first] = -1;
-- 2.43.0
-- Stefano
On Tue, 5 May 2026 19:53:43 +1000
David Gibson
On Tue, May 05, 2026 at 11:08:27AM +0200, Laurent Vivier wrote:
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
We can now receive updates to the forwarding rules from the pesto client and store them in a "pending" copy of the forwarding tables. Implement switching to using the new rules.
The logic is in a new fwd_listen_switch(). For now this closes all listening sockets related to the old tables, swaps the active and pending tables, then listens based on the new tables. In future we look to improve this so that we don't temporarily stop listening on ports that both the old and new tables specify.
Signed-off-by: David Gibson
Signed-off-by: Stefano Brivio --- conf.c | 5 ++--- fwd.c | 34 ++++++++++++++++++++++++++++++++++ fwd.h | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/conf.c b/conf.c index f035fd3..75b8291 100644 --- a/conf.c +++ b/conf.c @@ -2159,15 +2159,14 @@ void conf_handler(struct ctx *c, uint32_t events) fwd_rules_dump(info, fwd->rules, fwd->count, " ", ""); } + + fwd_listen_switch(c); } if (events & EPOLLHUP) { debug("Configuration client hangup"); - goto close; } - return; - close: conf_close(c); diff --git a/fwd.c b/fwd.c index d93d2e5..35b9e2b 100644 --- a/fwd.c +++ b/fwd.c @@ -534,6 +534,40 @@ int fwd_listen_init(const struct ctx *c) return 0; } +/** + * fwd_listen_switch() - Switch from current to pending rules table + * @c: Execution context + */ +void fwd_listen_switch(struct ctx *c) +{ + struct fwd_table *tmp[PIF_NUM_TYPES]; + unsigned i; + + /* Stop listening on the old tables */ + for (i = 0; i < PIF_NUM_TYPES; i++) { + struct fwd_table *fwd = c->fwd[i]; + + if (!fwd) + continue; + + debug("Flushing %u old %s rules", fwd->count, pif_name(i)); + fwd_listen_close(fwd); + fwd->count = fwd->sock_count = 0;
Perhaps we can reset fwd->count and fwd->sock_count in fwd_listen_close() as after fwd_listen_close() these values are wrong?
No, they're not. fwd_listen_close() closes the listening sockets, but it doesn't remove the rules. fwd->sock_count isn't the number of *open* listening sockets, it's the maximum potential number of sockets for all the rules. Having some or all of the sockets close (-1 stored in the array) is an allowed state. It's rare for most rules, but routine for SCAN ("auto") rules.
Ah, oops, I didn't realise that would be the case for "auto" rules. I'll leave this part as it is then. -- Stefano
On 5/5/26 12:15, Stefano Brivio wrote:
On Tue, 5 May 2026 19:53:43 +1000 David Gibson
wrote: On Tue, May 05, 2026 at 11:08:27AM +0200, Laurent Vivier wrote:
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
We can now receive updates to the forwarding rules from the pesto client and store them in a "pending" copy of the forwarding tables. Implement switching to using the new rules.
The logic is in a new fwd_listen_switch(). For now this closes all listening sockets related to the old tables, swaps the active and pending tables, then listens based on the new tables. In future we look to improve this so that we don't temporarily stop listening on ports that both the old and new tables specify.
Signed-off-by: David Gibson
Signed-off-by: Stefano Brivio --- conf.c | 5 ++--- fwd.c | 34 ++++++++++++++++++++++++++++++++++ fwd.h | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/conf.c b/conf.c index f035fd3..75b8291 100644 --- a/conf.c +++ b/conf.c @@ -2159,15 +2159,14 @@ void conf_handler(struct ctx *c, uint32_t events) fwd_rules_dump(info, fwd->rules, fwd->count, " ", ""); } + + fwd_listen_switch(c); } if (events & EPOLLHUP) { debug("Configuration client hangup"); - goto close; } - return; - close: conf_close(c); diff --git a/fwd.c b/fwd.c index d93d2e5..35b9e2b 100644 --- a/fwd.c +++ b/fwd.c @@ -534,6 +534,40 @@ int fwd_listen_init(const struct ctx *c) return 0; } +/** + * fwd_listen_switch() - Switch from current to pending rules table + * @c: Execution context + */ +void fwd_listen_switch(struct ctx *c) +{ + struct fwd_table *tmp[PIF_NUM_TYPES]; + unsigned i; + + /* Stop listening on the old tables */ + for (i = 0; i < PIF_NUM_TYPES; i++) { + struct fwd_table *fwd = c->fwd[i]; + + if (!fwd) + continue; + + debug("Flushing %u old %s rules", fwd->count, pif_name(i)); + fwd_listen_close(fwd); + fwd->count = fwd->sock_count = 0;
Perhaps we can reset fwd->count and fwd->sock_count in fwd_listen_close() as after fwd_listen_close() these values are wrong?
No, they're not. fwd_listen_close() closes the listening sockets, but it doesn't remove the rules. fwd->sock_count isn't the number of *open* listening sockets, it's the maximum potential number of sockets for all the rules. Having some or all of the sockets close (-1 stored in the array) is an allowed state. It's rare for most rules, but routine for SCAN ("auto") rules.
Ah, oops, I didn't realise that would be the case for "auto" rules. I'll leave this part as it is then.
Then add my:
Reviewed-by: Laurent Vivier
On Tue, May 05, 2026 at 12:15:14PM +0200, Stefano Brivio wrote:
On Tue, 5 May 2026 19:53:43 +1000 David Gibson
wrote: On Tue, May 05, 2026 at 11:08:27AM +0200, Laurent Vivier wrote:
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
We can now receive updates to the forwarding rules from the pesto client and store them in a "pending" copy of the forwarding tables. Implement switching to using the new rules.
The logic is in a new fwd_listen_switch(). For now this closes all listening sockets related to the old tables, swaps the active and pending tables, then listens based on the new tables. In future we look to improve this so that we don't temporarily stop listening on ports that both the old and new tables specify.
Signed-off-by: David Gibson
Signed-off-by: Stefano Brivio --- conf.c | 5 ++--- fwd.c | 34 ++++++++++++++++++++++++++++++++++ fwd.h | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/conf.c b/conf.c index f035fd3..75b8291 100644 --- a/conf.c +++ b/conf.c @@ -2159,15 +2159,14 @@ void conf_handler(struct ctx *c, uint32_t events) fwd_rules_dump(info, fwd->rules, fwd->count, " ", ""); } + + fwd_listen_switch(c); } if (events & EPOLLHUP) { debug("Configuration client hangup"); - goto close; } - return; - close: conf_close(c); diff --git a/fwd.c b/fwd.c index d93d2e5..35b9e2b 100644 --- a/fwd.c +++ b/fwd.c @@ -534,6 +534,40 @@ int fwd_listen_init(const struct ctx *c) return 0; } +/** + * fwd_listen_switch() - Switch from current to pending rules table + * @c: Execution context + */ +void fwd_listen_switch(struct ctx *c) +{ + struct fwd_table *tmp[PIF_NUM_TYPES]; + unsigned i; + + /* Stop listening on the old tables */ + for (i = 0; i < PIF_NUM_TYPES; i++) { + struct fwd_table *fwd = c->fwd[i]; + + if (!fwd) + continue; + + debug("Flushing %u old %s rules", fwd->count, pif_name(i)); + fwd_listen_close(fwd); + fwd->count = fwd->sock_count = 0;
Perhaps we can reset fwd->count and fwd->sock_count in fwd_listen_close() as after fwd_listen_close() these values are wrong?
No, they're not. fwd_listen_close() closes the listening sockets, but it doesn't remove the rules. fwd->sock_count isn't the number of *open* listening sockets, it's the maximum potential number of sockets for all the rules. Having some or all of the sockets close (-1 stored in the array) is an allowed state. It's rare for most rules, but routine for SCAN ("auto") rules.
Ah, oops, I didn't realise that would be the case for "auto" rules. I'll leave this part as it is then.
Even for non-auto rules, it's still conceptually correct to leave sock_count alonw in fwd_listen_close(). sock_count is really tracking the amount of space in the array allocated to store socket fds not the socket fds themselves. I'm open to different names that make that more obvious. -- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
On Tue, May 05, 2026 at 12:13:41PM +0200, Stefano Brivio wrote:
On Tue, 5 May 2026 16:22:43 +1000 David Gibson
wrote: On Tue, May 05, 2026 at 01:11:42AM +0200, Stefano Brivio wrote:
The new checks are actually sufficient but not enough for Coverity Scan. Now that fwd->sock_count and new->last are affected or supplied by clients, we need explicit (albeit redundant) checks on them.
Signed-off-by: Stefano Brivio
I'm assuming this does squash the warnings, but I think it does so in a somewhat confusing way.
You don't need to assume that, you could try yourself without this patch and you'll see exactly two warnings with a lot of details.
I'm getting better, but I'm by no means well yet. Some emails were within my capacity. Sorting out the new license key and doing a Coverity run, not so much.
--- fwd_rule.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/fwd_rule.c b/fwd_rule.c index b55e4df..03e8e80 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -271,13 +271,22 @@ int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) warn("Too many rules (maximum %d)", ARRAY_SIZE(fwd->rules)); return -ENOSPC; } + if ((fwd->sock_count + num) > ARRAY_SIZE(fwd->socks)) { warn("Rules require too many listening sockets (maximum %d)", ARRAY_SIZE(fwd->socks)); return -ENOSPC; } + /* Redundant, to make static checkers happy */ + if (fwd->sock_count > ARRAY_SIZE(fwd->socks)) + return -ENOSPC;
So there's actually two conditions that this is kind of relevant to:
1) (fwd->sock_count > ARRAY_SIZE(fwd->socks)) on entry
That means something is horribly wrong before we were even called. So, I think that would be better as an assert().
2) (fwd->sock_count + num) overflows
That's a closer-to-real concern. I'm pretty sure we can't hit it for real, because num is necessarily <= 65536, so as long as (1) is true this can't overflow. But that relies on the specific value of ARRAY_SIZE(fwd->socks), so it's kind of fragile.
I think an explicit check for this is a good idea, but it should actually check for this, not just side-effects of it, so: if (fwd->sock_count + num <= fwd->sock_count) { warn("Blah blah overflow"); return -EFAULT; /* or whatever */ }
fwd->rulesocks[fwd->count] = &fwd->socks[fwd->sock_count]; + + /* Redundant ('num' checked above), but not for static checkers */ + if (new->last > ARRAY_SIZE(fwd->socks) + new->first) + return -ENOSPC;
This way of organising the check is very confusing to me. I'm not really sure what it's trying to catch.
Same as above.
I'm not sure which "above" you mean.
We've already checked that last >= first, so using num is safer to deal with at this point than ARRAY_SIZE() + first, which could in principle overflow even if sock_count + num is perfectly ok.
Using 'num' won't work. It shouldn't overflow anyway because the addition happens in 'int'.
It shouldn't overflow, but proving that requires knowing that: a. sock_count is bounded by ARRAY_SIZE(socks) b. first, last and num are bounded by 2^16 c. ARRAY_SIZE(socks) + 2^16 won't overflow (in unsigned int) I'm not sure which part coverity is missing. (c) at least requires knowledge which is not found in immediately adjacent code. Oh... and... I just remembered that ARRAY_SIZE() is int, not unsigned (or size_t). I thought about changing that at some point, but it seemed to cause more trouble than it was worth. Does keep tripping me though, since it seems like it logically ought to be unsigned. Signed overflows are much nastier (UB) that unsigned overflows.
I'll try to change the rest if I find some time but it doesn't really look that critical to me.
for (port = new->first; port <= new->last; port++) fwd->rulesocks[fwd->count][port - new->first] = -1;
-- 2.43.0
-- Stefano
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
On Tue, May 05, 2026 at 12:04:09PM +0200, Stefano Brivio wrote:
On Tue, 5 May 2026 11:08:27 +0200 Laurent Vivier
wrote: On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
We can now receive updates to the forwarding rules from the pesto client and store them in a "pending" copy of the forwarding tables. Implement switching to using the new rules.
The logic is in a new fwd_listen_switch(). For now this closes all listening sockets related to the old tables, swaps the active and pending tables, then listens based on the new tables. In future we look to improve this so that we don't temporarily stop listening on ports that both the old and new tables specify.
Signed-off-by: David Gibson
Signed-off-by: Stefano Brivio --- conf.c | 5 ++--- fwd.c | 34 ++++++++++++++++++++++++++++++++++ fwd.h | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/conf.c b/conf.c index f035fd3..75b8291 100644 --- a/conf.c +++ b/conf.c @@ -2159,15 +2159,14 @@ void conf_handler(struct ctx *c, uint32_t events) fwd_rules_dump(info, fwd->rules, fwd->count, " ", ""); } + + fwd_listen_switch(c); }
if (events & EPOLLHUP) { debug("Configuration client hangup"); - goto close; }
- return; - close: conf_close(c);
diff --git a/fwd.c b/fwd.c index d93d2e5..35b9e2b 100644 --- a/fwd.c +++ b/fwd.c @@ -534,6 +534,40 @@ int fwd_listen_init(const struct ctx *c) return 0; }
+/** + * fwd_listen_switch() - Switch from current to pending rules table + * @c: Execution context + */ +void fwd_listen_switch(struct ctx *c) +{ + struct fwd_table *tmp[PIF_NUM_TYPES]; + unsigned i; + + /* Stop listening on the old tables */ + for (i = 0; i < PIF_NUM_TYPES; i++) { + struct fwd_table *fwd = c->fwd[i]; + + if (!fwd) + continue; + + debug("Flushing %u old %s rules", fwd->count, pif_name(i)); + fwd_listen_close(fwd); + fwd->count = fwd->sock_count = 0;
Perhaps we can reset fwd->count and fwd->sock_count in fwd_listen_close() as after fwd_listen_close() these values are wrong?
Right, while not strictly necessary it still looks like a good idea, I'll change that.
As noted elswhere this is correct as it is.
+ } + + /* Swap active and pending tables */ + static_assert(sizeof(tmp) == sizeof(c->fwd) && + sizeof(tmp) == sizeof(c->fwd_pending), + "Temporary has wrong size"); + memcpy(&tmp, (void *)c->fwd, sizeof(tmp)); + memcpy((void *)c->fwd, (void *)c->fwd_pending, sizeof(tmp)); + memcpy((void *)c->fwd_pending, &tmp, sizeof(tmp));
I know we have the static_assert(), but with memcpy() we usually use the sizeof() of the destination to avoid write overflow.
I'll change this as well.
Sounds good.
Why do we keep the old active table? Do we plan to have a "--restore" option?
It's just to add and delete rules using a temporary table so that we can abort cleanly and atomically on errors.
Are you asking why we don't wipe the old table afterwards? No particular reason for that, even though I'm not sure if it's useful.
Also looking ahead to listening socket continuity. In that case we need to keep the old table until we've stolen all the sockets we can re-use for the new table. *Then* we can fwd_listen_close() anything left over, and wipe the table.
Actually some kind of --restore option might be desirable, even though we would probably need to re-validate all the rules, or keep a "dirty" bit that's set on other types of changes and would tell us that the previous table can't be used as it is anymore.
-- Stefano
+ + /* Start listening on the new tables */ + if (fwd_listen_init(c) < 0) + err("Error switching to new forwarding rules"); +} + /* See enum in kernel's include/net/tcp_states.h */ #define UDP_LISTEN 0x07 #define TCP_LISTEN 0x0a diff --git a/fwd.h b/fwd.h index ac24782..b60697d 100644 --- a/fwd.h +++ b/fwd.h @@ -61,6 +61,7 @@ int fwd_listen_sync(const struct ctx *c, uint8_t pif, const struct fwd_scan *tcp, const struct fwd_scan *udp); void fwd_listen_close(const struct fwd_table *fwd); int fwd_listen_init(const struct ctx *c); +void fwd_listen_switch(struct ctx *c);
bool nat_inbound(const struct ctx *c, const union inany_addr *addr, union inany_addr *translated);
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
On Tue, 5 May 2026 09:31:27 +0200
Laurent Vivier
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
This adds parsing of options using fwd_rule_parse(), validates them and adds them to the existing rules. It doesn't yet send those rules back to passt or pasta.
Message-ID: <20260322141843.4095972-3-sbrivio@redhat.com> [dwg: Based on an early draft by Stefano] Signed-off-by: David Gibson
[sbrivio: Recycled usage messages for -T and -U from conf.c as suggested by Laurent, dropped unrelated whitespace change] [sbrivio: Add description of -t, -u, -T, -U to pesto.1] -T, -U, -s are still missing in pesto.1
Oops, thanks, I (mostly) copied them from passt.1 in v8. -- Stefano
On Tue, 5 May 2026 11:08:27 +0200
Laurent Vivier
On 5/5/26 01:11, Stefano Brivio wrote:
From: David Gibson
We can now receive updates to the forwarding rules from the pesto client and store them in a "pending" copy of the forwarding tables. Implement switching to using the new rules.
The logic is in a new fwd_listen_switch(). For now this closes all listening sockets related to the old tables, swaps the active and pending tables, then listens based on the new tables. In future we look to improve this so that we don't temporarily stop listening on ports that both the old and new tables specify.
Signed-off-by: David Gibson
Signed-off-by: Stefano Brivio --- conf.c | 5 ++--- fwd.c | 34 ++++++++++++++++++++++++++++++++++ fwd.h | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/conf.c b/conf.c index f035fd3..75b8291 100644 --- a/conf.c +++ b/conf.c @@ -2159,15 +2159,14 @@ void conf_handler(struct ctx *c, uint32_t events) fwd_rules_dump(info, fwd->rules, fwd->count, " ", ""); } + + fwd_listen_switch(c); }
if (events & EPOLLHUP) { debug("Configuration client hangup"); - goto close; }
- return; - close: conf_close(c);
diff --git a/fwd.c b/fwd.c index d93d2e5..35b9e2b 100644 --- a/fwd.c +++ b/fwd.c @@ -534,6 +534,40 @@ int fwd_listen_init(const struct ctx *c) return 0; }
+/** + * fwd_listen_switch() - Switch from current to pending rules table + * @c: Execution context + */ +void fwd_listen_switch(struct ctx *c) +{ + struct fwd_table *tmp[PIF_NUM_TYPES]; + unsigned i; + + /* Stop listening on the old tables */ + for (i = 0; i < PIF_NUM_TYPES; i++) { + struct fwd_table *fwd = c->fwd[i]; + + if (!fwd) + continue; + + debug("Flushing %u old %s rules", fwd->count, pif_name(i)); + fwd_listen_close(fwd); + fwd->count = fwd->sock_count = 0;
Perhaps we can reset fwd->count and fwd->sock_count in fwd_listen_close() as after fwd_listen_close() these values are wrong?
+ } + + /* Swap active and pending tables */ + static_assert(sizeof(tmp) == sizeof(c->fwd) && + sizeof(tmp) == sizeof(c->fwd_pending), + "Temporary has wrong size"); + memcpy(&tmp, (void *)c->fwd, sizeof(tmp)); + memcpy((void *)c->fwd, (void *)c->fwd_pending, sizeof(tmp)); + memcpy((void *)c->fwd_pending, &tmp, sizeof(tmp));
I know we have the static_assert(), but with memcpy() we usually use the sizeof() of the destination to avoid write overflow.
Changed in v8. -- Stefano
On Wed, 6 May 2026 00:41:15 +1000
David Gibson
On Tue, May 05, 2026 at 12:13:41PM +0200, Stefano Brivio wrote:
On Tue, 5 May 2026 16:22:43 +1000 David Gibson
wrote: On Tue, May 05, 2026 at 01:11:42AM +0200, Stefano Brivio wrote:
The new checks are actually sufficient but not enough for Coverity Scan. Now that fwd->sock_count and new->last are affected or supplied by clients, we need explicit (albeit redundant) checks on them.
Signed-off-by: Stefano Brivio
I'm assuming this does squash the warnings, but I think it does so in a somewhat confusing way.
You don't need to assume that, you could try yourself without this patch and you'll see exactly two warnings with a lot of details.
I'm getting better, but I'm by no means well yet. Some emails were within my capacity. Sorting out the new license key and doing a Coverity run, not so much.
--- fwd_rule.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/fwd_rule.c b/fwd_rule.c index b55e4df..03e8e80 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -271,13 +271,22 @@ int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) warn("Too many rules (maximum %d)", ARRAY_SIZE(fwd->rules)); return -ENOSPC; } + if ((fwd->sock_count + num) > ARRAY_SIZE(fwd->socks)) { warn("Rules require too many listening sockets (maximum %d)", ARRAY_SIZE(fwd->socks)); return -ENOSPC; } + /* Redundant, to make static checkers happy */ + if (fwd->sock_count > ARRAY_SIZE(fwd->socks)) + return -ENOSPC;
So there's actually two conditions that this is kind of relevant to:
1) (fwd->sock_count > ARRAY_SIZE(fwd->socks)) on entry
That means something is horribly wrong before we were even called. So, I think that would be better as an assert().
But the (good) reason why Coverity Scan is complaining about a missing check here is that the client is able to manipulate sock_count (via num) in a previous call to this function, so making us crash is exactly what we want to avoid: /home/sbrivio/passt/conf.c:2024:4: Type: Untrusted value as argument (TAINTED_SCALAR) /home/sbrivio/passt/conf.c:1992:3: Tainted data flows to a taint sink 1. path: Condition "read_u8(fd, &pif)", taking false branch. /home/sbrivio/passt/conf.c:1995:3: 2. path: Condition "pif == 0", taking false branch. /home/sbrivio/passt/conf.c:1998:3: 3. path: Condition "pif >= 4 /* (int)(sizeof (c->fwd_pending) / sizeof (c->fwd_pending[0])) */", taking false branch. /home/sbrivio/passt/conf.c:1998:3: 4. path: Condition "!(fwd = c->fwd_pending[pif])", taking false branch. /home/sbrivio/passt/conf.c:2004:3: 5. path: Condition "read_u32(fd, &count)", taking false branch. /home/sbrivio/passt/conf.c:2007:3: 6. path: Condition "count > 255U /* (1U << 8) - 1 */", taking false branch. /home/sbrivio/passt/conf.c:2013:3: 7. path: Condition "i < count", taking true branch. /home/sbrivio/passt/conf.c:2014:4: 8. tainted_argument: Calling function "fwd_rule_read" taints argument "r". /home/sbrivio/passt/fwd_rule.c:659:2: Tainted data flows to a taint sink 8.1. tainted_data_argument: Calling function "read_all_buf" taints parameter "*rule". /home/sbrivio/passt/serialise.c:39:2: Tainted data flows to a taint sink 8.1.1. var_assign_parm: Assigning: "p" = "buf". /home/sbrivio/passt/serialise.c:41:2: 8.1.2. path: Condition "left", taking true branch. /home/sbrivio/passt/serialise.c:44:3: 8.1.3. path: Condition "left <= len", taking true branch. /home/sbrivio/passt/serialise.c:47:4: 8.1.4. tainted_data_argument: Calling function "read" taints parameter "*p". [Note: The source code implementation of the function has been overridden by a builtin model.] /home/sbrivio/passt/serialise.c:48:37: 8.1.5. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:50:3: 8.1.6. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:53:3: 8.1.7. path: Condition "rc == 0", taking false branch. /home/sbrivio/passt/serialise.c:60:2: 8.1.8. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/serialise.c:41:2: 8.1.9. path: Condition "left", taking true branch. /home/sbrivio/passt/serialise.c:44:3: 8.1.10. path: Condition "left <= len", taking true branch. /home/sbrivio/passt/serialise.c:48:37: 8.1.11. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:50:3: 8.1.12. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:53:3: 8.1.13. path: Condition "rc == 0", taking false branch. /home/sbrivio/passt/serialise.c:60:2: 8.1.14. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/serialise.c:41:2: 8.1.15. path: Condition "left", taking false branch. /home/sbrivio/passt/fwd_rule.c:659:2: 8.2. path: Condition "read_all_buf(fd, rule, 40UL /* sizeof (*rule) */)", taking false branch. /home/sbrivio/passt/conf.c:2014:4: 9. path: Condition "fwd_rule_read(fd, &r)", taking false branch. /home/sbrivio/passt/conf.c:2017:4: 10. path: Condition "r.ifname[15UL /* sizeof (r.ifname) - 1 */]", taking false branch. /home/sbrivio/passt/conf.c:2024:4: 11. tainted_data_transitive: Call to function "fwd_rule_add" with tainted argument "r.last" transitively taints "fwd->sock_count". /home/sbrivio/passt/fwd_rule.c:197:2: Tainted data flows to a taint sink 11.1. path: Condition "new->first > new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:202:2: 11.2. path: Condition "!new->first", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 11.3. path: Condition "!new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 11.4. path: Condition "(in_port_t)(new->to + new->last - new->first) < new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:211:2: 11.5. path: Condition "new->flags & -8 /* ~allowed_flags */", taking false branch. /home/sbrivio/passt/fwd_rule.c:216:2: 11.6. path: Condition "new->flags & (1UL /* 1UL << 0 */)", taking true branch. /home/sbrivio/passt/fwd_rule.c:217:3: 11.7. path: Condition "!inany_equals(&new->addr, (union inany_addr const *)&in6addr_any)", taking false branch. /home/sbrivio/passt/fwd_rule.c:224:3: 11.8. path: Condition "!(fwd->caps & (1UL /* 1UL << 0 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:228:3: 11.9. path: Condition "!(fwd->caps & (2UL /* 1UL << 1 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:232:2: 11.10. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:242:2: 11.11. path: Condition "new->proto == IPPROTO_TCP", taking true branch. /home/sbrivio/passt/fwd_rule.c:243:3: 11.12. path: Condition "!(fwd->caps & (4UL /* 1UL << 2 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:247:2: 11.13. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:258:2: 11.14. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 11.15. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 11.16. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 11.17. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 11.18. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 11.19. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 11.20. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 11.21. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 11.22. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 11.23. path: Condition "i < fwd->count", taking false branch. /home/sbrivio/passt/fwd_rule.c:270:2: 11.24. path: Condition "fwd->count >= 255U /* (int)(sizeof (fwd->rules) / sizeof (fwd->rules[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:274:2: 11.25. path: Condition "fwd->sock_count + num > 327680U /* (int)(sizeof (fwd->socks) / sizeof (fwd->socks[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:281:2: 11.26. path: Condition "port <= new->last", taking true branch. /home/sbrivio/passt/fwd_rule.c:282:53: 11.27. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/fwd_rule.c:281:2: 11.28. path: Condition "port <= new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:285:2: 11.29. parm_assign: Assigning: "fwd->sock_count" += "num", which taints "fwd->sock_count". /home/sbrivio/passt/conf.c:2024:4: 12. path: Condition "fwd_rule_add(fwd, &r) < 0", taking false branch. /home/sbrivio/passt/conf.c:2026:3: 13. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/conf.c:2013:3: 14. path: Condition "i < count", taking true branch. /home/sbrivio/passt/conf.c:2014:4: 15. path: Condition "fwd_rule_read(fd, &r)", taking false branch. /home/sbrivio/passt/conf.c:2017:4: 16. path: Condition "r.ifname[15UL /* sizeof (r.ifname) - 1 */]", taking false branch. /home/sbrivio/passt/conf.c:2024:4: 17. tainted_data: Passing tainted expression "fwd->sock_count" to "fwd_rule_add", which uses it as an offset. /home/sbrivio/passt/fwd_rule.c:197:2: Tainted data flows to a taint sink 17.1. path: Condition "new->first > new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:202:2: 17.2. path: Condition "!new->first", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 17.3. path: Condition "!new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 17.4. path: Condition "(in_port_t)(new->to + new->last - new->first) < new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:211:2: 17.5. path: Condition "new->flags & -8 /* ~allowed_flags */", taking false branch. /home/sbrivio/passt/fwd_rule.c:216:2: 17.6. path: Condition "new->flags & (1UL /* 1UL << 0 */)", taking true branch. /home/sbrivio/passt/fwd_rule.c:217:3: 17.7. path: Condition "!inany_equals(&new->addr, (union inany_addr const *)&in6addr_any)", taking false branch. /home/sbrivio/passt/fwd_rule.c:224:3: 17.8. path: Condition "!(fwd->caps & (1UL /* 1UL << 0 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:228:3: 17.9. path: Condition "!(fwd->caps & (2UL /* 1UL << 1 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:232:2: 17.10. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:242:2: 17.11. path: Condition "new->proto == IPPROTO_TCP", taking true branch. /home/sbrivio/passt/fwd_rule.c:243:3: 17.12. path: Condition "!(fwd->caps & (4UL /* 1UL << 2 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:247:2: 17.13. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:258:2: 17.14. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 17.15. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 17.16. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 17.17. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 17.18. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 17.19. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 17.20. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 17.21. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 17.22. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 17.23. path: Condition "i < fwd->count", taking false branch. /home/sbrivio/passt/fwd_rule.c:270:2: 17.24. path: Condition "fwd->count >= 255U /* (int)(sizeof (fwd->rules) / sizeof (fwd->rules[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:274:2: 17.25. path: Condition "fwd->sock_count + num > 327680U /* (int)(sizeof (fwd->socks) / sizeof (fwd->socks[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:280:2: 17.26. data_index: Using tainted expression "fwd->sock_count" as an index to array "fwd->socks". /home/sbrivio/passt/conf.c:2024:4: 18. remediation: Ensure that tainted values are properly sanitized, by checking that their values are within a permissible range. At the same time, Coverity Scan doesn't realise that 'num' is positive, but we know that, so this will never trigger. As I wrote in the comment, this check is redundant and it won't trigger. It's just good to have to avoid noise from false positives.
2) (fwd->sock_count + num) overflows
That's a closer-to-real concern. I'm pretty sure we can't hit it for real, because num is necessarily <= 65536, so as long as (1) is true this can't overflow. But that relies on the specific value of ARRAY_SIZE(fwd->socks), so it's kind of fragile.
I think an explicit check for this is a good idea, but it should actually check for this, not just side-effects of it, so: if (fwd->sock_count + num <= fwd->sock_count) { warn("Blah blah overflow"); return -EFAULT; /* or whatever */ }
fwd->rulesocks[fwd->count] = &fwd->socks[fwd->sock_count]; + + /* Redundant ('num' checked above), but not for static checkers */ + if (new->last > ARRAY_SIZE(fwd->socks) + new->first) + return -ENOSPC;
This way of organising the check is very confusing to me. I'm not really sure what it's trying to catch.
Same as above.
I'm not sure which "above" you mean.
Same here: the warning is pretty clear: /home/sbrivio/passt/conf.c:2024:4: Type: Untrusted loop bound (TAINTED_SCALAR) /home/sbrivio/passt/conf.c:1992:3: Tainted data flows to a taint sink 1. path: Condition "read_u8(fd, &pif)", taking false branch. /home/sbrivio/passt/conf.c:1995:3: 2. path: Condition "pif == 0", taking false branch. /home/sbrivio/passt/conf.c:1998:3: 3. path: Condition "pif >= 4 /* (int)(sizeof (c->fwd_pending) / sizeof (c->fwd_pending[0])) */", taking false branch. /home/sbrivio/passt/conf.c:1998:3: 4. path: Condition "!(fwd = c->fwd_pending[pif])", taking false branch. /home/sbrivio/passt/conf.c:2004:3: 5. path: Condition "read_u32(fd, &count)", taking false branch. /home/sbrivio/passt/conf.c:2007:3: 6. path: Condition "count > 255U /* (1U << 8) - 1 */", taking false branch. /home/sbrivio/passt/conf.c:2013:3: 7. path: Condition "i < count", taking true branch. /home/sbrivio/passt/conf.c:2014:4: 8. path: Condition "fwd_rule_read(fd, &r)", taking false branch. /home/sbrivio/passt/conf.c:2017:4: 9. path: Condition "r.ifname[15UL /* sizeof (r.ifname) - 1 */]", taking false branch. /home/sbrivio/passt/conf.c:2024:4: 10. path: Condition "fwd_rule_add(fwd, &r) < 0", taking false branch. /home/sbrivio/passt/conf.c:2026:3: 11. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/conf.c:2013:3: 12. path: Condition "i < count", taking true branch. /home/sbrivio/passt/conf.c:2014:4: 13. tainted_argument: Calling function "fwd_rule_read" taints argument "r". /home/sbrivio/passt/fwd_rule.c:659:2: Tainted data flows to a taint sink 13.1. tainted_data_argument: Calling function "read_all_buf" taints parameter "*rule". /home/sbrivio/passt/serialise.c:39:2: Tainted data flows to a taint sink 13.1.1. var_assign_parm: Assigning: "p" = "buf". /home/sbrivio/passt/serialise.c:41:2: 13.1.2. path: Condition "left", taking true branch. /home/sbrivio/passt/serialise.c:44:3: 13.1.3. path: Condition "left <= len", taking true branch. /home/sbrivio/passt/serialise.c:47:4: 13.1.4. tainted_data_argument: Calling function "read" taints parameter "*p". [Note: The source code implementation of the function has been overridden by a builtin model.] /home/sbrivio/passt/serialise.c:48:37: 13.1.5. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:50:3: 13.1.6. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:53:3: 13.1.7. path: Condition "rc == 0", taking false branch. /home/sbrivio/passt/serialise.c:60:2: 13.1.8. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/serialise.c:41:2: 13.1.9. path: Condition "left", taking true branch. /home/sbrivio/passt/serialise.c:44:3: 13.1.10. path: Condition "left <= len", taking true branch. /home/sbrivio/passt/serialise.c:48:37: 13.1.11. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:50:3: 13.1.12. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:53:3: 13.1.13. path: Condition "rc == 0", taking false branch. /home/sbrivio/passt/serialise.c:60:2: 13.1.14. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/serialise.c:41:2: 13.1.15. path: Condition "left", taking false branch. /home/sbrivio/passt/fwd_rule.c:659:2: 13.2. path: Condition "read_all_buf(fd, rule, 40UL /* sizeof (*rule) */)", taking false branch. /home/sbrivio/passt/conf.c:2014:4: 14. path: Condition "fwd_rule_read(fd, &r)", taking false branch. /home/sbrivio/passt/conf.c:2017:4: 15. path: Condition "r.ifname[15UL /* sizeof (r.ifname) - 1 */]", taking false branch. /home/sbrivio/passt/conf.c:2024:4: 16. tainted_data: Passing tainted expression "r.last" to "fwd_rule_add", which uses it as a loop boundary. /home/sbrivio/passt/fwd_rule.c:197:2: Tainted data flows to a taint sink 16.1. lower_bounds: Casting narrower unsigned "new->last" to wider signed type "int" effectively tests its lower bound. /home/sbrivio/passt/fwd_rule.c:197:2: 16.2. path: Condition "new->first > new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:197:2: 16.3. lower_bounds: Checking lower bounds of unsigned scalar "new->last" by taking the false branch of "new->first > new->last". /home/sbrivio/passt/fwd_rule.c:202:2: 16.4. path: Condition "!new->first", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 16.5. path: Condition "!new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 16.6. lower_bounds: Casting narrower unsigned "new->last" to wider signed type "int" effectively tests its lower bound. /home/sbrivio/passt/fwd_rule.c:206:2: 16.7. path: Condition "(in_port_t)(new->to + new->last - new->first) < new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:211:2: 16.8. path: Condition "new->flags & -8 /* ~allowed_flags */", taking false branch. /home/sbrivio/passt/fwd_rule.c:216:2: 16.9. path: Condition "new->flags & (1UL /* 1UL << 0 */)", taking true branch. /home/sbrivio/passt/fwd_rule.c:217:3: 16.10. path: Condition "!inany_equals(&new->addr, (union inany_addr const *)&in6addr_any)", taking false branch. /home/sbrivio/passt/fwd_rule.c:224:3: 16.11. path: Condition "!(fwd->caps & (1UL /* 1UL << 0 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:228:3: 16.12. path: Condition "!(fwd->caps & (2UL /* 1UL << 1 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:232:2: 16.13. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:242:2: 16.14. path: Condition "new->proto == IPPROTO_TCP", taking true branch. /home/sbrivio/passt/fwd_rule.c:243:3: 16.15. path: Condition "!(fwd->caps & (4UL /* 1UL << 2 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:247:2: 16.16. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:258:2: 16.17. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 16.18. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 16.19. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 16.20. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 16.21. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 16.22. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 16.23. path: Condition "i < fwd->count", taking false branch. /home/sbrivio/passt/fwd_rule.c:270:2: 16.24. path: Condition "fwd->count >= 255U /* (int)(sizeof (fwd->rules) / sizeof (fwd->rules[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:274:2: 16.25. path: Condition "fwd->sock_count + num > 327680U /* (int)(sizeof (fwd->socks) / sizeof (fwd->socks[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:281:2: 16.26. loop_bound_upper: Using tainted expression "new->last" as a loop boundary. /home/sbrivio/passt/conf.c:2024:4: 17. remediation: Ensure that tainted values are properly sanitized, by checking that their values are within a permissible range. I'll expand the comment and remove the newline between this and the for loop.
We've already checked that last >= first, so using num is safer to deal with at this point than ARRAY_SIZE() + first, which could in principle overflow even if sock_count + num is perfectly ok.
Using 'num' won't work. It shouldn't overflow anyway because the addition happens in 'int'.
It shouldn't overflow, but proving that requires knowing that: a. sock_count is bounded by ARRAY_SIZE(socks) b. first, last and num are bounded by 2^16 c. ARRAY_SIZE(socks) + 2^16 won't overflow (in unsigned int)
I'm not sure which part coverity is missing. (c) at least requires knowledge which is not found in immediately adjacent code.
It's missing the fact that 'num' is already checked above (that is, a.), and due to that we know we're not overflowing fwd->rulesocks[x].
Oh... and... I just remembered that ARRAY_SIZE() is int, not unsigned (or size_t). I thought about changing that at some point, but it seemed to cause more trouble than it was worth. Does keep tripping me though, since it seems like it logically ought to be unsigned. Signed overflows are much nastier (UB) that unsigned overflows.
I'll try to change the rest if I find some time but it doesn't really look that critical to me.
for (port = new->first; port <= new->last; port++) fwd->rulesocks[fwd->count][port - new->first] = -1;
-- 2.43.0
-- Stefano
-- Stefano
On Wed, May 06, 2026 at 09:46:44AM +0200, Stefano Brivio wrote:
On Wed, 6 May 2026 00:41:15 +1000 David Gibson
wrote: On Tue, May 05, 2026 at 12:13:41PM +0200, Stefano Brivio wrote:
On Tue, 5 May 2026 16:22:43 +1000 David Gibson
wrote: On Tue, May 05, 2026 at 01:11:42AM +0200, Stefano Brivio wrote:
The new checks are actually sufficient but not enough for Coverity Scan. Now that fwd->sock_count and new->last are affected or supplied by clients, we need explicit (albeit redundant) checks on them.
Signed-off-by: Stefano Brivio
I'm assuming this does squash the warnings, but I think it does so in a somewhat confusing way.
You don't need to assume that, you could try yourself without this patch and you'll see exactly two warnings with a lot of details.
I'm getting better, but I'm by no means well yet. Some emails were within my capacity. Sorting out the new license key and doing a Coverity run, not so much.
--- fwd_rule.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/fwd_rule.c b/fwd_rule.c index b55e4df..03e8e80 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -271,13 +271,22 @@ int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) warn("Too many rules (maximum %d)", ARRAY_SIZE(fwd->rules)); return -ENOSPC; } + if ((fwd->sock_count + num) > ARRAY_SIZE(fwd->socks)) { warn("Rules require too many listening sockets (maximum %d)", ARRAY_SIZE(fwd->socks)); return -ENOSPC; } + /* Redundant, to make static checkers happy */ + if (fwd->sock_count > ARRAY_SIZE(fwd->socks)) + return -ENOSPC;
So there's actually two conditions that this is kind of relevant to:
1) (fwd->sock_count > ARRAY_SIZE(fwd->socks)) on entry
That means something is horribly wrong before we were even called. So, I think that would be better as an assert().
But the (good) reason why Coverity Scan is complaining about a missing check here is that the client is able to manipulate sock_count (via num) in a previous call to this function,
Uh... they should never be able to make sock_count outside of [0, ARRAY_SIZE(socks)). If they can, that's a bug elsewhere.
so making us crash is exactly what we want to avoid:
/home/sbrivio/passt/conf.c:2024:4: Type: Untrusted value as argument (TAINTED_SCALAR)
/home/sbrivio/passt/conf.c:1992:3: Tainted data flows to a taint sink 1. path: Condition "read_u8(fd, &pif)", taking false branch. /home/sbrivio/passt/conf.c:1995:3: 2. path: Condition "pif == 0", taking false branch. /home/sbrivio/passt/conf.c:1998:3: 3. path: Condition "pif >= 4 /* (int)(sizeof (c->fwd_pending) / sizeof (c->fwd_pending[0])) */", taking false branch. /home/sbrivio/passt/conf.c:1998:3: 4. path: Condition "!(fwd = c->fwd_pending[pif])", taking false branch. /home/sbrivio/passt/conf.c:2004:3: 5. path: Condition "read_u32(fd, &count)", taking false branch. /home/sbrivio/passt/conf.c:2007:3: 6. path: Condition "count > 255U /* (1U << 8) - 1 */", taking false branch. /home/sbrivio/passt/conf.c:2013:3: 7. path: Condition "i < count", taking true branch. /home/sbrivio/passt/conf.c:2014:4: 8. tainted_argument: Calling function "fwd_rule_read" taints argument "r". /home/sbrivio/passt/fwd_rule.c:659:2: Tainted data flows to a taint sink 8.1. tainted_data_argument: Calling function "read_all_buf" taints parameter "*rule". /home/sbrivio/passt/serialise.c:39:2: Tainted data flows to a taint sink 8.1.1. var_assign_parm: Assigning: "p" = "buf". /home/sbrivio/passt/serialise.c:41:2: 8.1.2. path: Condition "left", taking true branch. /home/sbrivio/passt/serialise.c:44:3: 8.1.3. path: Condition "left <= len", taking true branch. /home/sbrivio/passt/serialise.c:47:4: 8.1.4. tainted_data_argument: Calling function "read" taints parameter "*p". [Note: The source code implementation of the function has been overridden by a builtin model.] /home/sbrivio/passt/serialise.c:48:37: 8.1.5. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:50:3: 8.1.6. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:53:3: 8.1.7. path: Condition "rc == 0", taking false branch. /home/sbrivio/passt/serialise.c:60:2: 8.1.8. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/serialise.c:41:2: 8.1.9. path: Condition "left", taking true branch. /home/sbrivio/passt/serialise.c:44:3: 8.1.10. path: Condition "left <= len", taking true branch. /home/sbrivio/passt/serialise.c:48:37: 8.1.11. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:50:3: 8.1.12. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:53:3: 8.1.13. path: Condition "rc == 0", taking false branch. /home/sbrivio/passt/serialise.c:60:2: 8.1.14. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/serialise.c:41:2: 8.1.15. path: Condition "left", taking false branch. /home/sbrivio/passt/fwd_rule.c:659:2: 8.2. path: Condition "read_all_buf(fd, rule, 40UL /* sizeof (*rule) */)", taking false branch. /home/sbrivio/passt/conf.c:2014:4: 9. path: Condition "fwd_rule_read(fd, &r)", taking false branch. /home/sbrivio/passt/conf.c:2017:4: 10. path: Condition "r.ifname[15UL /* sizeof (r.ifname) - 1 */]", taking false branch. /home/sbrivio/passt/conf.c:2024:4: 11. tainted_data_transitive: Call to function "fwd_rule_add" with tainted argument "r.last" transitively taints "fwd->sock_count". /home/sbrivio/passt/fwd_rule.c:197:2: Tainted data flows to a taint sink 11.1. path: Condition "new->first > new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:202:2: 11.2. path: Condition "!new->first", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 11.3. path: Condition "!new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 11.4. path: Condition "(in_port_t)(new->to + new->last - new->first) < new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:211:2: 11.5. path: Condition "new->flags & -8 /* ~allowed_flags */", taking false branch. /home/sbrivio/passt/fwd_rule.c:216:2: 11.6. path: Condition "new->flags & (1UL /* 1UL << 0 */)", taking true branch. /home/sbrivio/passt/fwd_rule.c:217:3: 11.7. path: Condition "!inany_equals(&new->addr, (union inany_addr const *)&in6addr_any)", taking false branch. /home/sbrivio/passt/fwd_rule.c:224:3: 11.8. path: Condition "!(fwd->caps & (1UL /* 1UL << 0 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:228:3: 11.9. path: Condition "!(fwd->caps & (2UL /* 1UL << 1 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:232:2: 11.10. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:242:2: 11.11. path: Condition "new->proto == IPPROTO_TCP", taking true branch. /home/sbrivio/passt/fwd_rule.c:243:3: 11.12. path: Condition "!(fwd->caps & (4UL /* 1UL << 2 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:247:2: 11.13. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:258:2: 11.14. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 11.15. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 11.16. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 11.17. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 11.18. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 11.19. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 11.20. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 11.21. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 11.22. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 11.23. path: Condition "i < fwd->count", taking false branch. /home/sbrivio/passt/fwd_rule.c:270:2: 11.24. path: Condition "fwd->count >= 255U /* (int)(sizeof (fwd->rules) / sizeof (fwd->rules[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:274:2: 11.25. path: Condition "fwd->sock_count + num > 327680U /* (int)(sizeof (fwd->socks) / sizeof (fwd->socks[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:281:2: 11.26. path: Condition "port <= new->last", taking true branch. /home/sbrivio/passt/fwd_rule.c:282:53: 11.27. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/fwd_rule.c:281:2: 11.28. path: Condition "port <= new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:285:2: 11.29. parm_assign: Assigning: "fwd->sock_count" += "num", which taints "fwd->sock_count". /home/sbrivio/passt/conf.c:2024:4: 12. path: Condition "fwd_rule_add(fwd, &r) < 0", taking false branch. /home/sbrivio/passt/conf.c:2026:3: 13. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/conf.c:2013:3: 14. path: Condition "i < count", taking true branch. /home/sbrivio/passt/conf.c:2014:4: 15. path: Condition "fwd_rule_read(fd, &r)", taking false branch. /home/sbrivio/passt/conf.c:2017:4: 16. path: Condition "r.ifname[15UL /* sizeof (r.ifname) - 1 */]", taking false branch. /home/sbrivio/passt/conf.c:2024:4: 17. tainted_data: Passing tainted expression "fwd->sock_count" to "fwd_rule_add", which uses it as an offset. /home/sbrivio/passt/fwd_rule.c:197:2: Tainted data flows to a taint sink 17.1. path: Condition "new->first > new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:202:2: 17.2. path: Condition "!new->first", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 17.3. path: Condition "!new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 17.4. path: Condition "(in_port_t)(new->to + new->last - new->first) < new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:211:2: 17.5. path: Condition "new->flags & -8 /* ~allowed_flags */", taking false branch. /home/sbrivio/passt/fwd_rule.c:216:2: 17.6. path: Condition "new->flags & (1UL /* 1UL << 0 */)", taking true branch. /home/sbrivio/passt/fwd_rule.c:217:3: 17.7. path: Condition "!inany_equals(&new->addr, (union inany_addr const *)&in6addr_any)", taking false branch. /home/sbrivio/passt/fwd_rule.c:224:3: 17.8. path: Condition "!(fwd->caps & (1UL /* 1UL << 0 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:228:3: 17.9. path: Condition "!(fwd->caps & (2UL /* 1UL << 1 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:232:2: 17.10. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:242:2: 17.11. path: Condition "new->proto == IPPROTO_TCP", taking true branch. /home/sbrivio/passt/fwd_rule.c:243:3: 17.12. path: Condition "!(fwd->caps & (4UL /* 1UL << 2 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:247:2: 17.13. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:258:2: 17.14. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 17.15. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 17.16. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 17.17. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 17.18. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 17.19. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 17.20. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 17.21. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 17.22. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 17.23. path: Condition "i < fwd->count", taking false branch. /home/sbrivio/passt/fwd_rule.c:270:2: 17.24. path: Condition "fwd->count >= 255U /* (int)(sizeof (fwd->rules) / sizeof (fwd->rules[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:274:2: 17.25. path: Condition "fwd->sock_count + num > 327680U /* (int)(sizeof (fwd->socks) / sizeof (fwd->socks[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:280:2: 17.26. data_index: Using tainted expression "fwd->sock_count" as an index to array "fwd->socks". /home/sbrivio/passt/conf.c:2024:4: 18. remediation: Ensure that tainted values are properly sanitized, by checking that their values are within a permissible range.
At the same time, Coverity Scan doesn't realise that 'num' is positive,
Uh.. what? num is unsigned, so it must know it's positive.
but we know that, so this will never trigger. As I wrote in the comment, this check is redundant and it won't trigger. It's just good to have to avoid noise from false positives.
Agreed, but sock_count < ARRAY_SIZE() is a fundamental invariant of the data structure, so that makes more sense to document/enforce with a check.
2) (fwd->sock_count + num) overflows
That's a closer-to-real concern. I'm pretty sure we can't hit it for real, because num is necessarily <= 65536, so as long as (1) is true this can't overflow. But that relies on the specific value of ARRAY_SIZE(fwd->socks), so it's kind of fragile.
I think an explicit check for this is a good idea, but it should actually check for this, not just side-effects of it, so: if (fwd->sock_count + num <= fwd->sock_count) { warn("Blah blah overflow"); return -EFAULT; /* or whatever */ }
fwd->rulesocks[fwd->count] = &fwd->socks[fwd->sock_count]; + + /* Redundant ('num' checked above), but not for static checkers */ + if (new->last > ARRAY_SIZE(fwd->socks) + new->first) + return -ENOSPC;
This way of organising the check is very confusing to me. I'm not really sure what it's trying to catch.
Same as above.
I'm not sure which "above" you mean.
Same here: the warning is pretty clear:
/home/sbrivio/passt/conf.c:2024:4: Type: Untrusted loop bound (TAINTED_SCALAR)
/home/sbrivio/passt/conf.c:1992:3: Tainted data flows to a taint sink 1. path: Condition "read_u8(fd, &pif)", taking false branch. /home/sbrivio/passt/conf.c:1995:3: 2. path: Condition "pif == 0", taking false branch. /home/sbrivio/passt/conf.c:1998:3: 3. path: Condition "pif >= 4 /* (int)(sizeof (c->fwd_pending) / sizeof (c->fwd_pending[0])) */", taking false branch. /home/sbrivio/passt/conf.c:1998:3: 4. path: Condition "!(fwd = c->fwd_pending[pif])", taking false branch. /home/sbrivio/passt/conf.c:2004:3: 5. path: Condition "read_u32(fd, &count)", taking false branch. /home/sbrivio/passt/conf.c:2007:3: 6. path: Condition "count > 255U /* (1U << 8) - 1 */", taking false branch. /home/sbrivio/passt/conf.c:2013:3: 7. path: Condition "i < count", taking true branch. /home/sbrivio/passt/conf.c:2014:4: 8. path: Condition "fwd_rule_read(fd, &r)", taking false branch. /home/sbrivio/passt/conf.c:2017:4: 9. path: Condition "r.ifname[15UL /* sizeof (r.ifname) - 1 */]", taking false branch. /home/sbrivio/passt/conf.c:2024:4: 10. path: Condition "fwd_rule_add(fwd, &r) < 0", taking false branch. /home/sbrivio/passt/conf.c:2026:3: 11. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/conf.c:2013:3: 12. path: Condition "i < count", taking true branch. /home/sbrivio/passt/conf.c:2014:4: 13. tainted_argument: Calling function "fwd_rule_read" taints argument "r". /home/sbrivio/passt/fwd_rule.c:659:2: Tainted data flows to a taint sink 13.1. tainted_data_argument: Calling function "read_all_buf" taints parameter "*rule". /home/sbrivio/passt/serialise.c:39:2: Tainted data flows to a taint sink 13.1.1. var_assign_parm: Assigning: "p" = "buf". /home/sbrivio/passt/serialise.c:41:2: 13.1.2. path: Condition "left", taking true branch. /home/sbrivio/passt/serialise.c:44:3: 13.1.3. path: Condition "left <= len", taking true branch. /home/sbrivio/passt/serialise.c:47:4: 13.1.4. tainted_data_argument: Calling function "read" taints parameter "*p". [Note: The source code implementation of the function has been overridden by a builtin model.] /home/sbrivio/passt/serialise.c:48:37: 13.1.5. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:50:3: 13.1.6. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:53:3: 13.1.7. path: Condition "rc == 0", taking false branch. /home/sbrivio/passt/serialise.c:60:2: 13.1.8. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/serialise.c:41:2: 13.1.9. path: Condition "left", taking true branch. /home/sbrivio/passt/serialise.c:44:3: 13.1.10. path: Condition "left <= len", taking true branch. /home/sbrivio/passt/serialise.c:48:37: 13.1.11. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:50:3: 13.1.12. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:53:3: 13.1.13. path: Condition "rc == 0", taking false branch. /home/sbrivio/passt/serialise.c:60:2: 13.1.14. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/serialise.c:41:2: 13.1.15. path: Condition "left", taking false branch. /home/sbrivio/passt/fwd_rule.c:659:2: 13.2. path: Condition "read_all_buf(fd, rule, 40UL /* sizeof (*rule) */)", taking false branch. /home/sbrivio/passt/conf.c:2014:4: 14. path: Condition "fwd_rule_read(fd, &r)", taking false branch. /home/sbrivio/passt/conf.c:2017:4: 15. path: Condition "r.ifname[15UL /* sizeof (r.ifname) - 1 */]", taking false branch. /home/sbrivio/passt/conf.c:2024:4: 16. tainted_data: Passing tainted expression "r.last" to "fwd_rule_add", which uses it as a loop boundary. /home/sbrivio/passt/fwd_rule.c:197:2: Tainted data flows to a taint sink 16.1. lower_bounds: Casting narrower unsigned "new->last" to wider signed type "int" effectively tests its lower bound. /home/sbrivio/passt/fwd_rule.c:197:2: 16.2. path: Condition "new->first > new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:197:2: 16.3. lower_bounds: Checking lower bounds of unsigned scalar "new->last" by taking the false branch of "new->first > new->last". /home/sbrivio/passt/fwd_rule.c:202:2: 16.4. path: Condition "!new->first", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 16.5. path: Condition "!new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 16.6. lower_bounds: Casting narrower unsigned "new->last" to wider signed type "int" effectively tests its lower bound. /home/sbrivio/passt/fwd_rule.c:206:2: 16.7. path: Condition "(in_port_t)(new->to + new->last - new->first) < new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:211:2: 16.8. path: Condition "new->flags & -8 /* ~allowed_flags */", taking false branch. /home/sbrivio/passt/fwd_rule.c:216:2: 16.9. path: Condition "new->flags & (1UL /* 1UL << 0 */)", taking true branch. /home/sbrivio/passt/fwd_rule.c:217:3: 16.10. path: Condition "!inany_equals(&new->addr, (union inany_addr const *)&in6addr_any)", taking false branch. /home/sbrivio/passt/fwd_rule.c:224:3: 16.11. path: Condition "!(fwd->caps & (1UL /* 1UL << 0 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:228:3: 16.12. path: Condition "!(fwd->caps & (2UL /* 1UL << 1 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:232:2: 16.13. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:242:2: 16.14. path: Condition "new->proto == IPPROTO_TCP", taking true branch. /home/sbrivio/passt/fwd_rule.c:243:3: 16.15. path: Condition "!(fwd->caps & (4UL /* 1UL << 2 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:247:2: 16.16. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:258:2: 16.17. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 16.18. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 16.19. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 16.20. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 16.21. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 16.22. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 16.23. path: Condition "i < fwd->count", taking false branch. /home/sbrivio/passt/fwd_rule.c:270:2: 16.24. path: Condition "fwd->count >= 255U /* (int)(sizeof (fwd->rules) / sizeof (fwd->rules[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:274:2: 16.25. path: Condition "fwd->sock_count + num > 327680U /* (int)(sizeof (fwd->socks) / sizeof (fwd->socks[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:281:2: 16.26. loop_bound_upper: Using tainted expression "new->last" as a loop boundary. /home/sbrivio/passt/conf.c:2024:4: 17. remediation: Ensure that tainted values are properly sanitized, by checking that their values are within a permissible range.
I'll expand the comment and remove the newline between this and the for loop.
We've already checked that last >= first, so using num is safer to deal with at this point than ARRAY_SIZE() + first, which could in principle overflow even if sock_count + num is perfectly ok.
Using 'num' won't work. It shouldn't overflow anyway because the addition happens in 'int'.
It shouldn't overflow, but proving that requires knowing that: a. sock_count is bounded by ARRAY_SIZE(socks) b. first, last and num are bounded by 2^16 c. ARRAY_SIZE(socks) + 2^16 won't overflow (in unsigned int)
I'm not sure which part coverity is missing. (c) at least requires knowledge which is not found in immediately adjacent code.
It's missing the fact that 'num' is already checked above (that is, a.), and due to that we know we're not overflowing fwd->rulesocks[x].
Oh... and... I just remembered that ARRAY_SIZE() is int, not unsigned (or size_t). I thought about changing that at some point, but it seemed to cause more trouble than it was worth. Does keep tripping me though, since it seems like it logically ought to be unsigned. Signed overflows are much nastier (UB) that unsigned overflows.
I'll try to change the rest if I find some time but it doesn't really look that critical to me.
for (port = new->first; port <= new->last; port++) fwd->rulesocks[fwd->count][port - new->first] = -1;
-- 2.43.0
-- Stefano
-- Stefano
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
On Wed, 6 May 2026 18:00:28 +1000
David Gibson
On Wed, May 06, 2026 at 09:46:44AM +0200, Stefano Brivio wrote:
On Wed, 6 May 2026 00:41:15 +1000 David Gibson
wrote: On Tue, May 05, 2026 at 12:13:41PM +0200, Stefano Brivio wrote:
On Tue, 5 May 2026 16:22:43 +1000 David Gibson
wrote: On Tue, May 05, 2026 at 01:11:42AM +0200, Stefano Brivio wrote:
The new checks are actually sufficient but not enough for Coverity Scan. Now that fwd->sock_count and new->last are affected or supplied by clients, we need explicit (albeit redundant) checks on them.
Signed-off-by: Stefano Brivio
I'm assuming this does squash the warnings, but I think it does so in a somewhat confusing way.
You don't need to assume that, you could try yourself without this patch and you'll see exactly two warnings with a lot of details.
I'm getting better, but I'm by no means well yet. Some emails were within my capacity. Sorting out the new license key and doing a Coverity run, not so much.
--- fwd_rule.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/fwd_rule.c b/fwd_rule.c index b55e4df..03e8e80 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -271,13 +271,22 @@ int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) warn("Too many rules (maximum %d)", ARRAY_SIZE(fwd->rules)); return -ENOSPC; } + if ((fwd->sock_count + num) > ARRAY_SIZE(fwd->socks)) { warn("Rules require too many listening sockets (maximum %d)", ARRAY_SIZE(fwd->socks)); return -ENOSPC; } + /* Redundant, to make static checkers happy */ + if (fwd->sock_count > ARRAY_SIZE(fwd->socks)) + return -ENOSPC;
So there's actually two conditions that this is kind of relevant to:
1) (fwd->sock_count > ARRAY_SIZE(fwd->socks)) on entry
That means something is horribly wrong before we were even called. So, I think that would be better as an assert().
But the (good) reason why Coverity Scan is complaining about a missing check here is that the client is able to manipulate sock_count (via num) in a previous call to this function,
Uh... they should never be able to make sock_count outside of [0, ARRAY_SIZE(socks)). If they can, that's a bug elsewhere.
Indeed, but Coverity Scan doesn't seem to realise that.
so making us crash is exactly what we want to avoid:
/home/sbrivio/passt/conf.c:2024:4: Type: Untrusted value as argument (TAINTED_SCALAR)
/home/sbrivio/passt/conf.c:1992:3: Tainted data flows to a taint sink 1. path: Condition "read_u8(fd, &pif)", taking false branch. /home/sbrivio/passt/conf.c:1995:3: 2. path: Condition "pif == 0", taking false branch. /home/sbrivio/passt/conf.c:1998:3: 3. path: Condition "pif >= 4 /* (int)(sizeof (c->fwd_pending) / sizeof (c->fwd_pending[0])) */", taking false branch. /home/sbrivio/passt/conf.c:1998:3: 4. path: Condition "!(fwd = c->fwd_pending[pif])", taking false branch. /home/sbrivio/passt/conf.c:2004:3: 5. path: Condition "read_u32(fd, &count)", taking false branch. /home/sbrivio/passt/conf.c:2007:3: 6. path: Condition "count > 255U /* (1U << 8) - 1 */", taking false branch. /home/sbrivio/passt/conf.c:2013:3: 7. path: Condition "i < count", taking true branch. /home/sbrivio/passt/conf.c:2014:4: 8. tainted_argument: Calling function "fwd_rule_read" taints argument "r". /home/sbrivio/passt/fwd_rule.c:659:2: Tainted data flows to a taint sink 8.1. tainted_data_argument: Calling function "read_all_buf" taints parameter "*rule". /home/sbrivio/passt/serialise.c:39:2: Tainted data flows to a taint sink 8.1.1. var_assign_parm: Assigning: "p" = "buf". /home/sbrivio/passt/serialise.c:41:2: 8.1.2. path: Condition "left", taking true branch. /home/sbrivio/passt/serialise.c:44:3: 8.1.3. path: Condition "left <= len", taking true branch. /home/sbrivio/passt/serialise.c:47:4: 8.1.4. tainted_data_argument: Calling function "read" taints parameter "*p". [Note: The source code implementation of the function has been overridden by a builtin model.] /home/sbrivio/passt/serialise.c:48:37: 8.1.5. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:50:3: 8.1.6. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:53:3: 8.1.7. path: Condition "rc == 0", taking false branch. /home/sbrivio/passt/serialise.c:60:2: 8.1.8. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/serialise.c:41:2: 8.1.9. path: Condition "left", taking true branch. /home/sbrivio/passt/serialise.c:44:3: 8.1.10. path: Condition "left <= len", taking true branch. /home/sbrivio/passt/serialise.c:48:37: 8.1.11. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:50:3: 8.1.12. path: Condition "rc < 0", taking false branch. /home/sbrivio/passt/serialise.c:53:3: 8.1.13. path: Condition "rc == 0", taking false branch. /home/sbrivio/passt/serialise.c:60:2: 8.1.14. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/serialise.c:41:2: 8.1.15. path: Condition "left", taking false branch. /home/sbrivio/passt/fwd_rule.c:659:2: 8.2. path: Condition "read_all_buf(fd, rule, 40UL /* sizeof (*rule) */)", taking false branch. /home/sbrivio/passt/conf.c:2014:4: 9. path: Condition "fwd_rule_read(fd, &r)", taking false branch. /home/sbrivio/passt/conf.c:2017:4: 10. path: Condition "r.ifname[15UL /* sizeof (r.ifname) - 1 */]", taking false branch. /home/sbrivio/passt/conf.c:2024:4: 11. tainted_data_transitive: Call to function "fwd_rule_add" with tainted argument "r.last" transitively taints "fwd->sock_count". /home/sbrivio/passt/fwd_rule.c:197:2: Tainted data flows to a taint sink 11.1. path: Condition "new->first > new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:202:2: 11.2. path: Condition "!new->first", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 11.3. path: Condition "!new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 11.4. path: Condition "(in_port_t)(new->to + new->last - new->first) < new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:211:2: 11.5. path: Condition "new->flags & -8 /* ~allowed_flags */", taking false branch. /home/sbrivio/passt/fwd_rule.c:216:2: 11.6. path: Condition "new->flags & (1UL /* 1UL << 0 */)", taking true branch. /home/sbrivio/passt/fwd_rule.c:217:3: 11.7. path: Condition "!inany_equals(&new->addr, (union inany_addr const *)&in6addr_any)", taking false branch. /home/sbrivio/passt/fwd_rule.c:224:3: 11.8. path: Condition "!(fwd->caps & (1UL /* 1UL << 0 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:228:3: 11.9. path: Condition "!(fwd->caps & (2UL /* 1UL << 1 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:232:2: 11.10. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:242:2: 11.11. path: Condition "new->proto == IPPROTO_TCP", taking true branch. /home/sbrivio/passt/fwd_rule.c:243:3: 11.12. path: Condition "!(fwd->caps & (4UL /* 1UL << 2 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:247:2: 11.13. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:258:2: 11.14. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 11.15. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 11.16. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 11.17. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 11.18. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 11.19. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 11.20. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 11.21. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 11.22. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 11.23. path: Condition "i < fwd->count", taking false branch. /home/sbrivio/passt/fwd_rule.c:270:2: 11.24. path: Condition "fwd->count >= 255U /* (int)(sizeof (fwd->rules) / sizeof (fwd->rules[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:274:2: 11.25. path: Condition "fwd->sock_count + num > 327680U /* (int)(sizeof (fwd->socks) / sizeof (fwd->socks[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:281:2: 11.26. path: Condition "port <= new->last", taking true branch. /home/sbrivio/passt/fwd_rule.c:282:53: 11.27. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/fwd_rule.c:281:2: 11.28. path: Condition "port <= new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:285:2: 11.29. parm_assign: Assigning: "fwd->sock_count" += "num", which taints "fwd->sock_count". /home/sbrivio/passt/conf.c:2024:4: 12. path: Condition "fwd_rule_add(fwd, &r) < 0", taking false branch. /home/sbrivio/passt/conf.c:2026:3: 13. path: Jumping back to the beginning of the loop. /home/sbrivio/passt/conf.c:2013:3: 14. path: Condition "i < count", taking true branch. /home/sbrivio/passt/conf.c:2014:4: 15. path: Condition "fwd_rule_read(fd, &r)", taking false branch. /home/sbrivio/passt/conf.c:2017:4: 16. path: Condition "r.ifname[15UL /* sizeof (r.ifname) - 1 */]", taking false branch. /home/sbrivio/passt/conf.c:2024:4: 17. tainted_data: Passing tainted expression "fwd->sock_count" to "fwd_rule_add", which uses it as an offset. /home/sbrivio/passt/fwd_rule.c:197:2: Tainted data flows to a taint sink 17.1. path: Condition "new->first > new->last", taking false branch. /home/sbrivio/passt/fwd_rule.c:202:2: 17.2. path: Condition "!new->first", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 17.3. path: Condition "!new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:206:2: 17.4. path: Condition "(in_port_t)(new->to + new->last - new->first) < new->to", taking false branch. /home/sbrivio/passt/fwd_rule.c:211:2: 17.5. path: Condition "new->flags & -8 /* ~allowed_flags */", taking false branch. /home/sbrivio/passt/fwd_rule.c:216:2: 17.6. path: Condition "new->flags & (1UL /* 1UL << 0 */)", taking true branch. /home/sbrivio/passt/fwd_rule.c:217:3: 17.7. path: Condition "!inany_equals(&new->addr, (union inany_addr const *)&in6addr_any)", taking false branch. /home/sbrivio/passt/fwd_rule.c:224:3: 17.8. path: Condition "!(fwd->caps & (1UL /* 1UL << 0 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:228:3: 17.9. path: Condition "!(fwd->caps & (2UL /* 1UL << 1 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:232:2: 17.10. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:242:2: 17.11. path: Condition "new->proto == IPPROTO_TCP", taking true branch. /home/sbrivio/passt/fwd_rule.c:243:3: 17.12. path: Condition "!(fwd->caps & (4UL /* 1UL << 2 */))", taking false branch. /home/sbrivio/passt/fwd_rule.c:247:2: 17.13. path: Falling through to end of if statement. /home/sbrivio/passt/fwd_rule.c:258:2: 17.14. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 17.15. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 17.16. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 17.17. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 17.18. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 17.19. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 17.20. path: Condition "i < fwd->count", taking true branch. /home/sbrivio/passt/fwd_rule.c:261:3: 17.21. path: Condition "!fwd_rule_conflicts(new, &fwd->rules[i])", taking true branch. /home/sbrivio/passt/fwd_rule.c:262:4: 17.22. path: Continuing loop. /home/sbrivio/passt/fwd_rule.c:258:2: 17.23. path: Condition "i < fwd->count", taking false branch. /home/sbrivio/passt/fwd_rule.c:270:2: 17.24. path: Condition "fwd->count >= 255U /* (int)(sizeof (fwd->rules) / sizeof (fwd->rules[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:274:2: 17.25. path: Condition "fwd->sock_count + num > 327680U /* (int)(sizeof (fwd->socks) / sizeof (fwd->socks[0])) */", taking false branch. /home/sbrivio/passt/fwd_rule.c:280:2: 17.26. data_index: Using tainted expression "fwd->sock_count" as an index to array "fwd->socks". /home/sbrivio/passt/conf.c:2024:4: 18. remediation: Ensure that tainted values are properly sanitized, by checking that their values are within a permissible range.
At the same time, Coverity Scan doesn't realise that 'num' is positive,
Uh.. what? num is unsigned, so it must know it's positive.
It looks like it doesn't for some reason.
but we know that, so this will never trigger. As I wrote in the comment, this check is redundant and it won't trigger. It's just good to have to avoid noise from false positives.
Agreed, but sock_count < ARRAY_SIZE() is a fundamental invariant of the data structure, so that makes more sense to document/enforce with a check.
Feel free to propose a patch at a later point. I don't understand what kind of changes you want. I'll document this better in v9 but that's all I can do for the moment. -- Stefano
participants (3)
-
David Gibson
-
Laurent Vivier
-
Stefano Brivio