[PATCH 0/8] Clean up and fix bugs in port forwarding data structures
The information we need to keep track of which ports are forwarded where is currently split across several different values and data structures in several different places. Worse, a number of those structures are incorrectly sized due to an off by one error, which could lead to buffer overruns. Fix the sizing, and re-organize the data structures in a way that should make it less likely to repeat that mistake. While we're there, correct a similar off-by-one mis-sizing of a number of other arrays. David Gibson (8): Improve types and names for port forwarding configuration Consolidate port forwarding configuration into a common structure udp: Delay initialization of UDP reversed port mapping table Don't use indirect remap functions for conf_ports() Pass entire port forwarding configuration substructure to conf_ports() Treat port numbers as unsigned Fix widespread off-by-one error dealing with port numbers icmp: Correct off by one errors dealing with number of echo request ids Makefile | 2 +- conf.c | 136 +++++++++++++++++++++++---------------------------- icmp.c | 5 +- passt.h | 1 + port_fwd.h | 34 +++++++++++++ tcp.c | 68 ++++++++------------------ tcp.h | 13 ++--- tcp_splice.c | 4 +- udp.c | 59 ++++++++++------------ udp.h | 27 +++++----- 10 files changed, 168 insertions(+), 181 deletions(-) create mode 100644 port_fwd.h -- 2.37.3
enum conf_port_type is local to conf.c and is used to track the port
forwarding mode during configuration. We don't keep it around in the
context structure, however the 'init_detect_ports' and 'ns_detect_ports'
fields in the context are based solely on this. Rather than changing
encoding, just include the forwarding mode into the context structure.
Move the type definition to a new port_fwd.h, which is kind of trivial at
the moment but will have more stuff later.
While we're there, "conf_port_type" doesn't really convey that this enum is
describing how port forwarding is configured. Rename it to port_fwd_mode.
The variables (now fields) of this type also have mildly confusing names
since it's not immediately obvious whether 'ns' and 'init' refer to the
source or destination of the packets. Use "in" (host to guest / init to
ns) and "out" (guest to host / ns to init) instead.
This has the added bonus that we no longer have locals 'udp_init' and
'tcp_init' which shadow global functions.
In addition, add a typedef 'port_fwd_map' for a bitmap of each port number,
which is used in several places.
Signed-off-by: David Gibson
On Fri, 23 Sep 2022 14:57:59 +1000
David Gibson
[...]
--- /dev/null +++ b/port_fwd.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later + * Copyright Red Hat + * Author: Stefano Brivio
+ * Author: David Gibson + */ + +#ifndef PORT_FWD_H +#define PORT_FWD_H + +enum port_fwd_mode { + FWD_SPEC = 1, + FWD_NONE, + FWD_AUTO, + FWD_ALL, +}; + +typedef uint8_t port_fwd_map[DIV_ROUND_UP(USHRT_MAX, 8)];
Given that this gets conveniently embedded in a struct in 2/8, could we avoid the typedef (or perhaps drop it after 2/8)? It makes the actual type less obvious to figure out, and in general I agree with most points from this slide deck: http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/mgp00... :) ...well, unless there's some resulting complexity I'm missing. I reviewed the rest of the series, it all makes sense to me, thanks. -- Stefano
On Sat, Sep 24, 2022 at 12:52:57AM +0200, Stefano Brivio wrote:
On Fri, 23 Sep 2022 14:57:59 +1000 David Gibson
wrote: [...]
--- /dev/null +++ b/port_fwd.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later + * Copyright Red Hat + * Author: Stefano Brivio
+ * Author: David Gibson + */ + +#ifndef PORT_FWD_H +#define PORT_FWD_H + +enum port_fwd_mode { + FWD_SPEC = 1, + FWD_NONE, + FWD_AUTO, + FWD_ALL, +}; + +typedef uint8_t port_fwd_map[DIV_ROUND_UP(USHRT_MAX, 8)]; Given that this gets conveniently embedded in a struct in 2/8, could we avoid the typedef (or perhaps drop it after 2/8)? It makes the actual type less obvious to figure out, and in general I agree with most points from this slide deck:
http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/mgp00...
As do I, in fact especially for array types, due to their idiosyncratic handling in C.
:) ...well, unless there's some resulting complexity I'm missing.
Well, maybe. I added the typedef because of two things: 1) the fact that in one place we use a bitmap of this format separately from the rest for the 'exclude' map in conf_ports(). 2) the fact that we need the size of this map in get_bound_ports(). Having the typedef lets us handle both without duplicating the calculation of the size, which would mean more opportunities to get it wrong. I feel the advantages outweigh the disadvantages of a typedef in this case, but I won't be offended if you disagree. We could use a #define or const of the bitmap size instead.
I reviewed the rest of the series, it all makes sense to me, thanks.
-- David Gibson | 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 Sat, 24 Sep 2022 12:57:25 +1000
David Gibson
On Sat, Sep 24, 2022 at 12:52:57AM +0200, Stefano Brivio wrote:
On Fri, 23 Sep 2022 14:57:59 +1000 David Gibson
wrote: [...]
--- /dev/null +++ b/port_fwd.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later + * Copyright Red Hat + * Author: Stefano Brivio
+ * Author: David Gibson + */ + +#ifndef PORT_FWD_H +#define PORT_FWD_H + +enum port_fwd_mode { + FWD_SPEC = 1, + FWD_NONE, + FWD_AUTO, + FWD_ALL, +}; + +typedef uint8_t port_fwd_map[DIV_ROUND_UP(USHRT_MAX, 8)]; Given that this gets conveniently embedded in a struct in 2/8, could we avoid the typedef (or perhaps drop it after 2/8)? It makes the actual type less obvious to figure out, and in general I agree with most points from this slide deck:
http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/mgp00...
As do I, in fact especially for array types, due to their idiosyncratic handling in C.
:) ...well, unless there's some resulting complexity I'm missing.
Well, maybe. I added the typedef because of two things: 1) the fact that in one place we use a bitmap of this format separately from the rest for the 'exclude' map in conf_ports(). 2) the fact that we need the size of this map in get_bound_ports(). Having the typedef lets us handle both without duplicating the calculation of the size, which would mean more opportunities to get it wrong.
I see, but at the same time: - forgetting to use the new type should be as easy as forgetting to use a define representing the size - the risk of doing stupid things (such as trying to pass this by value), despite the clear name of the typedef, looks to me slightly bigger than with "uint8_t ports[SIZE_PORTS]"
I feel the advantages outweigh the disadvantages of a typedef in this case, but I won't be offended if you disagree. We could use a #define or const of the bitmap size instead.
Well, I see now, and I don't have such a strong preference anymore... even though I would still prefer the define (perhaps SIZE_PORTS, based on NUM_PORTS from 7/8?). Also introducing the first typedef in the whole project for something we can implement almost equivalently, hmm. I don't know, if you still think it's clearly better than the alternative, let's go with it, I just have a slight preference against it at this point. -- Stefano
On Sat, Sep 24, 2022 at 08:28:32AM +0200, Stefano Brivio wrote:
On Sat, 24 Sep 2022 12:57:25 +1000 David Gibson
wrote: On Sat, Sep 24, 2022 at 12:52:57AM +0200, Stefano Brivio wrote:
On Fri, 23 Sep 2022 14:57:59 +1000 David Gibson
wrote: [...]
--- /dev/null +++ b/port_fwd.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later + * Copyright Red Hat + * Author: Stefano Brivio
+ * Author: David Gibson + */ + +#ifndef PORT_FWD_H +#define PORT_FWD_H + +enum port_fwd_mode { + FWD_SPEC = 1, + FWD_NONE, + FWD_AUTO, + FWD_ALL, +}; + +typedef uint8_t port_fwd_map[DIV_ROUND_UP(USHRT_MAX, 8)]; Given that this gets conveniently embedded in a struct in 2/8, could we avoid the typedef (or perhaps drop it after 2/8)? It makes the actual type less obvious to figure out, and in general I agree with most points from this slide deck:
http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/mgp00...
As do I, in fact especially for array types, due to their idiosyncratic handling in C.
:) ...well, unless there's some resulting complexity I'm missing.
Well, maybe. I added the typedef because of two things: 1) the fact that in one place we use a bitmap of this format separately from the rest for the 'exclude' map in conf_ports(). 2) the fact that we need the size of this map in get_bound_ports(). Having the typedef lets us handle both without duplicating the calculation of the size, which would mean more opportunities to get it wrong.
I see, but at the same time:
- forgetting to use the new type should be as easy as forgetting to use a define representing the size
- the risk of doing stupid things (such as trying to pass this by value), despite the clear name of the typedef, looks to me slightly bigger than with "uint8_t ports[SIZE_PORTS]"
I feel the advantages outweigh the disadvantages of a typedef in this case, but I won't be offended if you disagree. We could use a #define or const of the bitmap size instead.
Well, I see now, and I don't have such a strong preference anymore... even though I would still prefer the define (perhaps SIZE_PORTS, based on NUM_PORTS from 7/8?).
Also introducing the first typedef in the whole project for something we can implement almost equivalently, hmm.
I don't know, if you still think it's clearly better than the alternative, let's go with it, I just have a slight preference against it at this point.
I think you convinced me. TBH, I only thought of the size define when I replied here, not when I originally wrote it. I'll respin with that instead of the typedef. -- David Gibson | 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
The configuration for how to forward ports in and out of the guest/ns is
divided between several different variables. For each connect direction
and protocol we have a mode in the udp/tcp context structure, a bitmap
of which ports to forward also in the context structure and an array of
deltas to apply if the outward facing and inward facing port numbers are
different. This last is a separate global variable, rather than being in
the context structure, for no particular reason. UDP also requires an
additional array which has the reverse mapping used for return packets.
Consolidate these into a re-used substructure in the context structure.
Signed-off-by: David Gibson
Because it's connectionless, when mapping UDP ports we need, in addition
to the table of deltas for destination ports needed by TCP, we need an
inverted table to translate the source ports on return packets.
Currently we fill out the inverted table at the same time we construct the
main table in udp_remap_to_tap() and udp_remap_to_init(). However, we
don't use either table until after we've initialized UDP, so we can delay
the construction of the reverse table to udp_init(). This makes the
configuration more symmetric between TCP and UDP which will enable further
cleanups.
Signed-off-by: David Gibson
Now that we've delayed initialization of the UDP specific "reverse" map
until udp_init(), the only difference between the various 'remap' functions
used in conf_ports() is which array they target. So, simplify by open
coding the logic into conf_ports() with a pointer to the correct mapping
array.
Signed-off-by: David Gibson
conf_ports() switches on the optname argument to select the target array
for several updates. Now that all these maps are in a common structure, we
can simplify by just passing in a pointer to the whole struct port_fwd to
update.
Signed-off-by: David Gibson
Port numbers are unsigned values, but we're storing them in (signed) int
variables in some places. This isn't actually harmful, because int is
large enough to hold the entire range of ports. However in places we don't
want to use an in_port_t (usually to avoid overflow on the last iteration
of a loop) it makes more conceptual sense to use an unsigned int. This will
also avoid some problems with later cleanups.
Signed-off-by: David Gibson
Port numbers (for both TCP and UDP) are 16-bit, and so fit exactly into a
'short'. USHRT_MAX is therefore the maximum port number and this is widely
used in the code. Unfortunately, a lot of those places don't actually
want the maximum port number (USHRT_MAX == 65535), they want the total
number of ports (65536). This leads to a number of potentially nasty
consequences:
* We have buffer overruns on the port_fwd::delta array if we try to use
port 65535
* We have similar potential overruns for the tcp_sock_* arrays
* Interestingly udp_act had the correct size, but we can calculate it in
a more direct manner
* We have a logical overrun of the ports bitmap as well, although it will
just use an unused bit in the last byte so isnt harmful
* Many loops don't consider port 65535 (which does mitigate some but not
all of the buffer overruns above)
* In udp_invert_portmap() we incorrectly compute the reverse port
translation for return packets
Correct all these by using a new NUM_PORTS defined explicitly for this
purpose.
Signed-off-by: David Gibson
ICMP echo request and reply packets include a 16-bit 'id' value. We have
some arrays indexed by this id value. Unfortunately we size those arrays
with USHRT_MAX (65535) when they need to be sized by the total number of
id values (65536). This could lead to buffer overruns. Resize the arrays
correctly, using a new define for the purpose.
Signed-off-by: David Gibson
participants (2)
-
David Gibson
-
Stefano Brivio