[Bug 295415] net/if_ethersubr: Promiscuous frames are passed to upper layers with netgraph

From: <bugzilla-noreply_at_freebsd.org>
Date: Tue, 19 May 2026 16:22:30 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=295415

            Bug ID: 295415
           Summary: net/if_ethersubr: Promiscuous frames are passed to
                    upper layers with netgraph
           Product: Base System
           Version: 16.0-CURRENT
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Many People
          Priority: ---
         Component: kern
          Assignee: bugs@FreeBSD.org
          Reporter: martin.mayer@m2-it-solutions.de

Overview
--------

Frames ingressing the NIC which are not destined to this host are passed to
upper layers if netgraph is used and promiscuous mode is enabled.
This can also lead to packets being seen as duplicated/re-routed if the machine
has IP forwarding enabled.


Steps to reproduce
------------------

Add a netgraph to NIC:

  ngctl mkpeer em0: tee lower left && \
  ngctl name em0:lower tee1 && \
  ngctl connect em0: tee1: upper right


Enable promiscuous mode:

  ngctl msg em0: setpromisc 1


Connect host to a shared ethernet (no switched network).
Packets from foreign hosts will also arrive on the machine's NIC.
If you want to see FreeBSD to re-route foreign IP packets, enable IP
forwarding:
  sysctl net.inet.ip.forwarding=1

If foreign hosts ping each other, you'll notice duplicates.


How does this bug affect production environments
------------------------------------------------

If FreeBSD is used in a CARP setup promiscuous mode is enabled. If one wants to
use netgraph, destination checks in ether_input_internal() are skipped.
Because of that, frames which are not destined to the host miss the M_PROMISC
flag in m->m_flags when entering ether_demux() after passing netgraph.

This can lead to duplicate packets until TTL is decremented to the minimum.
It can be even observed in switched networks when DLFs happen or traffic needs
to be flooded for other reasons.

Normal behavior
---------------

If netgraph is not used, a packet is flagged with M_PROMISC if it is a
promiscuously captured frame.

ether_input_internal():

#if defined(INET) || defined(INET6)
        /*
         * Clear M_PROMISC on frame so that carp(4) will see it when the
         * mbuf flows up to Layer 3.
         * FreeBSD's implementation of carp(4) uses the inprotosw
         * to dispatch IPPROTO_CARP. carp(4) also allocates its own
         * Ethernet addresses of the form 00:00:5e:00:01:xx, which
         * is outside the scope of the M_PROMISC test below.
         * TODO: Maintain a hash table of ethernet addresses other than
         * ether_dhost which may be active on this ifp.
         */
        if (ifp->if_carp && (*carp_forus_p)(ifp, eh->ether_dhost)) {
                m->m_flags &= ~M_PROMISC;
        } else
#endif
        {
                /*
                 * If the frame received was not for our MAC address, set the
                 * M_PROMISC flag on the mbuf chain. The frame may need to
                 * be seen by the rest of the Ethernet input path in case of
                 * re-entry (e.g. bridge, vlan, netgraph) but should not be
                 * seen by upper protocol layers.
                 */
                if (!ETHER_IS_MULTICAST(eh->ether_dhost) &&
                    memcmp(IF_LLADDR(ifp), eh->ether_dhost, ETHER_ADDR_LEN) !=
0)
                        m->m_flags |= M_PROMISC;
        }


Afterwards it will be freed and not passed to higher layers.

ether_demux():

        /*
         * Pass promiscuously received frames to the upper layer if the user
         * requested this by setting IFF_PPROMISC. Otherwise, drop them.
         */
        if ((ifp->if_flags & IFF_PPROMISC) == 0 && (m->m_flags & M_PROMISC)) {
                m_freem(m);
                return;
        }


Possible solution
-----------------

A possible solution might be to move the checks at the end of
ether_input_internal() to the beginning of ether_demux().
I wrote quick'n'dirty fixes which proof that this will fix the issue, but I'm
not sure if it will break other things.


External references
-------------------

- https://lists.freebsd.org/archives/freebsd-net/2026-January/008272.html
- https://github.com/opnsense/src/issues/279

-- 
You are receiving this mail because:
You are the assignee for the bug.