netfilter: ebtables: do not hook tables by default

If any of these modules is loaded, hooks get registered in all netns:

Before: 'unshare -n nft list hooks' shows:
family bridge hook prerouting {
	-2147483648 ebt_broute
	-0000000300 ebt_nat_hook
}
family bridge hook input {
	-0000000200 ebt_filter_hook
}
family bridge hook forward {
	-0000000200 ebt_filter_hook
}
family bridge hook output {
	+0000000100 ebt_nat_hook
	+0000000200 ebt_filter_hook
}
family bridge hook postrouting {
	+0000000300 ebt_nat_hook
}

This adds 'template 'tables' for ebtables.

Each ebtable_foo registers the table as a template, with an init function
that gets called once the first get/setsockopt call is made.

ebtables core then searches the (per netns) list of tables.
If no table is found, it searches the list of templates instead.
If a template entry exists, the init function is called which will
enable the table and register the hooks (so packets are diverted
to the table).

If no entry is found in the template list, request_module is called.

After this, hook registration is delayed until the 'ebtables'
(set/getsockopt) request is made for a given table and will only
happen in the specific namespace.

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
Florian Westphal 2021-07-23 15:18:01 +02:00 committed by Pablo Neira Ayuso
parent f2e3778db7
commit 87663c39f8
5 changed files with 139 additions and 23 deletions

View File

@ -127,4 +127,6 @@ static inline bool ebt_invalid_target(int target)
return (target < -NUM_STANDARD_TARGETS || target >= 0); return (target < -NUM_STANDARD_TARGETS || target >= 0);
} }
int ebt_register_template(const struct ebt_table *t, int(*table_init)(struct net *net));
void ebt_unregister_template(const struct ebt_table *t);
#endif #endif

View File

@ -98,7 +98,7 @@ static const struct nf_hook_ops ebt_ops_broute = {
.priority = NF_BR_PRI_FIRST, .priority = NF_BR_PRI_FIRST,
}; };
static int __net_init broute_net_init(struct net *net) static int broute_table_init(struct net *net)
{ {
return ebt_register_table(net, &broute_table, &ebt_ops_broute); return ebt_register_table(net, &broute_table, &ebt_ops_broute);
} }
@ -114,19 +114,30 @@ static void __net_exit broute_net_exit(struct net *net)
} }
static struct pernet_operations broute_net_ops = { static struct pernet_operations broute_net_ops = {
.init = broute_net_init,
.exit = broute_net_exit, .exit = broute_net_exit,
.pre_exit = broute_net_pre_exit, .pre_exit = broute_net_pre_exit,
}; };
static int __init ebtable_broute_init(void) static int __init ebtable_broute_init(void)
{ {
return register_pernet_subsys(&broute_net_ops); int ret = ebt_register_template(&broute_table, broute_table_init);
if (ret)
return ret;
ret = register_pernet_subsys(&broute_net_ops);
if (ret) {
ebt_unregister_template(&broute_table);
return ret;
}
return 0;
} }
static void __exit ebtable_broute_fini(void) static void __exit ebtable_broute_fini(void)
{ {
unregister_pernet_subsys(&broute_net_ops); unregister_pernet_subsys(&broute_net_ops);
ebt_unregister_template(&broute_table);
} }
module_init(ebtable_broute_init); module_init(ebtable_broute_init);

View File

@ -86,7 +86,7 @@ static const struct nf_hook_ops ebt_ops_filter[] = {
}, },
}; };
static int __net_init frame_filter_net_init(struct net *net) static int frame_filter_table_init(struct net *net)
{ {
return ebt_register_table(net, &frame_filter, ebt_ops_filter); return ebt_register_table(net, &frame_filter, ebt_ops_filter);
} }
@ -102,19 +102,30 @@ static void __net_exit frame_filter_net_exit(struct net *net)
} }
static struct pernet_operations frame_filter_net_ops = { static struct pernet_operations frame_filter_net_ops = {
.init = frame_filter_net_init,
.exit = frame_filter_net_exit, .exit = frame_filter_net_exit,
.pre_exit = frame_filter_net_pre_exit, .pre_exit = frame_filter_net_pre_exit,
}; };
static int __init ebtable_filter_init(void) static int __init ebtable_filter_init(void)
{ {
return register_pernet_subsys(&frame_filter_net_ops); int ret = ebt_register_template(&frame_filter, frame_filter_table_init);
if (ret)
return ret;
ret = register_pernet_subsys(&frame_filter_net_ops);
if (ret) {
ebt_unregister_template(&frame_filter);
return ret;
}
return 0;
} }
static void __exit ebtable_filter_fini(void) static void __exit ebtable_filter_fini(void)
{ {
unregister_pernet_subsys(&frame_filter_net_ops); unregister_pernet_subsys(&frame_filter_net_ops);
ebt_unregister_template(&frame_filter);
} }
module_init(ebtable_filter_init); module_init(ebtable_filter_init);

View File

@ -85,7 +85,7 @@ static const struct nf_hook_ops ebt_ops_nat[] = {
}, },
}; };
static int __net_init frame_nat_net_init(struct net *net) static int frame_nat_table_init(struct net *net)
{ {
return ebt_register_table(net, &frame_nat, ebt_ops_nat); return ebt_register_table(net, &frame_nat, ebt_ops_nat);
} }
@ -101,19 +101,30 @@ static void __net_exit frame_nat_net_exit(struct net *net)
} }
static struct pernet_operations frame_nat_net_ops = { static struct pernet_operations frame_nat_net_ops = {
.init = frame_nat_net_init,
.exit = frame_nat_net_exit, .exit = frame_nat_net_exit,
.pre_exit = frame_nat_net_pre_exit, .pre_exit = frame_nat_net_pre_exit,
}; };
static int __init ebtable_nat_init(void) static int __init ebtable_nat_init(void)
{ {
return register_pernet_subsys(&frame_nat_net_ops); int ret = ebt_register_template(&frame_nat, frame_nat_table_init);
if (ret)
return ret;
ret = register_pernet_subsys(&frame_nat_net_ops);
if (ret) {
ebt_unregister_template(&frame_nat);
return ret;
}
return ret;
} }
static void __exit ebtable_nat_fini(void) static void __exit ebtable_nat_fini(void)
{ {
unregister_pernet_subsys(&frame_nat_net_ops); unregister_pernet_subsys(&frame_nat_net_ops);
ebt_unregister_template(&frame_nat);
} }
module_init(ebtable_nat_init); module_init(ebtable_nat_init);

View File

