Re: [PATCH net 02/10] netfilter: ebt_nflog: pin the NFLOG backend
Pablo Neira Ayuso <[email protected]> Wed, 5 Aug 2026 19:18:01 +0200
| Newsgroups | gmane.comp.security.firewalls.netfilter.devel,gmane.linux.network |
|---|---|
| Message-ID | <anNwSVI29j5-FfwJ@chamomile> |
Hi Jakub,
This refers to pre-existing issue, it seems the model is now not
indicating this for some reason, see below for details.
On Tue, Aug 04, 2026 at 05:15:29PM -0700, Jakub Kicinski wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> ---
> netfilter: ebt_nflog: pin the NFLOG backend
>
> This adds a module reference on the ULOG logger when an ebtables nflog
> rule is validated, and drops it from a new .destroy callback, so that
> nfnetlink_log cannot be unloaded while such a rule is installed. It also
> requests the nfnetlink_log module for legacy (non nft_compat) callers.
>
> A few questions about the change and the commit message.
This patch is doing exactly the same as xt_LOG and xt_NFLOG so it is
needed.
> > nf_log_unregister() runs after the per-net teardown so its final RCU
> > grace period also drains readers that obtained the logger from a per-net
> > binding.
>
> [Severity: High]
> Is this description accurate? This isn't a bug introduced by this patch,
> but the ordering seems to be the other way around.
>
> nfnetlink_log_fini() frees the per-net state first:
>
> net/netfilter/nfnetlink_log.c:nfnetlink_log_fini() {
> ...
> unregister_pernet_subsys(&nfnl_log_net_ops);
> nf_log_unregister(&nfulnl_logger);
> ...
> }
>
> unregister_pernet_subsys() reaches ops_undo_list(), where the only
> synchronize_rcu() happens before the exit callbacks run, and the kfree
> follows immediately after them:
>
> net/core/net_namespace.c:ops_undo_list() {
> ...
> list_for_each_entry_continue_reverse(ops, ops_list, list)
> ops_exit_list(ops, net_exit_list);
>
> ops = saved_ops;
> list_for_each_entry_continue_reverse(ops, ops_list, list)
> ops_free_list(ops, net_exit_list);
> ...
> }
>
> So the grace period inside nf_log_unregister() runs after struct
> nfnl_log_net has already been freed, which would mean it protects the
> module text rather than the per-net data.
>
> Does the per-net binding path stay exposed after this patch? A logger
> bound per-net (sysctl /proc/sys/net/netfilter/nf_log/<pf>, or
> nf_log_bind_pf()) is only cleared in the pernet exit hook:
>
> net/netfilter/nfnetlink_log.c:nfnl_log_net_exit() {
> nf_log_unset(net, &nfulnl_logger);
> ...
> }
>
> and nf_log_unset() does not wait for readers, since the grace period was
> removed by the commit named in the Fixes: tag:
>
> net/netfilter/nf_log.c:nf_log_unset() {
> ...
> RCU_INIT_POINTER(net->nf.nf_loggers[i], NULL);
> ...
> }
>
> A reader that resolved the logger through the per-net branch of
> nf_log_packet():
>
> if (loginfo != NULL)
> logger = rcu_dereference(loggers[pf][loginfo->type]);
> else
> logger = rcu_dereference(net->nf.nf_loggers[pf]);
>
> can still be inside nfulnl_log_packet() -> nfnl_log_pernet(net) ->
> instance_lookup_get_rcu() -> __instance_lookup() when ops_free_list()
> frees the blob, which is the same KASAN signature quoted above with no
> ebtables rule involved. Reachable loginfo == NULL callers today include
> nf_log_trace(), the conntrack helper log in net/netfilter/nf_conntrack_helper.c
> and net/netfilter/nfnetlink_osf.c.
>
> Would reordering nfnetlink_log_fini() so that nf_log_unregister() runs
> before unregister_pernet_subsys() close all of the frontends at once?
This sounds like a pre-existing issue.
This needs a follow up which does not belong to this patch IMO.
> > diff --git a/net/bridge/netfilter/ebt_nflog.c b/net/bridge/netfilter/ebt_nflog.c
> > index 61bf8f4465ab7..426f8adc912c7 100644
> > --- a/net/bridge/netfilter/ebt_nflog.c
> > +++ b/net/bridge/netfilter/ebt_nflog.c
> > @@ -41,11 +41,25 @@ ebt_nflog_tg(struct sk_buff *skb, const struct xt_action_param *par)
> > static int ebt_nflog_tg_check(const struct xt_tgchk_param *par)
> > {
> > struct ebt_nflog_info *info = par->targinfo;
> > + int ret;
> >
> > if (info->flags & ~EBT_NFLOG_MASK)
> > return -EINVAL;
> > info->prefix[EBT_NFLOG_PREFIX_SIZE - 1] = '\0';
> > - return 0;
> > +
> > + ret = nf_logger_find_get(par->family, NF_LOG_TYPE_ULOG);
>
> [Severity: Medium]
> Should the commit message mention that this can reject rulesets that used
> to load? ebt_nflog_tg_check() previously returned 0 unconditionally, so an
> ebtables nflog rule installed fine and simply logged nothing when
> loggers[pf][NF_LOG_TYPE_ULOG] was NULL. Now the raw -ENOENT from
> nf_logger_find_get() is propagated out through xt_check_target(), so
> ebtables -A ... --nflog and ebtables-restore fail. This seems worth
> spelling out given the Cc: stable.
>
> Also, is the stated parity with xt_NFLOG complete? NETFILTER_XT_TARGET_NFLOG
> carries select NETFILTER_NETLINK_LOG in net/netfilter/Kconfig, while
> BRIDGE_EBT_NFLOG has no select or depends:
>
> net/bridge/netfilter/Kconfig:
> config BRIDGE_EBT_NFLOG
> tristate "ebt: nflog support"
> help
> This option enables the nflog watcher, which allows to LOG
> messages through the netfilter logging API, which can use
> either the old LOG target, the old ULOG target or nfnetlink_log
> as backend.
>
> On a kernel with CONFIG_BRIDGE_EBT_NFLOG set and
> CONFIG_NETFILTER_NETLINK_LOG=n, the request_module() fallback below can
> never succeed, so the target becomes unusable. Should a
> select NETFILTER_NETLINK_LOG be added here? The help text claiming the
> watcher can use the old LOG or ULOG targets as backend also no longer
> matches the code.
This sounds again like a pre-existing issue in ebt_nflog's Kconfig.
>
> > + if (ret != 0 && !par->nft_compat) {
> > + request_module("%s", "nfnetlink_log");
> > +
> > + ret = nf_logger_find_get(par->family, NF_LOG_TYPE_ULOG);
> > + }
> > +
> > + return ret;
>
> [Severity: Medium]
> Who loads nfnetlink_log for the nft_compat case?
This is again a pre-existing issue that should be handled in a follow
up, it affects xt_LOG and xt_NFLOG too.
> The !par->nft_compat skip is copied from xt_NFLOG, but xt_NFLOG also
> declares MODULE_SOFTDEP("pre: nfnetlink_log"), and ebt_nflog.c has
> no MODULE_SOFTDEP at all, so demand-loading ebt_nflog through
> xt_request_find_target(NFPROTO_BRIDGE, "nflog", rev) does not bring
> in the backend.
>
> The -ENOENT recovery in nft_compat only matches the uppercase names:
>
> net/netfilter/nft_compat.c:nft_target_init() {
> ...
> if (ret == -ENOENT) {
> const char *modname = NULL;
>
> if (strcmp(target->name, "LOG") == 0)
> modname = "nf_log_syslog";
> else if (strcmp(target->name, "NFLOG") == 0)
> modname = "nfnetlink_log";
>
> if (modname &&
> nft_request_module(ctx->net, "%s", modname) == -EAGAIN)
> return -EAGAIN;
> }
> ...
> }
>
> The target registered here is .name = "nflog", so modname stays NULL, no
> module is requested and there is no -EAGAIN retry. Does an nftables
> bridge-family compat rule using nflog now fail with -ENOENT unless
> nfnetlink_log is already resident, where it succeeded before? Adding
> MODULE_SOFTDEP("pre: nfnetlink_log") to ebt_nflog.c, or extending the name
> mapping in nft_target_init(), would seem to cover it.
>
> > +}
> > +
> > +static void ebt_nflog_tg_destroy(const struct xt_tgdtor_param *par)
> > +{
> > + nf_logger_put(par->family, NF_LOG_TYPE_ULOG);
> > }
> >
> > static struct xt_target ebt_nflog_tg_reg __read_mostly = {
> > @@ -54,6 +68,7 @@ static struct xt_target ebt_nflog_tg_reg __read_mostly = {
> > .family = NFPROTO_BRIDGE,
> > .target = ebt_nflog_tg,
> > .checkentry = ebt_nflog_tg_check,
> > + .destroy = ebt_nflog_tg_destroy,
> > .targetsize = sizeof(struct ebt_nflog_info),
> > .me = THIS_MODULE,
> > };