Re: [PATCH nf 1/1] netfilter: nf_conntrack_tcp: defer invalid logging until after unlock

zihan xi <[email protected]> Wed, 29 Jul 2026 23:28:36 +0800
Newsgroups gmane.linux.kernel.stable,gmane.comp.security.firewalls.netfilter.devel
Message-ID <CAANe3eQHBikBexNfBaKpfUgzP8iXM=bf_2Xj7pFnT5yJRbOQUA@mail.gmail.com>
On Wed, Jul 29, 2026 at 10:59 PM Florian Westphal <[email protected]> wrote:
>
> Zihan Xi <[email protected]> wrote:
> > nf_conntrack_tcp_packet() can log invalid packets while ct->lock is still
> > held. This happens both through tcp_in_window() -> nf_tcp_log_invalid()
> > and through the NFCT_TCP_INVALID path -> nf_tcp_handle_invalid().
>
> Correct, this is a bug. While I like the general direction, I find this
> patch too large and too complicated.
>
> Please consider making at least two patches to fix this.
>
> - One patch to make nf_tcp_handle_invalid() return bool, then log
> outside of spinlock().  The timeout doesn't even have to be included,
>         you can change the message to omit it (e.g. lowered timeout to
>                         UNACK or whatever).
>
> This will help with review and it will simplify this patch too.
>
> > +enum nf_tcp_invalid_log_type {
> > +     NF_TCP_LOG_NONE,
> > +     NF_TCP_LOG_OVERSHOT,
> > +     NF_TCP_LOG_SEQ_OVER,
> > +     NF_TCP_LOG_ACK_OVER,
> > +     NF_TCP_LOG_SEQ_UNDER,
> > +     NF_TCP_LOG_ACK_UNDER,
>
> This is fine.
>
> > +struct nf_tcp_invalid_log {
> > +     enum nf_tcp_invalid_log_type type;
> > +     u32 value;
> > +     u8 index;
> > +     u8 dir;
> > +     u8 last_index;
>
> index, dir, last_index no longer need this stashing once lowered-timeout
> is solved as outlined above.
>
> > +struct nf_tcp_invalid_logs {
> > +     /* At most one tcp_in_window() log plus one lower-timeout log. */
> > +     struct nf_tcp_invalid_log entries[2];
>
> Same, once the lower-timeout is handled differently this is not needed.
>
> > +static enum nf_ct_tcp_action
> > +nf_tcp_log_invalid(const struct nf_conn *ct,
> > +                const struct ip_ct_tcp_state *sender,
> > +                struct nf_tcp_invalid_logs *logs,
> > +                enum nf_ct_tcp_action ret,
> > +                enum nf_tcp_invalid_log_type type,
> > +                u32 value)
> >  {
>
> This function doesn't log anymore.
>
> >  static enum nf_ct_tcp_action
> >  tcp_in_window(struct nf_conn *ct, enum ip_conntrack_dir dir,
> >             unsigned int index, const struct sk_buff *skb,
> >             unsigned int dataoff, const struct tcphdr *tcph,
> > -           const struct nf_hook_state *hook_state)
> > +           struct nf_tcp_invalid_logs *logs)
> >  {
> >       struct ip_ct_tcp *state = &ct->proto.tcp;
> >       struct ip_ct_tcp_state *sender = &state->seen[dir];
> > @@ -640,31 +728,29 @@ tcp_in_window(struct nf_conn *ct, enum ip_conntrack_dir dir,
> >                       sender->td_end = end;
> >                       sender->flags |= IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED;
> >
> > -                     return nf_tcp_log_invalid(skb, ct, hook_state, sender, NFCT_TCP_IGNORE,
> > -                                               "%u bytes more than expected", overshot);
> > +                     return nf_tcp_log_invalid(ct, sender, logs, NFCT_TCP_IGNORE,
> > +                                       NF_TCP_LOG_OVERSHOT, overshot);
>
> Maybe rename to nf_tcp_store_invalid() or similar.  It only has to stash
> the value (overshoot in this case) and NF_TCP_LOG_OVERSHOT.
>
> Could you also add a lockdep_assert_not_held(ct->Lock) to the old
> nf_ct_log_invalid() plus a comment explaining that we must not call into
> nfnetlink_log with that lock held?
>
> Thanks!

Thanks, this makes sense. I’ll split the fix into at least two
patches, first handling the nf_tcp_handle_invalid() timeout-lowering
log separately by deferring it until after unlocking, then simplifying
the remaining tcp_in_window() invalid logging path and renaming the
helper accordingly. I’ll also add a lockdep assertion/comment to make
the no-log-under-ct->lock rule explicit in v2.