When running builds with UBSan, some undefined behavior was detected in the iteration of common data data structures in OVS. Coincidentally, a bug was reported [1] whose root cause was another, this time undetected, undefined behavior in the iteration macros.
>From both cases, we conclude that the way we're currently iterating the data >structures is prone to errors and UB. This series is an attempt to rewrite >those macros in a UB-safe manner. The core problem is that #define OBJECT_CONTAINING(POINTER, OBJECT, MEMBER) macro is being used on invalid POINTER values. In some cases we use NULL to compute the end-of-loop condition. In others, we allow it to point to non-contained objects (e.g: a non-contained stack allocated "struct ovs_list" as in [1]). In order to systematically solve this in all cases this series introduces a new set of macros that implement a multi-variable loop iteration. They declare a hidden iterator variable inside the loop, used to iterate and evaluate the loop condition and only compute its OBJECT_CONTAINING if it satisfies the loop condition. One consequence of this safety guard is that the pointer provided by the user is set to NULL after the loop (if not exited via "break;"). Apart from normal iteration, many OVS data structures have a _SAFE version of the loop which require the user to declare an extra variable to hold the next value of the iterator. The use of internal iterator variables makes the declaration variable not necessary. However, some users might still need that extra variable. For that reason, this series introduces two versions of _SAFE iteration loops: - The _LONG version keeps using the external variable but avoids calling OBJECT_CONTAINING on invalid values. Note that alghough this version still uses an external variable, it's behavior changes slightly as the "next" variable could be NULL if it's not safe to calculate the OBJECT_CONTAINING of its internal iterator value. - The _SHORT version removes the use of the external variable alltogether. On relevant data structures, an initial patch rewrites the macros using the _LONG, backwards-compatible helpers. A second patch adds the _SHORT version and removes the unneeded variable from the callers. In order to be even more flexible, the original macro name is overloaded and the right version is selected depending on the number of arguments provided by the user. The first patch would be easy to backport and the second would make code cleaner for the master branch. * Testing / reviewing notes * In order to verify this series removes all the loop-related UB, I've tested it on top of Dumitru's series [2] (without patch 1/11, which can hide some still invalid use of OBJECT_CONTAINING). I've also verified no extra errors are reported through clang-analyzer or ASan. There are a number of checkpatch.py errors: - ERROR: Inappropriate bracing around statement: Seems a limitation in checkpatch.py that only happens when a FOR_EACH* macro calls another FOR_EACH* macro. I don't think it's worth modifying checkpatch.py for this. - ERROR: Use ovs_assert() in place of assert(): On tests/*, I'll send an independent patch to exclude the "tests" directory from this check. I have verified the builds pass for MVSC in AppVeyor but have not run any further verification. Note this series changes some potential undefined behavior with some potential NULL pointer dereferencing, which should be easier to catch by the static and dynamic analyzers. * Limitations * The proposed approach benefits code readability, therefore the name of the iterator variable is derived from the name of the object pointer given by the caller. This means that in an unlikely but still possible case in which a caller wants to nest two loops with the same iterating pointer object, the inner loop iterator variable will hide the one declared in the outer loop. This limitation is easy to spot (the compiler will warn) and easy to work around (just declaring another object pointer variable). I found no such code in the OvS or OVN tree. V2 -> V3 - Added ITER_TYPE to MULTIVAR_INIT macro to avoid depending on typeof (missing in MVSC). - Added macro overloading helpers that work around MVSC issues. V1 -> V2 - Added LONG and SHORT versions of _SAFE macros with macro name overloading. - Rebased on top of latest master. - Added Dumitru's patch to fix sparse header inclusion and version recommendation. - Homogenize argument order in helper macros: VAR and MEMBER always first. - Added SHORT version of _SAFE loops to SSET and idlc. [1] https://bugzilla.redhat.com/show_bug.cgi?id=2014942 [2] https://patchwork.ozlabs.org/project/openvswitch/list/?series=277900 Adrian Moreno (17): util: add multi-variable loop iterator macros util: add safe multi-variable iterators util: add helpers to overload SAFE macro list: use multi-variable helpers for list loops list: ensure iterator is NULL after pop loop list: use short version of safe loops if possible hmap: use multi-variable helpers for hmap loops hmap: implement UB-safe hmap pop iterator hmap: use short version of safe loops if possible cmap: use multi-variable iterators hindex: use multi-variable iterators hindex: remove the next variable in safe loops rculist: use multi-variable helpers for loop macros vtep: use _SAFE iterator if freeing the iterator idlc: support short version of SAFE macros sparse: bump recommended version and include headers sset: add SHORT version of SAFE loop macros Documentation/intro/install/general.rst | 2 +- acinclude.m4 | 2 +- include/openvswitch/hmap.h | 116 ++++++++++++------- include/openvswitch/list.h | 100 +++++++++++----- include/openvswitch/shash.h | 15 ++- include/openvswitch/util.h | 146 ++++++++++++++++++++++++ lib/cfm.c | 4 +- lib/classifier.c | 4 +- lib/cmap.h | 24 ++-- lib/conntrack.c | 8 +- lib/dns-resolve.c | 4 +- lib/dpif-netdev.c | 19 ++- lib/fat-rwlock.c | 4 +- lib/hindex.h | 76 +++++++++--- lib/hmapx.c | 4 +- lib/hmapx.h | 14 ++- lib/id-fpool.c | 3 +- lib/ipf.c | 22 ++-- lib/json.c | 4 +- lib/lacp.c | 4 +- lib/lldp/lldpd-structs.c | 7 +- lib/lldp/lldpd.c | 8 +- lib/mac-learning.c | 4 +- lib/mcast-snooping.c | 12 +- lib/namemap.c | 4 +- lib/netdev-afxdp.c | 4 +- lib/netdev-dpdk.c | 8 +- lib/netdev-linux.c | 4 +- lib/netdev-offload-tc.c | 4 +- lib/ofp-msgs.c | 8 +- lib/ovs-lldp.c | 12 +- lib/ovs-numa.h | 4 +- lib/ovsdb-cs.c | 12 +- lib/ovsdb-idl.c | 46 ++++---- lib/ovsdb-map-op.c | 4 +- lib/ovsdb-set-op.c | 4 +- lib/pcap-file.c | 4 +- lib/perf-counter.c | 4 +- lib/poll-loop.c | 4 +- lib/rculist.h | 83 +++++++++----- lib/seq.c | 8 +- lib/shash.c | 8 +- lib/simap.c | 4 +- lib/simap.h | 16 ++- lib/smap.c | 4 +- lib/smap.h | 15 ++- lib/sset.c | 8 +- lib/sset.h | 15 ++- lib/stopwatch.c | 4 +- lib/tnl-ports.c | 16 +-- lib/unixctl.c | 8 +- lib/vconn.c | 4 +- ofproto/bond.c | 6 +- ofproto/connmgr.c | 28 ++--- ofproto/in-band.c | 4 +- ofproto/netflow.c | 8 +- ofproto/ofproto-dpif-ipfix.c | 12 +- ofproto/ofproto-dpif-sflow.c | 4 +- ofproto/ofproto-dpif-trace.c | 4 +- ofproto/ofproto-dpif-xlate.c | 12 +- ofproto/ofproto-dpif.c | 28 +++-- ofproto/ofproto.c | 16 +-- ovsdb/condition.c | 8 +- ovsdb/jsonrpc-server.c | 40 +++---- ovsdb/monitor.c | 36 +++--- ovsdb/ovsdb-idlc.in | 19 ++- ovsdb/ovsdb-server.c | 11 +- ovsdb/ovsdb-tool.c | 7 +- ovsdb/ovsdb.c | 4 +- ovsdb/query.c | 4 +- ovsdb/raft-private.c | 4 +- ovsdb/raft.c | 33 +++--- ovsdb/relay.c | 4 +- ovsdb/replication.c | 8 +- ovsdb/table.c | 4 +- ovsdb/transaction-forward.c | 10 +- ovsdb/transaction.c | 36 +++--- ovsdb/trigger.c | 4 +- tests/test-cmap.c | 7 +- tests/test-hindex.c | 37 ++++++ tests/test-hmap.c | 42 +++++++ tests/test-list.c | 44 ++++++- utilities/ovs-ofctl.c | 4 +- utilities/ovs-vsctl.c | 48 ++++---- vswitchd/bridge.c | 72 ++++++------ vtep/vtep-ctl.c | 20 ++-- 86 files changed, 1007 insertions(+), 546 deletions(-) -- 2.34.1 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev