[PATCH] iptables: Allocate the struct option for iptables 1.8.11 and above

Jussi Laakkonen <[email protected]> Fri, 10 Apr 2026 13:56:33 +0300
Newsgroups dev.linux.lists.connman
Message-ID <[email protected]>
In iptables 1.8.11 (XTABLES_VERSION_CODE 12) the memory leaks were fixed
by always freeing and allocating a new option struct. This changes the
default behavior when using iptables via libxtables to requiring that
the opts need to be allocated before passing it to
xtables_merge_options() (xtables_options_xfrm() calls this).

This adds version checks for the XTABLES_VERSION_CODE to allocate and
accordingly free the struct option passed as opts to libxtables only
with versions 1.8.11 and above.
---
 src/iptables.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 50 insertions(+), 4 deletions(-)

diff --git a/src/iptables.c b/src/iptables.c
index a81a0779..964bbbf9 100644
--- a/src/iptables.c
+++ b/src/iptables.c
@@ -1951,7 +1951,7 @@ static void dump_target(struct connman_iptables_entry *entry)
 	}
 
 	if (xt_t == xt_t->next)
-		free(xt_t);
+		g_free(xt_t);
 }
 
 static void dump_match(struct connman_iptables_entry *entry)
@@ -2005,7 +2005,7 @@ static void dump_match(struct connman_iptables_entry *entry)
 		return;
 	}
 	if (xt_m == xt_m->next)
-		free(xt_m);
+		g_free(xt_m);
 
 out:
 	DBG("\tmatch %s", match->u.user.name);
@@ -2587,6 +2587,38 @@ static struct option iptables_opts[] = {
 	{NULL},
 };
 
+#if XTABLES_VERSION_CODE > 11
+static struct option *iptables_option_alloc(struct option *orig)
+{
+	struct option *copy;
+	size_t count = 0;
+	size_t i;
+
+	if (!orig)
+		return NULL;
+
+	/* Count entries (including terminating zero entry) */
+	while (orig[count].name)
+		count++;
+
+	/* Include the terminating NULL entry */
+	count++;
+
+	copy = g_malloc0(count * sizeof(struct option));
+	if (!copy)
+		return NULL;
+
+	for (i = 0; i < count; i++) {
+		copy[i] = orig[i];
+		copy[i].name = orig[i].name;
+		copy[i].has_arg = orig[i].has_arg;
+		copy[i].val = orig[i].val;
+	}
+
+	return copy;
+}
+#endif
+
 void iptables_exit(enum xtables_exittype status, const char *msg, ...)
 			__attribute__((noreturn, format(printf,2,3)));
 
@@ -2862,7 +2894,7 @@ static struct xtables_match *prepare_matches(struct connman_iptables *table,
 			g_free(xt_m->m);
 
 			if (xt_m == xt_m->next)
-				free(xt_m);
+				g_free(xt_m);
 
 			xt_m = NULL;
 		}
@@ -2888,7 +2920,7 @@ static struct xtables_match *prepare_matches(struct connman_iptables *table,
 			g_free(xt_m->m);
 
 			if (xt_m == xt_m->next)
-				free(xt_m);
+				g_free(xt_m);
 
 			xt_m = NULL;
 		}
@@ -3569,7 +3601,11 @@ static void reset_xtables(void)
 	 */
 	if (xt_params->opts != xt_params->orig_opts) {
 		g_free(xt_params->opts);
+#if XTABLES_VERSION_CODE > 11
+		xt_params->opts = iptables_option_alloc(xt_params->orig_opts);
+#else
 		xt_params->opts = xt_params->orig_opts;
+#endif
 	}
 	xt_params->option_offset = 0;
 }
@@ -4061,6 +4097,11 @@ int __connman_iptables_init(void)
 	table_hash_ipv6 = g_hash_table_new_full(g_str_hash, g_str_equal,
 						NULL, remove_table);
 
+#if XTABLES_VERSION_CODE > 11
+	iptables_globals.opts = iptables_option_alloc(iptables_opts);
+	ip6tables_globals.opts = iptables_option_alloc(iptables_opts);
+#endif
+
 	return 0;
 }
 
@@ -4068,6 +4109,11 @@ void __connman_iptables_cleanup(void)
 {
 	DBG("");
 
+#if XTABLES_VERSION_CODE > 11
+	g_free(iptables_globals.opts);
+	g_free(ip6tables_globals.opts);
+#endif
+
 	g_hash_table_destroy(table_hash);
 	g_hash_table_destroy(table_hash_ipv6);
 }
-- 
2.47.3