@ -44,7 +44,16 @@ struct ebt_pernet {
struct list_head tables; struct list_head tables;
}; };
struct ebt_template {
struct list_head list;
char name[EBT_TABLE_MAXNAMELEN];
struct module *owner;
/* called when table is needed in the given netns */
int (*table_init)(struct net *net);
};
static unsigned int ebt_pernet_id __read_mostly; static unsigned int ebt_pernet_id __read_mostly;
static LIST_HEAD(template_tables);
static DEFINE_MUTEX(ebt_mutex); static DEFINE_MUTEX(ebt_mutex);
#ifdef CONFIG_NETFILTER_XTABLES_COMPAT #ifdef CONFIG_NETFILTER_XTABLES_COMPAT
@ -309,30 +318,57 @@ letscontinue:
/* If it succeeds, returns element and locks mutex */ /* If it succeeds, returns element and locks mutex */
static inline void * static inline void *
find_inlist_lock_noload(struct list_head *head, const char *name, int *error, find_inlist_lock_noload(struct net *net, const char *name, int *error,
struct mutex *mutex) struct mutex *mutex)
{ {
struct { struct ebt_pernet *ebt_net = net_generic(net, ebt_pernet_id);
struct list_head list; struct ebt_template *tmpl;
char name[EBT_FUNCTION_MAXNAMELEN]; struct ebt_table *table;
} *e;
mutex_lock(mutex); mutex_lock(mutex);
list_for_each_entry(e, head, list) { list_for_each_entry(table, &ebt_net->tables, list) {
if (strcmp(e->name, name) == 0) if (strcmp(table->name, name) == 0)
return e; return table;
} }
list_for_each_entry(tmpl, &template_tables, list) {
if (strcmp(name, tmpl->name) == 0) {
struct module *owner = tmpl->owner;
if (!try_module_get(owner))
goto out;
mutex_unlock(mutex);
*error = tmpl->table_init(net);
if (*error) {
module_put(owner);
return NULL;
}
mutex_lock(mutex);
module_put(owner);
break;
}
}
list_for_each_entry(table, &ebt_net->tables, list) {
if (strcmp(table->name, name) == 0)
return table;
}
out:
*error = -ENOENT; *error = -ENOENT;
mutex_unlock(mutex); mutex_unlock(mutex);
return NULL; return NULL;
} }
static void * static void *
find_inlist_lock(struct list_head *head, const char *name, const char *prefix, find_inlist_lock(struct net *net, const char *name, const char *prefix,
int *error, struct mutex *mutex) int *error, struct mutex *mutex)
{ {
return try_then_request_module( return try_then_request_module(
find_inlist_lock_noload(head, name, error, mutex), find_inlist_lock_noload(net, name, error, mutex),
"%s%s", prefix, name); "%s%s", prefix, name);
} }
@ -340,10 +376,7 @@ static inline struct ebt_table *
find_table_lock(struct net *net, const char *name, int *error, find_table_lock(struct net *net, const char *name, int *error,
struct mutex *mutex) struct mutex *mutex)
{ {
struct ebt_pernet *ebt_net = net_generic(net, ebt_pernet_id); return find_inlist_lock(net, name, "ebtable_", error, mutex);
return find_inlist_lock(&ebt_net->tables, name,
"ebtable_", error, mutex);
} }
static inline void ebt_free_table_info(struct ebt_table_info *info) static inline void ebt_free_table_info(struct ebt_table_info *info)
@ -1258,6 +1291,54 @@ out:
return ret; return ret;
} }
int ebt_register_template(const struct ebt_table *t, int (*table_init)(struct net *net))
{
struct ebt_template *tmpl;
mutex_lock(&ebt_mutex);
list_for_each_entry(tmpl, &template_tables, list) {
if (WARN_ON_ONCE(strcmp(t->name, tmpl->name) == 0)) {
mutex_unlock(&ebt_mutex);
return -EEXIST;
}
}
tmpl = kzalloc(sizeof(*tmpl), GFP_KERNEL);
if (!tmpl) {
mutex_unlock(&ebt_mutex);
return -ENOMEM;
}
tmpl->table_init = table_init;
strscpy(tmpl->name, t->name, sizeof(tmpl->name));
tmpl->owner = t->me;
list_add(&tmpl->list, &template_tables);
mutex_unlock(&ebt_mutex);
return 0;
}
EXPORT_SYMBOL(ebt_register_template);
void ebt_unregister_template(const struct ebt_table *t)
{
struct ebt_template *tmpl;
mutex_lock(&ebt_mutex);
list_for_each_entry(tmpl, &template_tables, list) {
if (strcmp(t->name, tmpl->name))
continue;
list_del(&tmpl->list);
mutex_unlock(&ebt_mutex);
kfree(tmpl);
return;
}
mutex_unlock(&ebt_mutex);
WARN_ON_ONCE(1);
}
EXPORT_SYMBOL(ebt_unregister_template);
static struct ebt_table *__ebt_find_table(struct net *net, const char *name) static struct ebt_table *__ebt_find_table(struct net *net, const char *name)
{ {
struct ebt_pernet *ebt_net = net_generic(net, ebt_pernet_id); struct ebt_pernet *ebt_net = net_generic(net, ebt_pernet_id);