On Thu, Jun 12, 2025 at 05:50:39PM +0200, Dion Bosschieter wrote:
Upon VM bootstrapping (start,restore,incoming migration)
iptablesCreateBaseChainsFW is called and unconditionally deletes and
reinserts top-level firewall chain jumps (e.g. INPUT, FORWARD rules).
This briefly opens a hole in the firewall, allowing packets through
until the insertions complete.
This commit ensures that the base chains are only created once per layer
(IPV4/IPV6) and checks whether the expected rules already exist using
`iptables -C`. If they do, no delete/insert operations are performed.
This eliminates the short window where packets could bypass filters during
VM lifecycle operations.
Signed-off-by: Dion Bosschieter <dionbosschieter(a)gmail.com>
---
src/nwfilter/nwfilter_ebiptables_driver.c | 79 ++++++++++++++---------
1 file changed, 47 insertions(+), 32 deletions(-)
diff --git a/src/nwfilter/nwfilter_ebiptables_driver.c
b/src/nwfilter/nwfilter_ebiptables_driver.c
index 067df6e612..42a0133159 100644
--- a/src/nwfilter/nwfilter_ebiptables_driver.c
+++ b/src/nwfilter/nwfilter_ebiptables_driver.c
@@ -131,6 +131,14 @@ static char chainprefixes_host_temp[3] = {
0
};
+typedef struct {
+ const char *chain;
+ const char *position;
+ const char *targetChain;
+} iptablesBaseChainFW;
+
+static bool baseChainFWDefined[VIR_FIREWALL_LAYER_LAST] = { false };
+
static int
printVar(virNWFilterVarCombIter *vars,
char *buf, int bufsize,
@@ -403,38 +411,45 @@ static void
iptablesCreateBaseChainsFW(virFirewall *fw,
virFirewallLayer layer)
{
- virFirewallAddCmdFull(fw, layer,
- true, NULL, NULL,
- "-N", VIRT_IN_CHAIN, NULL);
- virFirewallAddCmdFull(fw, layer,
- true, NULL, NULL,
- "-N", VIRT_OUT_CHAIN, NULL);
- virFirewallAddCmdFull(fw, layer,
- true, NULL, NULL,
- "-N", VIRT_IN_POST_CHAIN, NULL);
- virFirewallAddCmdFull(fw, layer,
- true, NULL, NULL,
- "-N", HOST_IN_CHAIN, NULL);
- virFirewallAddCmdFull(fw, layer,
- true, NULL, NULL,
- "-D", "FORWARD", "-j",
VIRT_IN_CHAIN, NULL);
- virFirewallAddCmdFull(fw, layer,
- true, NULL, NULL,
- "-D", "FORWARD", "-j",
VIRT_OUT_CHAIN, NULL);
- virFirewallAddCmdFull(fw, layer,
- true, NULL, NULL,
- "-D", "FORWARD", "-j",
VIRT_IN_POST_CHAIN, NULL);
- virFirewallAddCmdFull(fw, layer,
- true, NULL, NULL,
- "-D", "INPUT", "-j",
HOST_IN_CHAIN, NULL);
- virFirewallAddCmd(fw, layer,
- "-I", "FORWARD", "1",
"-j", VIRT_IN_CHAIN, NULL);
- virFirewallAddCmd(fw, layer,
- "-I", "FORWARD", "2",
"-j", VIRT_OUT_CHAIN, NULL);
- virFirewallAddCmd(fw, layer,
- "-I", "FORWARD", "3",
"-j", VIRT_IN_POST_CHAIN, NULL);
- virFirewallAddCmd(fw, layer,
- "-I", "INPUT", "1", "-j",
HOST_IN_CHAIN, NULL);
+ iptablesBaseChainFW fw_chains[] = {
+ {"FORWARD", "1", VIRT_IN_CHAIN},
+ {"FORWARD", "2", VIRT_OUT_CHAIN},
+ {"FORWARD", "3", VIRT_IN_POST_CHAIN},
+ {"INPUT", "1", HOST_IN_CHAIN},
+ };
+ size_t i;
+
+ // iptablesCreateBaseChainsFW already ran once for this layer,
+ // we don't have to recreate the base chains on every firewall update
+ if (baseChainFWDefined[layer])
+ return;
+
+ // set defined state so we skip the following logic next run
+ baseChainFWDefined[layer] = true;
+
+ virFirewallStartTransaction(fw, 0);
+
+ for (i = 0; i < G_N_ELEMENTS(fw_chains); i++)
+ virFirewallAddCmd(fw, layer,
+ "-C", fw_chains[i].chain,
+ "-j", fw_chains[i].targetChain, NULL);
+
+ if (virFirewallApply(fw) == 0)
+ // rules already in place
+ return;
+
+ for (i = 0; i < G_N_ELEMENTS(fw_chains); i++) {
+ virFirewallAddCmdFull(fw, layer,
+ true, NULL, NULL,
+ "-N", fw_chains[i].targetChain, NULL);
+ virFirewallAddCmdFull(fw, layer,
+ true, NULL, NULL,
+ "-D", fw_chains[i].chain, "-j",
+ fw_chains[i].targetChain, NULL);
+ virFirewallAddCmd(fw, layer,
+ "-I", fw_chains[i].chain, fw_chains[i].position,
+ "-j", fw_chains[i].targetChain, NULL);
+ }
}
If I purge all iptables rules
iptables -F
iptables -X
and then restart virtnwfilterd and attempt to create a VM I get a fatal
error
# virsh start --console f42
error: Failed to start domain 'f42'
error: internal error: Failed to run firewall command iptables -w -C FORWARD -j
libvirt-in: iptables v1.8.11 (nf_tables): Chain 'libvirt-in' does not exist
Try `iptables -h' or 'iptables --help' for more information.
the 'iptables -w -C FORWARD -j libvirt-in' bit is from this patch IIUC.
The problem here is that you cannot simply call 'virFirewallApply' in
this context.
This method needs to be exclusively adding rules to the virFirewall
object. The call to virFirewallApply happens later on in the code
paths, and currently that later call is picking up the faiiled
transaction your early virFirewallApply call triggers.
To make this work you need to change to use virFirewallAddCmdFull
with '-L' to query what rules currently exist. Pass in a callback
function to receive the output of '-L', and in that callback you
can then use 'virFirwallAddCmd to create the base chains if they
were missing.
With regards,
Daniel
--
|:
https://berrange.com -o-
https://www.flickr.com/photos/dberrange :|
|:
https://libvirt.org -o-
https://fstop138.berrange.com :|
|:
https://entangle-photo.org -o-
https://www.instagram.com/dberrange :|